Сначала разделите ожидание на go -программах и done
-канале.
Используйте sync.WaitGroup
для координации подпрограмм.
func main() {
wait := &sync.WaitGroup{}
N := 3
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, true)
}
wait.Wait()
fmt.Println(`Exiting main`)
}
Каждая подпрограмма будет выглядеть следующим образом это:
// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-T:
fmt.Println(`Tick `, i)
if closer {
return
}
}
}
}
(https://play.golang.org/p/mDO4P56lzBU)
Наше основное веселье c - это успешное ожидание выхода из программы перед выходом. Каждая программа закрывается сама, и мы хотим, чтобы все наши программы были отменены одновременно.
Мы сделаем это с chan
и воспользуемся этой функцией получения из каналов:
QUOTE: Операция приема на закрытом канале всегда может быть выполнена немедленно, давая нулевое значение типа элемента после получения любых ранее отправленных значений. (https://golang.org/ref/spec#Receive_operator)
Мы изменяем наши процедуры для проверки на ЗАКРЫТИЕ:
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-CLOSE:
return
case <-T:
fmt.Println(`Tick `, i)
if closer {
close(CLOSE)
}
}
}
}
, а затем мы изменяем наш func main
, чтобы он проходил ЗАКРЫТЬ через канал, и мы установим переменную closer
так, чтобы только последняя из наших программ запустила закрытие:
func main() {
wait := &sync.WaitGroup{}
N := 3
CLOSE := make(chan struct{})
// Launch the goroutines
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, i == N, CLOSE)
}
// Wait for the goroutines to finish
wait.Wait()
fmt.Println(`Exiting main`)
}
(https://play.golang.org/p/E91CtRAHDp2)
Теперь похоже, что все работает.
Но это не так. Параллельность сложна. В этом коде скрывается ошибка, просто ожидающая, чтобы укусить вас в работе. Давайте рассмотрим это.
Измените наш пример так, чтобы каждая процедура закрывалась:
func main() {
wait := &sync.WaitGroup{}
N := 3
CLOSE := make(chan struct{})
// Launch the goroutines
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, true /*** EVERY GOROUTINE WILL CLOSE ***/, CLOSE)
}
// Wait for the goroutines to finish
wait.Wait()
fmt.Println(`Exiting main`)
}
Измените процедуру так, чтобы до закрытия потребовалось некоторое время. Мы хотим, чтобы две программы закрывались одновременно:
// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-CLOSE:
return
case <-T:
fmt.Println(`Tick `, i)
if closer {
/*** TAKE A WHILE BEFORE CLOSING ***/
time.Sleep(time.Second)
close(CLOSE)
}
}
}
}
(https://play.golang.org/p/YHnbDpnJCks)
Мы производим sh с:
Tick 1
Tick 2
Tick 3
Exiting 1
Exiting 2
panic: close of closed channel
goroutine 7 [running]:
main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
/tmp/sandbox558886627/prog.go:24 +0x2e0
created by main.main
/tmp/sandbox558886627/prog.go:38 +0xc0
Program exited: status 2.
Хотя прием по закрытому каналу немедленно возвращается, вы не можете закрыть закрытый канал.
Нам нужно немного координации. Мы можем сделать это с помощью sync.Mutex
и bool
, чтобы указать, закрыли ли мы канал или нет. Давайте создадим структуру, чтобы сделать это:
type Close struct {
C chan struct{}
l sync.Mutex
closed bool
}
func NewClose() *Close {
return &Close {
C: make(chan struct{}),
}
}
func (c *Close) Close() {
c.l.Lock()
if (!c.closed) {
c.closed=true
close(c.C)
}
c.l.Unlock()
}
Перепишите наш gofun c и наше основное, чтобы использовать нашу новую структуру Close, и мы готовы к go: https://play.golang.org/p/eH3djHu8EXW
Проблема с параллелизмом заключается в том, что вам всегда нужно задаться вопросом, что произойдет, если в коде есть еще один «поток».