Menu
Coddy logo textTech

Каналы и взаимодействие

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 60 из 107.

Каналы — основной механизм Go для обмена данными между горутинами. Вместо непосредственного совместного использования памяти горутины отправляют значения через каналы и получают их из каналов, следуя философии Go: «Не общайтесь посредством совместного использования памяти; совместно используйте память посредством общения».

Создайте канал с помощью функции make и ключевого слова chan:

messages := make(chan string)

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

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // отправить в канал
    }()
    
    msg := <-messages  // получить из канала
    fmt.Println(msg)   // Вывод: hello
}

По умолчанию отправка и получение блокируются, пока другая сторона не будет готова. Когда goroutine отправляет "hello", она ждёт, пока main не будет готова принять данные. Такое блокирующее поведение обеспечивает встроенную синхронизацию: time.Sleep не нужен.

Вы можете закрыть канал, чтобы сообщить, что больше не будет отправлено значений. Получатели могут обнаружить это:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

Каналы типизированы: chan string может передавать только строки. Такая типобезопасность гарантирует, что goroutine взаимодействуют с правильными типами данных, выявляя ошибки на этапе компиляции, а не во время выполнения.

challenge icon

Задание

Легко

Давайте построим систему ретрансляции сообщений, демонстрирующую, как горутины взаимодействуют через channels. Вы создадите pipeline, в котором messages перемещаются между различными этапами обработки, причём каждый этап выполняется параллельно.

Вы организуете код в двух файлах:

  • relay.go: Define pipeline обработки messages.

    Создайте структуру Message с полями ID (int) и Content (string).

    Реализуйте три функции, представляющие различные этапы вашего pipeline:

    • Producer(messages []Message, out chan Message) — принимает срез messages и отправляет каждое из них в выходной channel. После отправки всех messages закройте channel, чтобы сообщить о завершении.
    • Transformer(in chan Message, out chan Message) — получает messages из входного channel, преобразует content каждого message в верхний регистр и отправляет преобразованный message в выходной channel. Когда входной channel закрыт (что определяется с помощью формы получения двух значений), закройте выходной channel.
    • Consumer(in chan Message) []string — получает все messages из входного channel и собирает их в срез отформатированных строк. Каждая строка должна соответствовать формату: Message [ID]: [Content]. Верните срез, когда channel будет закрыт.
  • main.go: Настройте pipeline и организуйте параллельный поток messages.

    Прочитайте number messages, затем прочитайте ID и content каждого message. Создайте messages и настройте два channels для соединения трёх этапов pipeline. Запустите Producer и Transformer как goroutines, затем выполните Consumer в основной goroutine, чтобы собрать результаты. Выведите каждый результат в отдельной строке.

Будут предоставлены следующие входные данные:

  • Строка 1: number messages (целое число)
  • Следующие строки: для каждого message — две строки: ID message (целое число), затем его content (строка)

Например, для следующих данных:

2
1
hello world
2
go channels

Ваш вывод должен быть таким:

Message 1: HELLO WORLD
Message 2: GO CHANNELS

pipeline работает следующим образом: Producer отправляет messages → Transformer converts их в верхний регистр → Consumer collects и форматирует результаты. Каждая стрелка представляет channel, соединяющий параллельно выполняющиеся этапы. Используйте пакет strings для преобразования в верхний регистр.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Прочитать количество сообщений
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Прочитать сообщения
	messages := make([]Message, n)
	for i := 0; i < n; i++ {
		idLine, _ := reader.ReadString('\n')
		id, _ := strconv.Atoi(strings.TrimSpace(idLine))
		contentLine, _ := reader.ReadString('\n')
		content := strings.TrimSpace(contentLine)
		messages[i] = Message{ID: id, Content: content}
	}

	// TODO: Создать два канала для соединения этапов конвейера

	// TODO: Запустить Producer как горутину

	// TODO: Запустить Transformer как горутину

	// TODO: Запустить Consumer в главной горутине и собрать результаты

	// TODO: Вывести каждый результат на отдельной строке
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go