GoLang: практика асинхронного взаимодействия

Jan 28, 2021 10:53


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 г.)

golang, база знаний, конспект

Previous post Next post
Up