Это 23-я статья из серии «Изучаем язык го».
Роль Избранного
Использование select несколько похоже на оператор switch, но select не имеет входного значения и используется только для работы с каналом. select используется для выбора из нескольких операций отправки или получения канала. Оператор будет блокироваться до тех пор, пока один из каналов не сможет работать. Если есть несколько каналов, которые могут работать, один из случаев будет выбран случайным образом для выполнения. Взгляните на пример ниже:
func service1(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
time.Sleep(1 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // 会发送阻塞
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
}
}
Вывод: из службы2 Приведенный выше пример блокируется, когда выполняется оператор select, а основная сопрограмма ожидает выполнения операции case.Очевидно, что служба 2 сначала подготавливает данные для чтения (засыпает на 1 с), поэтому вывод выполняется из службы 2. Взгляните на ситуацию, когда обе операции готовы:
func service1(ch chan string) {
//time.Sleep(2 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
//time.Sleep(1 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
time.Sleep(2*time.Second)
select {
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
}
}
Закомментируем задержку в функции, добавим задержку в 2 с перед выбором основной функции, чтобы дождаться готовности данных двух каналов, select случайным образом выберет один из кейсов для выполнения, поэтому вывод тоже случайный.
default case
Как и оператор switch, оператор select также имеет регистр по умолчанию. Да, оператор select не блокирует. Если другие операции канала не готовы, ветвь по умолчанию будет выполнена напрямую.
func service1(ch chan string) {
ch <- "from service1"
}
func service2(ch chan string) {
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // ch1 ch2 都还没有准备好,直接执行 default 分支
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
default:
fmt.Println("no case ok")
}
}
Вывод: ни в коем случае ок Когда выполняется оператор select, поскольку каналы ch1 и ch2 не готовы, непосредственно выполняется оператор по умолчанию.
func service1(ch chan string) {
ch <- "from service1"
}
func service2(ch chan string) {
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
time.Sleep(time.Second) // 延时 1s,等待 ch1 ch2 准备就绪
select {
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
default:
fmt.Println("no case ok")
}
}
Перед оператором select добавляется задержка в 1 с, ожидающая готовности ch1 ch2. Поскольку оба канала готовы, оператор по умолчанию не пойдет. Случайный вывод из службы1 или из службы2.
nil channel
Значение канала по умолчанию — nil, и никакие операции чтения или записи не могут выполняться на нулевом канале. см. пример ниже
func service1(ch chan string) {
ch <- "from service1"
}
func main() {
var ch chan string
go service1(ch)
select {
case str := <-ch:
fmt.Println(str)
}
}
Ошибка:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
goroutine 18 [chan send (nil chan)]:
Следует помнить о двух ошибках: В ветке case [select (no case)], если канал нулевой,ветка будет проигнорирована, то вышеприведенное становится пустым оператором select{}, блокирует основную сопрограмму, планирует сопрограмму service1, работает на нулевом канале и сообщает об ошибке [chan send (nil chan)]. Таких ошибок можно избежать, используя приведенный выше случай по умолчанию.
func service1(ch chan string) {
ch <- "from service1"
}
func main() {
var ch chan string
go service1(ch)
select {
case str := <-ch:
fmt.Println(str)
default:
fmt.Println("I am default")
}
}
Вывод: я по умолчанию
добавить тайм-аут
Иногда мы не хотим немедленно выполнять оператор по умолчанию, а хотим подождать некоторое время, и если в течение этого периода времени нет рабочего канала, выполнить указанный оператор. Тайм-аут может быть установлен после оператора case.
func service1(ch chan string) {
time.Sleep(5 * time.Second)
ch <- "from service1"
}
func service2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from service2"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go service1(ch1)
go service2(ch2)
select { // 会发送阻塞
case s1 := <-ch1:
fmt.Println(s1)
case s2 := <-ch2:
fmt.Println(s2)
case <-time.After(2*time.Second): // 等待 2s
fmt.Println("no case ok")
}
}
Вывод: ни в коем случае ок В третьем операторе case устанавливается тайм-аут 2 с.Если в течение этих 2 с есть другие рабочие каналы, то case будет выполнен.
пустой выбор
package main
func main() {
select {}
}
Мы знаем, что оператор select будет блокироваться до тех пор, пока не появится случай для обработки. Но пустой оператор select не имеет ветки case, поэтому постоянная блокировка вызовет взаимоблокировку. Ошибка:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]
Надеюсь, эта статья поможет вам, Добрый день!
(Конец полного текста)
Оригинал статьи, если нужно перепечатать, указывайте источник!
Добро пожаловать, чтобы отсканировать код и подписаться на официальный аккаунт »Голанг здесь” или двигатьсяseekload.net, см. больше замечательных статей.
Я подготовил для вас книги, связанные с изучением языка Go, и официальный аккаунт будет отвечать на [e-book] в фоновом режиме!