Go: Каналы

В Go существует постулат: "Do not communicate by sharing memory; instead, share memory by communicating" (Не общайтесь разделением памяти. Разделяйте память через общение). Для безопасной коммуникации между горутинами используется специальный тип данных: chan (канал).

Как слайсы и мапы, каналы инициализируются с помощью функции make:

numCh := make(chan int)

Чтение и запись в канал происходит через конструкцию <-. Стрелка ставится перед, если канал читается и после, если записывается:

numCh := make(chan int)

numCh <- 10 // записали значение в канал

num := <- numCh // прочитали значение из канала и записали в переменную "num"

Чтение из канала блокирует текущую горутину, пока не вернется значение:

package main

import (
	"fmt"
)

func main() {
	numCh := make(chan int)

	<-numCh // программа зависнет здесь и будет ошибка: fatal error: all goroutines are asleep - deadlock!

	fmt.Println("program has ended") // эта строка никогда не выведется
}

Запись в канал так же блокирует текущую горутину, пока кто-то не прочтет значение.

Каналы также можно использовать для задачи из прошлого урока:

package main

import (
	"fmt"
)

func main() {
	fmt.Println(maxSum([]int{1, 2, 3}, []int{10, 20, 50})) // [10 20 50]
}

// суммирует значения каждого слайса nums и возвращает тот, который имеет наибольшую сумму
func maxSum(nums1, nums2 []int) []int {
	// канал для результата первой суммы
	s1Ch := make(chan int)
	go sumParallel(nums1, s1Ch)

	// канал для результата второй суммы
	s2Ch := make(chan int)
	go sumParallel(nums2, s2Ch)

	// присваиваем результаты в переменные. Здесь программа будет заблокирована, пока не придут результаты из обоих каналов.
	s1, s2 := <-s1Ch, <-s2Ch

	if s1 > s2 {
		return nums1
	}

	return nums2
}

func sumParallel(nums []int, resCh chan int) {
	s := 0
	for _, n := range nums {
		s += n
	}

	// результат суммы передаем в канал
	resCh <- s
}

Иногда требуется запустить обработчика в отдельной горутине, который будет выполнять работу на протяжении всего цикла жизни программы. С помощью конструкции for range можно читать из канала до того момента, пока он не будет закрыт:

package main

import (
	"fmt"
	"time"
)

func main() {
	// создаем канал, в который будем отправлять сообщения
	msgCh := make(chan string)

	// вызываем функцию асинхронно в горутине
	go printer(msgCh)

	msgCh <- "hello"
	msgCh <- "concurrent"
	msgCh <- "world"

	// закрываем канал
	close(msgCh)

	// и ждем, пока printer закончит работу
	time.Sleep(100 * time.Millisecond)
}

func printer(msgCh chan string) {
	// читаем из канала, пока он открыт
	for msg := range msgCh {
		fmt.Println(msg)
	}

	fmt.Println("printer has finished")
}

Задание

Реализуйте функцию-воркера SumWorker(numsCh chan []int, sumCh chan int), которая суммирует переданные числа из канала numsCh и передает результат в канал sumCh:

numsCh := make(chan []int)
sumCh := make(chan int)

go SumWorker(numsCh, sumCh)
numsCh <- []int{10, 10, 10}

res := <- sumCh // 30
Упражнение не проходит проверку — что делать? 😶

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

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

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются
Loading...

Ваше упражнение проверяется по этим тестам

1package solution
2
3import (
4	"github.com/stretchr/testify/assert"
5	"testing"
6)
7
8func TestSumWorker(t *testing.T) {
9	a := assert.New(t)
10
11	numsCh := make(chan []int)
12	sumCh := make(chan int)
13
14	go SumWorker(numsCh, sumCh)
15
16	numsCh <- nil
17	a.Equal(0, <-sumCh)
18
19	numsCh <- []int{}
20	a.Equal(0, <-sumCh)
21
22	numsCh <- []int{10, 10, 10}
23	a.Equal(30, <-sumCh)
24
25	numsCh <- []int{500, 5, 10, 25}
26	a.Equal(540, <-sumCh)
27}
28

Решение учителя откроется через:

20:00
waiting_clock
← Предыдущий