Конкуренция в Go

Теория: Select

select в Go — это инструмент управления конкурентными событиями. Он позволяет горутине ждать сразу несколько операций на каналах и реагировать на ту, которая сработала первой. Это делает код отзывчивым и управляемым: программа не блокируется на одном канале и может реагировать на таймауты, сигналы отмены или внешние события.

Синтаксис select

select по структуре похож на switch, но работает с каналами. Каждая ветка — это операция чтения или записи. Как только одна из них становится готовой, выполняется соответствующий блок.

select {
case msg := <-ch1:
	fmt.Println("пришло из ch1:", msg)
case msg := <-ch2:
	fmt.Println("пришло из ch2:", msg)
}

Если обе ветки готовы одновременно, планировщик выбирает случайную. Это гарантирует равномерную нагрузку и отсутствие приоритета. Если ни один канал не готов, select блокирует горутину до появления данных.

Ожидание на нескольких каналах

Основное применение — слушать сразу несколько источников. Например, одна горутина пишет логи, а другая может прислать команду остановки:

func main() {
	data := make(chan string)
	quit := make(chan struct{})

	go func() {
		for i := 1; i <= 3; i++ {
			data <- fmt.Sprintf("сообщение %d", i)
			time.Sleep(100 * time.Millisecond)
		}
		close(data)
	}()

	go func() {
		time.Sleep(250 * time.Millisecond)
		close(quit)
	}()

	for {
		select {
		case msg, ok := <-data:
			if !ok {
				fmt.Println("поток данных завершен")
				return
			}
			fmt.Println("получено:", msg)
		case <-quit:
			fmt.Println("получен сигнал остановки")
			return
		}
	}
}

Здесь select ждет данные из data, но одновременно следит за сигналом quit. Если данные кончились или поступил сигнал, цикл завершается.

Ветка default в select

Иногда нужно проверить состояние каналов, не блокируясь. Для этого есть ветка default. Она выполняется, если ни один канал не готов. Это удобно для опроса или периодических проверок:

select {
case msg := <-ch:
	fmt.Println("получено:", msg)
default:
	fmt.Println("нет данных, продолжаем работу")
}

Такой select не ждет — если канал пуст, выполняется default. Этот прием часто применяют в неблокирующих очередях или циклах событий.

Таймауты с time.After

В реальных программах ожидание не может длиться бесконечно. Чтобы ограничить время, Go предоставляет time.After(d). Эта функция возвращает канал, который сработает через заданное время. В select он становится простым средством задания таймаута.

select {
case msg := <-ch:
	fmt.Println("ответ:", msg)
case <-time.After(2 * time.Second):
	fmt.Println("время ожидания истекло")
}

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

Прерывание ожидания через context

Контекст (context.Context) — это стандартный способ управлять временем жизни операций. Его канал Done() срабатывает, когда контекст отменяется или истекает срок действия. Его удобно добавлять в select, чтобы в любой момент можно было остановить ожидание.

func main() {
	ch := make(chan string)
	ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
	defer cancel()

	go func() {
		time.Sleep(500 * time.Millisecond)
		ch <- "ответ"
	}()

	select {
	case msg := <-ch:
		fmt.Println("получено:", msg)
	case <-ctx.Done():
		fmt.Println("отмена:", ctx.Err())
	}
}

Контекст завершится раньше, чем придет сообщение, и ветка ctx.Done() выполнится первой. Этот механизм используется повсеместно — от HTTP-запросов до баз данных, где важно уметь корректно прервать операцию.

select превращает конкурентные программы в управляемые системы событий. Он объединяет каналы, таймеры и контексты в одну простую конструкцию. Вместо бесконечных циклов и флагов программа просто ждет: «что готово — то и выполняем». Благодаря этому Go остается минималистичным языком, но при этом способен решать задачи любой сложности в области асинхронного и параллельного программирования.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845