Задачи про каналы

1) Что выведет данная программа?

package main
import "fmt"

func main() {
	msgChan := make(chan string, 1)
	close(msgChan)
	stopChan := make(chan struct{})
	close(stopChan)

	select {
		case msgChan <- "msg":
			fmt.Println("msg sent")
		case <-stopChan:
			fmt.Println("stop signal received")
	}
}

Данная программа демонстрирует особенности работы с каналами (channels) в Go.

Объяснение

  1. Создание каналов:

    • msgChan - канал для передачи строк, с буфером размером 1.

    • stopChan - канал для передачи сигнала остановки, без буфера.

  2. Закрытие каналов:

    • Оба канала закрываются сразу после создания с помощью close(msgChan) и close(stopChan).

  3. Использование select оператора:

    • select оператор используется для ожидания готовности одного из каналов.

    • В данном случае, select ожидает, пока будет доступно одно из двух действий: отправка сообщения в msgChan или получение сигнала из stopChan.

  4. Почему выводится "stop signal received"?

    • Поскольку оба канала закрыты, ни одно из условий в select не может быть выполнено.

    • Когда ни одно из условий в select не может быть выполнено, Go выбирает случайно одно из них. В данном случае, был выбран case <-stopChan, и поэтому выводится "stop signal received".

    • Если Go случайно выберет вариант case msgChan <- "msg":, то будет panic error, потому что читать из закрытого канала можно, а писать нельзя.

Вывод

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

2) Что выведет данная программа и почему?

package main

func main() {
	ch := make(chan int, 1)
	for i := 0; i < 10; i++ {
		select {
			case x := <-ch:
				println(x)
			case ch <- i:
			}
		}
	}
Программа выведет 
0
2
4
6
8

Данная программа на языке Go использует канал `ch` и оператор `select` для управления потоком данных. Вот что происходит в программе:

1. Создается канал `ch` с буфером размером 1. Это означает, что канал может хранить одно значение без блокировки отправителя, пока это значение не будет прочитано.

2. Цикл `for` итерирует от 0 до 9. В каждой итерации цикла выполняется блок `select`, который делает одно из двух действий:
   - Читает значение из канала `ch` и выводит его, если в канале есть данные.
   - Отправляет текущее значение `i` в канал `ch`, если канал не полон (т.е. если в нем есть место для данных).

Поскольку канал изначально пуст, первая операция в `select` (чтение из канала) будет пропущена, и в канал будет отправлено первое значение `i` (0). В следующей итерации, поскольку в канале уже есть значение, операция чтения из канала будет выполнена, и число 0 будет выведено. На данной итерации в канал ничего не будет отправлено, а на следующей итерации в канал будет отправлено значение `i` (2), и так далее.

В результате программа будет чередовать отправку и чтение, выводя на экран числа от 0 до 8 с пропусками через 1.

3) Напишите на GoLang функцию, которая объединяет значения из n каналов в один результирующий, напишите программу для демонстрации работы этой функции

package main

import (
    "fmt"
    "sync"
)

// mergeChannels объединяет значения из нескольких каналов в один результирующий канал.
func mergeChannels(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    // Функция для чтения из каждого канала и отправки в результирующий канал.
    output := func(c <-chan int) {
        for n := range c {
            out <- n
        }
        wg.Done()
    }

    wg.Add(len(cs))
    for _, c := range cs {
        go output(c)
    }

    // Закрыть результирующий канал после завершения всех горутин.
    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

// Программа для демонстрации работы функции mergeChannels.
func main() {
    // Создаем несколько каналов.
    c1 := make(chan int)
    c2 := make(chan int)
    c3 := make(chan int)

    // Запускаем горутины для отправки данных в каналы.
    go func() {
        for _, x := range []int{1, 3, 5} {
            c1 <- x
        }
        close(c1)
    }()
    go func() {
        for _, x := range []int{2, 4, 6} {
            c2 <- x
        }
        close(c2)
    }()
    go func() {
        for _, x := range []int{7, 8, 9} {
            c3 <- x
        }
        close(c3)
    }()

    // Объединяем каналы и выводим результат.
    result := mergeChannels(c1, c2, c3)
    for v := range result {
        fmt.Println(v)
    }
}

1. **Функция `mergeChannels`** принимает переменное количество каналов, читает из каждого канала и отправляет прочитанные значения в один результирующий канал. Использование `sync.WaitGroup` позволяет дождаться завершения всех горутин, которые читают из исходных каналов, прежде чем закрыть результирующий канал.

2. В функции `main` создаются три канала, в каждый из которых в отдельной горутине отправляются числа. Затем эти каналы передаются в функцию `mergeChannels`, и результаты чтения из результирующего канала выводятся на экран.

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

1. **Конкуренция за ресурсы:** Горутины конкурируют за процессорное время и другие системные ресурсы. Планировщик Go управляет выполнением горутин, и точный момент, когда каждая горутина получает доступ к процессору, может варьироваться. Это зависит от множества факторов, включая загрузку системы и поведение планировщика.

2. **Недетерминированное планирование:** Планировщик горутин в Go работает по принципу "не блокирующего многозадачности", что означает, что горутины могут быть приостановлены и возобновлены в любой момент времени. Порядок, в котором горутины возобновляются и получают доступ к каналу для отправки данных, может каждый раз отличаться.

3. **Асинхронная запись в канал:** Когда несколько горутин пишут в один канал, каждая запись в канал происходит, когда горутина получает доступ к каналу. Если одна горутина задерживается или переключается планировщиком, другая горутина может продолжить запись, что приводит к изменению порядка данных в канале.

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

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

package main

import (
    "fmt"
    "sync"
	"sort"
)

type indexedValue struct {
    index int
    value int
}

// mergeChannels объединяет значения из нескольких каналов в один результирующий канал, сохраняя порядок.
func mergeChannels(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)
    intermediate := make(chan indexedValue)

    // Функция для чтения из каждого канала и отправки в промежуточный канал с индексом.
    output := func(c <-chan int, index int) {
        for n := range c {
            intermediate <- indexedValue{index: index, value: n}
        }
        wg.Done()
    }

    wg.Add(len(cs))
    for i, c := range cs {
        go output(c, i)
    }

    // Закрыть промежуточный канал после завершения всех горутин.
    go func() {
        wg.Wait()
        close(intermediate)
    }()

    // Сортировка значений по индексу перед отправкой в результирующий канал.
    go func() {
        var results []indexedValue
        for iv := range intermediate {
            results = append(results, iv)
        }
        // Сортировка по индексу
        sort.Slice(results, func(i, j int) bool {
            return results[i].index < results[j].index
        })
        // Отправка отсортированных значений
        for _, iv := range results {
            out <- iv.value
        }
        close(out)
    }()

    return out
}

func main() {
    c1 := make(chan int)
    c2 := make(chan int)
    c3 := make(chan int)

    go func() { for _, x := range []int{1, 3, 5} { c1 <- x }; close(c1) }()
    go func() { for _, x := range []int{2, 4, 6} { c2 <- x }; close(c2) }()
    go func() { for _, x := range []int{7, 8, 9} { c3 <- x }; close(c3) }()

    result := mergeChannels(c1, c2, c3)
    for v := range result {
        fmt.Println(v)
    }
}

В этом примере мы используем структуру indexedValue для хранения значений вместе с их индексом (порядком горутины). После сбора всех значений в промежуточный канал, мы сортируем их по индексу и только после этого отправляем в результирующий канал. Это гарантирует, что значения будут выводиться в порядке, соответствующем порядку запуска горутин.

Last updated