Принцип графического оператора Go select

Go

Оператор 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 для обсуждения и общения:

использованная литература: