LINUX.ORG.RU

Не срабатывает канал

 


0

2

Собственно, упражняюсь с горутинами. Есть такой код:

func main() {
	var line chan string = make(chan string)
	var quit chan int = make(chan int)
	var input string

	go func() {
		for {
			fmt.Scan(&input)
			if input == "exit" {
				quit <- 0
				break
			}
			go calc(line, quit, input)
		}
	}()
	for str := range line {
		fmt.Println(str)
	}
}

func calc(line chan string, quit chan int, s string) {
	defer close(line)
	defer close(quit)
	select {
	case line <- s:
		line <- "Echo " + <-line
	case <-quit:
		line <- "Quit"
		return
	}
}

В целом всё хорошо, кроме выхода из горутины. Если ввожу «exit», получаю вот такое сообщение о дедлоке:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        main.go:22 +0x11d

goroutine 6 [chan send]:
main.main.func1()
        main.go:16 +0x125
created by main.main in goroutine 1
        main.go:12 +0xb9
exit status 2
. Такое впечатление, что он пытается прочитать из канала text и не может. При этом все равно, есть там функции close или нет. Как аккуратно в данном случае завершить горутину?

case line <- s:
    line <- "Echo " + <-line

здесь ошибка)

Сначала записываешь в канал, а потом читаешь из него. Тем более читаешь в 2х местах. а calc() вообще на каждый инпут вызываешь => позакрываются кананалы несколько раз

dtorange
()
Ответ на: комментарий от dtorange

Поменял на line <- «Echo » + s, та же хрень. Оно конкретно на «exit» в командной строке не работает, когда в quit запись делается. Или там еще и канал сразу закрывать надо?

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от dtorange

Причем падает на строке for str := range line. Хотя по идее, оно должно ждать когда там данные будут, а потом закрываться, хм. И в случае кейса quit данные в line таки заносятся.

LongLiveUbuntu ★★★★★
() автор топика
Последнее исправление: LongLiveUbuntu (всего исправлений: 1)

Добрый день.

А что работает то?

если ввести что-то отличное от «exit» то calc (ранее запущенная) закроет quit и line как только что то появится в line и при попытки писать получим панику.

если ввести «exit» то calc не запускается и мы имеем вечный for str := range line

alnkapa
()
Ответ на: комментарий от LongLiveUbuntu

У меня так. И это по идее правильно так как после «123» не должно быть «Echo 123» calc ждет в «<-line».

go run .
123
123
234
Echo 234
panic: send on closed channel
        panic: close of closed channel
        panic: close of closed channel

goroutine 18 [running]:
main.calc(0xc0000260c0, 0x0?, {0xc00008c00b?, 0x0?})
        /home/sasha/Project/sky/renew/cmd/ww/main.go:32 +0x147
created by main.main.func1 in goroutine 6
        /home/sasha/Project/sky/renew/cmd/ww/main.go:19 +0x38
exit status 2

go version go1.21.6 linux/amd64

alnkapa
()
Ответ на: комментарий от alnkapa
unc main() {
	var line chan string = make(chan string)
	var quit chan int = make(chan int)
	var input string

	go func() {
		for {
			fmt.Scan(&input)
			if input == "exit" {
				quit <- 0
				return
			}
			go calc(line, quit, input)
		}
	}()
	for str := range line {
		fmt.Println(str)
	}
}

func calc(line chan string, quit chan int, s string) {
	select {
	case line <- s:
		line <- "Echo " + s
	case <-quit:
		line <- "Quit"
		return
	}
}
LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от stave

Вот я и пытаюсь понять, что тут не так. По идее, по приходу значения в канал quit должен срабатывать return в calc, каналы закрываться, а for по каналу - выводить «Quit» и на этом все. Но вместо этого дедлок. Пробовал ставить return во внешний цикл for - та же байда.

LongLiveUbuntu ★★★★★
() автор топика

1) Зачем ты в каждой горутине calc закрываешь каналы? Их нужно закрывать один раз.

2) У тебя все горутины слушают quit, но получит сообщение 0 только одна. Вообще для этого есть стандартный пакет context.

3) Одновременно читать и писать в один и тот же канал line (как в calc) — какая-то странная и плохая идея. Вот это вообще ад какой-то:

	case line <- s:
		line <- "Echo " + <-line

----------------------------------------------------------------

ХЗ, что ты пытаешься сделать, но начни с чего-то такого:

package main

import (
	"context"
	"fmt"
	"sync"
)

func main() {
	var line chan string = make(chan string)
	var ctx, cancel = context.Background()
	defer cancel()

	go func() {
		defer close(line)

		var grp sync.WaitGroup
		var input string
		for {
			fmt.Scan(&input)
			if input == "exit" {
				cancel()
				break
			}
			grp.Add(1)
			go calc(ctx, &grp, line, input)
		}
		grp.Wait()
	}()

	for str := range line {
		fmt.Println(str)
	}
	fmt.Println("Quit")
}

func calc(ctx context.Context, grp *sync.WaitGroup, out chan<- string, s string) {
	defer grp.Done()
	var result = "Echo " + s
	select {
	case <-ctx.Done:
	case out <- result:
	}
}
korvin_ ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Уровнем выше?

Его нужно закрывать в одном месте (в одной горутине), когда он уже никому не нужен, а не во множестве горутин, которые сами же его и используют.

korvin_ ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Сейчас про Context как раз гляну

Если без контекста и WaitGroup, чисто на каналах, то можно примерно так:

package main

import (
	"fmt"
)

func main() {
	var line = make(chan string)

	go func() {
		defer close(line)

		var (
			input string
			count int

			stop = make(chan struct{})
			done = make(chan struct{})
		)
		for {
			fmt.Scan(&input)
			if input == "exit" {
				close(stop)
				break
			}
			count++
			go calc(stop, done, line, input)
		}

		// wait all calc goroutines to finish
		for count > 0 {
			<-done
			count--
		}
	}()

	for str := range line {
		fmt.Println(str)
	}
	fmt.Println("Quit")
}

func calc(stop <-chan struct{}, done chan<- struct{}, out chan<- string, s string) {
	defer func() {
		done <- struct{}{}
	}()
	var result = "Echo " + s
	select {
	case <-stop:
	case out <- result:
	}
}

Проще тогда не плодить по горутине на каждый вызов, а сделать пул. Тем более, в твоём примере вообще не понятно, зачем нужно прерывание горутин calc, в них нет промежуточных точек, где они могли бы досрочно завершиться.

package main

import (
	"fmt"
)

func main() {
	var (
		nroutines = 64
		input     = make(chan string, nroutines)
		output    = make(chan string)
		done      = make(chan struct{}, nroutines)
	)
	
	for i := 0; i < nroutines; i++ {
		go calc(done, output, input)
	}

	go func() {
		defer close(input)
		var line string
		for {
			fmt.Scan(&line)
			if line == "exit" {
				break
			}
			input <- line
		}
	}()
	
	go func() {
		defer close(output)
		for i := 0; i < nroutines; i++ {
			<-done
		}
	}()

	for str := range line {
		fmt.Println(str)
	}
	fmt.Println("Quit")
}

func calc(
	done     chan<- struct{},
	output   chan<- string,
	input  <-chan   string,
) {
	defer func() {
		done <- struct{}{}
	}()
	for s := range input {
		var result = "Echo " + s
		out <- result
	}
}
korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

И каналы потом сразу всей пачкой закроются без утечки ресурсов? Но лучше наверное через более удобные средства сделать, согласен. Как раз сейчас «Learn Go» читаю по этому поводу, ну и упражнения там.

Кстати, такой вопрос: реально бывшему программисту на плюсах в гошники позицией сразу выше джуна устроиться или как? Про ORM знаю, БД проектировал, про Docker в курсе. Что еще? Портфолио?

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от korvin_

По поводу os.OpenFile есть вопросы. Разве во втором и третьем аргументах информация о флагах доступа к файлу не дублируется? Или зачем так сделали?

LongLiveUbuntu ★★★★★
() автор топика
Последнее исправление: LongLiveUbuntu (всего исправлений: 1)
Ответ на: комментарий от LongLiveUbuntu

Кстати, такой вопрос: реально бывшему программисту на плюсах в гошники позицией сразу выше джуна устроиться или как?

Реально. Я так с Джавы перешёл.

korvin_ ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Разве во втором и третьем аргументах информация о флагах доступа к файлу не дублируется?

Не дублируется. В третьем аргументе — режим доступа (и другие параметры) для создаваемого файла. Их ты увидишь потом в выводе ls -l например. А во втором — режим открытия файла. Например, попросить OS открыть файл только для чтения, или только для записи.

korvin_ ★★★★★
()