Без лишних слов, давайте к делу.
Общая структура канала
Краткое описание:
-
buf
Буферизованная структура канала, используемая для хранения буферизованных данных. круговой связанный список -
sendx
иrecvx
Для записиbuf
Эта цепочка кругов отправлена или получена ~ ИНДЕКС -
lock
является мьютексом. -
recvq
иsendq
Очереди структур (sudog), абстрагированные горутинами, которые получают (
Исходный код находится на/runtime/chan.go
Средний (текущая версия: 1.11). Структураhchan
.
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
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
}
Ниже мы подробно познакомимhchan
Как используется каждая часть.
Начните с создания
Сначала мы создаем канал.
ch := make(chan int, 3)
Создание CHANNEL фактически создается в памяти.hchan
структура и возвращает указатель ch. Мы используем этот указатель для передачи канала между функциями во время использования. Вот почему передача функции не должна использовать указатель канала, а просто использует канал напрямую, потому что сам канал является указателем.
Отправить send(ch
先考虑一个问题,如果你想让goroutine以先进先出(FIFO)的方式进入一个结构体中,你会怎么操作?
Замок! правильный! channel就是用了一个锁。 hchan本身包含一个互斥锁mutex
Как устроена очередь в реализации Channel?
В канале есть буфер buf, который используется для буферизации очереди данных (если инстанцируется буферизованный канал). Давайте сначала посмотрим, как реализована «очередь». Или канал, который вы только что создали
ch := make(chan int, 3)
когда используешьsend (ch <- xx)
илиrecv ( <-ch)
, сначала заблокируйтеhchan
эта структура.
затем начнитеsend (ch <- xx)
данные.
один
ch <- 1
два
ch <- 1
три
ch <- 1
На данный момент он заполнен, очередь не может быть заполнена Динамический граф представлен в виде:
затем взятьrecv ( <-ch)
Процесс является обратной операцией и также требует блокировки.
затем начнитеrecv (<-ch)
данные.
один
<-ch
два
<-ch
три
<-ch
На фото:
Обратите внимание на два снимкаbuf
иrecvx
а такжеsendx
Перемена,recvx
иsendx
основан на круговом связанном спискеbuf
изменения в связи с изменениями.
Что касается того, почему канал использует кольцевой связанный список в качестве структуры кеша, я лично думаю, что список кеша является динамическим.send
иrecv
процесс, найдите текущийsend
илиrecvx
расположение, выборsend
суммаrecvx
Расположение более удобное, пока вы продолжаете чередовать операции в порядке связанного списка.
Кэш хранится в порядке связанного списка, а данные считываются в порядке связанного списка при выборке данных, что соответствует принципу FIFO.
Уточнение отправки/получения
Примечание. Каждый шаг кэшированной таблицы цепочек необходим для блокировки!
Детали работы каждого шага могут быть уточнены следующим образом:
- Во-первых, заблокировать
- Во-вторых, скопируйте данные из горутины в «очередь» (или из очереди в горутину).
- В-третьих, снимите блокировку
Работа каждого шага резюмируется в виде динамической диаграммы: (процесс отправки)
Или: (процесс получения)
Так что нетрудно заметить, что классическое предложение в Go:Do not communicate by sharing memory; instead, share memory by communicating.
Конкретная реализация заключается в использовании канала для копирования данных с одного конца на другой!
действительно подходитchannel
Английское значение:
Что происходит, когда буфер канала заполнен? В чем причина этого?
При его использовании мы все знаем, что когда буфер канала заполнен или буфера нет, мы продолжаем отправлять (ch
Мы знаем, что горутины Go — это потоки пользовательского режима (user-space threads
), потоки пользовательского режима должны быть запланированы сами по себе, и в Go есть планировщик времени выполнения, который помогает нам выполнить планирование. Я не буду здесь вдаваться в подробности о GMP-модели планирования Go, если вы не понимаете, то можете прочитать другую мою статью (Принцип планирования Go)
Блокирующая операция горутины на самом деле вызываетsend (ch <- xx)
илиrecv ( <-ch)
Когда инициатива вызвала подробности, пожалуйста, прочтите следующее:
//goroutine1 中,记做G1
ch := make(chan int, 3)
ch <- 1
ch <- 1
ch <- 1
На этот раз G1 работает нормально, когда операция отправки (ch
В то же время G1 также будет абстрагироваться в указатель G1 и элемент отправки.sudog
Структура сохраняется в Гчанеsendq
ждет, когда его разбудят.
Итак, когда просыпается G1? В это время G2 совершил грандиозный дебют.
G2 выполнил операцию RECVp := <-ch
, поэтому произойдут следующие операции:
G2 берет данные из очереди кеша, канал помещает G1 в очередь ожидания, помещает данные, отправленные G1, в кеш, затем вызывает планировщик Go, пробуждает G1 и помещает G1 в готовую к выполнению очередь Goroutine.
Что, если G2 сначала выполняет операцию recv?
Возможно, вы сможете изменить вышеуказанную линию мышления. Во-первых:
В это время G2 будет активно вызывать планировщик Go, пусть G2 подождет, а M будет использоваться другими G.
G2 также будет абстрагироваться в указатель G2 и получать пустые элементы.sudog
Структура сохраняется в hchanrecvq
жду когда проснутся
В этот момент появляется горутина G1, которая начинает передавать данные в канал.ch <- 1
.
此时,非常有意思的事情发生了:
G1 не блокирует канал, а затем помещает данные в кеш, а напрямую копирует данные из G1 в стек G2. Этот метод очень хорош! В процессе пробуждения G2 больше не нужно запрашивать блокировку канала, а затем извлекать данные из кэша. Уменьшение копирования памяти и повышение эффективности.
Последующие вещи очевидны:
Для получения более интересного контента, пожалуйста, обратите внимание на мой публичный аккаунт WeChat.互联网技术窝
Или добавьте WeChat для обсуждения и общения:
использованная литература: