Оператор select Go — это специальный оператор, который может использоваться только для channl для отправки и получения сообщений.Этот оператор блокируется во время выполнения; когда в select нет оператора case, он блокирует текущую подпрограмму. Поэтому некоторые люди также скажут, что select используется для блокировки горутины прослушивания. Другие говорят: select — это механизм мультиплексирования ввода-вывода, предоставляемый Golang на уровне языка, который специально используется для определения того, готовы ли несколько каналов: доступны для чтения или записи.
Вышеупомянутые утверждения верны.
Мультиплексирование ввода/вывода
Давайте рассмотрим, что естьI/O多路复用
.
Обычный многопоточный (или процессный) ввод-вывод
Каждый раз, когда приходит процесс, соединение устанавливается, а затем блокируется до тех пор, пока не будут получены данные и не будет возвращен ответ. Недостаток этого распространенного подхода на самом деле очевиден: системе необходимо создавать и поддерживать дополнительные потоки или процессы. Поскольку большую часть времени большинство заблокированных потоков или процессов находятся в состоянии ожидания, только некоторые из них получат и обработают ответ, в то время как остальные ожидают. Для этого системе также необходимо выполнить много дополнительной работы по управлению потоками или процессами.
Чтобы решить эти избыточные потоки или процессы на рисунке, существует «мультиплексирование ввода-вывода».
Мультиплексирование ввода/вывода
Каждый поток или процесс сначала регистрируется в «устройстве» на рисунке, а затем блокируется, а затем «транспортируется» только один поток.Когда зарегистрированный поток или процесс готов для данных, «устройство» получит соответствующую информацию по зарегистрированным данным. От начала до конца ядро будет использовать только желтую нить на рисунке, и нет необходимости управлять дополнительными потоками или процессами, что повышает эффективность.
выбрать структуру композиции
Реализация select претерпела несколько изменений, текущая версия: 1.11.
Базовая реализация оператора select фактически состоит из двух частей:case语句
и执行函数
.
Адрес исходного кода: /go/src/runtime/select.go
Для каждого оператора case отдельно абстрагируются следующие структуры:
type scase struct {
c *hchan // chan
elem unsafe.Pointer // 读或者写的缓冲区地址
kind uint16 //case语句的类型,是default、传值写数据(channel <-) 还是 取值读数据(<- channel)
pc uintptr // race pc (for race detector / msan)
releasetime int64
}
Структуру можно представить следующей схемой:
Наиболее важные из них:hchan
, который является указателем на канал.
В select все операторы case образуютscase
Массив структур.
Затем выполнение оператора select фактически вызываетfunc selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
функция.
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
Параметры функции:
- CAS0 является структурой абстрагированной заявлением о случаях, упомянутых выше
scase
Адрес первого элемента массива - order0 — это буфер вдвое большей длины, чем массив cas0, который сохраняет случайную последовательность pollorder scase и последовательность адресов каналов в scase lockorder.
- nncases сказал
scase
длина массива
selectgo
Возвращает индекс выбранного масштаба (который соответствует порядковому номеру соответствующего вызова select {recv, send, default}). Кроме того, если выбранный масштаб является операцией приема (recv), он возвращает, было ли получено значение.
кто отвечает за вызовfunc selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
Функция?
существует/reflect/value.go
один изfunc rselect([]runtimeSelect) (chosen int, recvOK bool)
функция, реализация этой функции находится в/runtime/select.go
в файлеfunc reflect_rselect(cases []runtimeSelect) (int, bool)
В функции:
func reflect_rselect(cases []runtimeSelect) (int, bool) {
//如果cases语句为空,则阻塞当前groutine
if len(cases) == 0 {
block()
}
//实例化case的结构体
sel := make([]scase, len(cases))
order := make([]uint16, 2*len(cases))
for i := range cases {
rc := &cases[i]
switch rc.dir {
case selectDefault:
sel[i] = scase{kind: caseDefault}
case selectSend:
sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}
case selectRecv:
sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}
}
if raceenabled || msanenabled {
selectsetpc(&sel[i])
}
}
return selectgo(&sel[0], &order[0], len(cases))
}
Кто звонилfunc rselect([]runtimeSelect) (chosen int, recvOK bool)
Шерстяная ткань?
существует/refect/value.go
, существует одинfunc Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
функция, которая вызываетrselect
функцию и вернуть возвращаемое значение последнего оператора select в Go.
Стек вызовов трех вышеуказанных функций выглядит следующим образом:
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
func rselect([]runtimeSelect) (chosen int, recvOK bool)
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
Возвращаемые значения и параметры этих трех функций похожи, это можно рассматривать просто и грубо: параметры функции передаются в case-операторе, а возвращаемое значение возвращает выбранный case-оператор.
Кто звонилfunc Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
Шерстяная ткань?
Его можно просто представить как систему.
Вот простая картинка:
Первые две функцииSelect
иrselect
Все они выполняют простые параметры инициализации и вызывают следующую функцию. Настоящая основная функция select находится в последней функцииfunc selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
реализовано в.
что делает функция selectgo
Перетасовать порядок входящих структур case
заблокировать в нем все каналы
Через весь канал, посмотрите, может ли он читать или писать
Если канал в нем доступен для чтения или записи, разблокировать все каналы и вернуть соответствующие данные канала
Если нет канала, доступного для чтения или записи, но есть оператор по умолчанию, то же, что и выше: вернуть scase, соответствующий оператору по умолчанию, и разблокировать все каналы.
Если нет ни канала, доступного для чтения или записи, ни инструкции по умолчанию, текущая запущенная подпрограмма будет заблокирована и добавлена в очередь ожидания всех текущих каналов.
Затем разблокируйте все каналы и дождитесь пробуждения.
В это время, если канал доступен для чтения или записи, снова активируйте и заблокируйте все каналы.
Пройдите по всем каналам, чтобы найти соответствующий канал и G, разбудите G и удалите неудачный G из очереди ожидания всех каналов.
Если соответствующее значение Casse не пусто, верните требуемое значение и разблокируйте все каналы
Если соответствующий scase пуст, зациклить этот процесс.
Отношения между выбором и каналом
Размышляя о том, что делают select и channel, я думаю, что это то же самое, что и мультиплексирование.
Для получения более интересного контента, пожалуйста, обратите внимание на мой публичный аккаунт WeChat.互联网技术窝
Или добавьте WeChat для обсуждения и общения:
использованная литература: