package
0.0.0-20240731124852-a4f780a8695e
Repository: https://github.com/nixolay/training.git
Documentation: pkg.go.dev

# README

Chanel/Каналы

https://go.dev/src/runtime/chan.go

Каналы - это структура используемая как основной способ передачи сообщений между каналами.

Базовая работа с каналами

Каналы бывают буферизированные и не буферизированные.

ch := make(chan int, 1) // буферизированный, с размером буфера 1
ch := make(chan int, 0) // не буферизированный, с размером буфера 0
ch := make(chan int)    // не буферизированный, с размером буфера 0

Что значит это значит. Буферизированный канал не будет блокировать запись пока его буфер это позволяет. Не буферизированный канал блокируется при первой же записи.

Пример записи и чтения в каналы:

ch := make(chan int, 1)
ch <- 1 // Записываем в канал, что бы тут не упасть на дедлок, необходимо что бы канал был буферизированным.
println(<-ch) // Читаем из канала и отправляем на вывод.

Так же для чтения из канала мы можем использовать range:

ch := make(chan int, 1)
go func(){
  defer close(ch)
  for i := range [10]int{}{
    ch <- i
  }
}()

for e := range ch {
  println(e)
}

range читает до тех пор пока канал не будет закрыт.

Направление каналов

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

func writer(c chan<- string)
func printer(c <-chan string)

Каналы изнутри

type hchan struct {
	qcount   uint           // всего данных в очереди
	dataqsiz uint           // размер циклической очереди
	buf      unsafe.Pointer // указывает на массив элементов
	elemsize uint16
	closed   uint32
	elemtype *_type // тип элемента
	sendx    uint   // индекс элементы массива, который будет отправлен.
	recvx    uint   // индекс массива, куда будет положен новый элемент
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

type waitq struct {
	first *sudog
	last  *sudog
}

рассмотрим какие поля что обозначают в этой структуре:

dataqsize это размер буфера который мы указали при создании канала. elemsize размер одного элемента в канале buf циклическая очередь(циклический буфер) где сохраняются данные. Используется только в буферизированных каналах. closed индикатор закрытого канала. При создании канала это поле 0. После вызова close в это поле устанавливается 1. sendx и recvx это поля для сохранения состояния буфера. Они указывают на позиции в массиве откуда должна происходить отправка или куда должны попадать новые данные. recvq и sendq очереди заблокированных горутин, которые ожидают отправки в канал или чтение из него. lock все отправки и получения должны быть защищены блокировкой. sudog это представление горутины которая стоит в очереди.

Не буферизированный канал

Давайте создадим канал и отправим в него число

ch := make(chan int, 3)
go func(){ch <- 2}()

Посмотрим что будет внутри

chan int {
    qcount: 0,
    dataqsiz: 0,
    buf: *[0]int [],
    elemsize: 8,
    closed: 0,
    elemtype: *runtime._type {...},
    sendx: 0,
    recvx: 0,
    recvq: waitq<int> { first: nil, lsat: nil },
    sendq: waitq<int> {
        first: *(*sudog<int>)(0xc000074000),
        last: *(*sudog<int>)(0xc000074000),
    },
    lock: runtime.mutex {key:0},
}

Как видим буфер у нас длинной 0, туда мы не можем записать. Это значение сохраняется в структуре sudog. Когда горутина пытается отправить сообщение в канал, но еще нет ни одного получателя. Горутина попадает в список sendq и блокируется.

Буферизированный канал

Давайте создадим канал

ch := make(chan int, 3)

и посмотри что у него будет внутри

chan int {
    qcount: 0,
    dataqsiz: 3,
    buf: *[3]int [0,0,0],
    elemsize: 8,
    closed: 0,
    elemtype: *runtime._type {...},
    sendx: 0,
    recvx: 0,
    recvq: waitq<int> { first: nil, lsat: nil },
    sendq: waitq<int> { first: nil, last: nil },
    lock: runtime.mutex {key:0},
}

Теперь мы видим что у нас появился буфер из трех элементов и пока этот буфер не будет заполнен, блокировки на запись не произойдет.

Операции с нулевым каналом

Нулевой канал реагирует на действия с следующим образом:

  var c chan int
  v := <-c // блокируется на всегда
  c <- v   // блокируется на всегда
  close(c) // panic

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

for chanA != nil || chanB != nil {
  select {
  case v, ok := <-chanA:
    if !ok {
      chanA = nil
    }
  case v, ok := <-chanB:
    if !ok {
      chanB = nil
    }
  }
}