1) Флаг для синхронизации - канал с пустой структурой (она почти ничего не стоит, так как в ней нет информации о типах)
done := make(chan struct{})
// [...]
done <- struct{}{}
2) Односторонний канал для сигнализации о завершении работы
func main() {
done := make(chan struct{})
go func(done chan<- struct{}) {
// stuff
done <- struct{}{} // перед завершением сообщаем об этом
} (done) // передаем канал внутрь для ясности
<- done // ожидание завершения горутины
}
И done в горутине нужен только для записи
Можно сделать двусторонний канал односторонним так
done := make(chan struct{})
writingChan := (chan<- struct{})(done) // первые скобки не важны
readingChan := (<-chan struct{})(done) // первые скобки обязательны
3) Если нужно выполнять горутину в в основном треде ОС (main OS thread). Библиотеки как - OpenGL, libSDL, Cocoa - используют локальные для процесса структуры данных (thread local storage). Это значит, что они должны выполняться в основном треде ОС (main OS thread), иначе - ошибка. Функция
runtime.LockOSThread() позволяет прикрепить текущую горутину к текущему треду (thread) ОС
package main
import (
"fmt"
"runtime"
)
func init() {
runtime.LockOSThread() // прикрепить текущую горутину к текущему треду
}
func main() {
/*
коммуникации
*/
done := make(chan struct{}) // <- остановка и выход
stuff := make(chan func()) // <- отправка функций в основной тред
/*
создадим второй тред (в данном случае - вторую горутину, но это не важно)
и начнём отправлять "работу" в первый
*/
go func(done chan<- struct{}, stuff chan<- func()) { // параллельная работа
stuff <- func() { // первый пошёл
fmt.Println("1")
}
stuff <- func() { // второй пошёл
fmt.Println("2")
}
stuff <- func() { // третий пошёл
fmt.Println("3")
}
done <- struct{}{}
}(done, stuff)
Loop:
for {
select {
case do := <-stuff: // получение "работы"
do() // и выполнение
case <-done:
break Loop
}
}
}
4) Вынос блокирующих операций (например, блокирующие IO-операции)
package main
import "os"
func main() {
/*
коммуникации
*/
stop := make(chan struct{}) // нужен для остановки "пишущей" горутины
done := make(chan struct{}) // ожидание её завершения
write := make(chan []byte) // данные для записи
/*
параллельный поток для IO-операций
*/
go func(write <-chan []byte, stop <-chan struct{}, done chan<- struct{}) {
Loop:
for {
select {
case msg := <-write: // получения сообщения для записи
os.Stdout.Write(msg) // асинхронная запись
case <-stop:
break Loop
}
}
done <- struct{}{}
}(write, stop, done)
write <- []byte("Hello ") // отправка сообщений
write <- []byte("World!\n") // на запись
stop <- struct{}{} // остановка
<-done // ожидание завершения
}
Если несколько горутин будут отправлять свои сообщения одной «пишущей», то они всё равно будут блокироваться. В этом случае выручит канал с буфером. Зная, что slice - это ссылочный тип, по каналу будет пересылаться только указатель.
Конспект с
https://habr.com/ru/post/267785/ (2015 г.)