Канал завершения

Как вы знаете, с помощью done-канала горутина сигнализирует вызывающему, что закончила работать.

Есть пара вариантов реализации.

➊ Принимаем done-канал на входе:

func worker1(done chan struct{}) {
    // do work
    time.Sleep(10 * time.Millisecond)
    close(done)
}
done := make(chan struct{})
go worker1(done)
<-done

➋ Возвращаем done-канал из функции:

func worker2() chan struct{} {
    done := make(chan struct{})
    go func() {
        // do work
        time.Sleep(10 * time.Millisecond)
        close(done)
    }()
    return done
}
done := worker2()
<-done

песочница

У вас есть предпочтения? Сейчас расскажу о моих.

Кто канал создал, тот и закрывает

В Go есть одна эвристика, которую лучше не нарушать без веских причин: кто канал создал, тот и закрывает.

Поэтому мне больше по душе такая реализация канала завершения:

func work() <-chan struct{} {
    done := make(chan struct{})
    go func() {
        // do work
        time.Sleep(10 * time.Millisecond)
        close(done)
    }()
    return done
}
done := work()
<-done

Если нужно не просто сигнализировать о завершении, а возвращать значение — заменяем chan struct{} на нужный тип вроде chan int, и готово.

А если хотим возвращать еще и ошибку, то так:

type Result[T any] struct {
    Value T
    Err   error
}

func work() <-chan Result[int] {
    out := make(chan Result[int], 1)
    go func() {
        // do work
        time.Sleep(10 * time.Millisecond)
        out <- Result[int]{Value: 42}
        close(out)
    }()
    return out
}
out := work()
result := <-out
fmt.Println(result)
// {42 <nil>}

песочница

Удобно!

★ Подписывайтесь на новые заметки.