Это 22-я статья из серии "Изучаем язык Go".
В последней статье говорилось о некоторых вариантах использования сопрограмм, например о том, как создавать сопрограммы, анонимные сопрограммы и т. д. В этой статье мы поговорим о вере.Каналы — это каналы для связи между сопрограммами, отправляющими данные с одного конца и получающими данные с другого.
объявление канала
Перед тем, как использовать канал, нужно объявить, есть два способа:
var c chan int // 方式一
c := make(chan int) // 方式二
Используйте ключевое слово chan для создания канала и объявите его с типом, указывающим, что канал разрешает передачу данных только этого типа. Нулевое значение канала равно нулю. Метод 1 объявляет нулевой канал. Канал nil не действует ни на отправку, ни на получение данных. Второй метод использует функцию make для создания пригодного для использования канала c.
func main() {
c := make(chan int)
fmt.Printf("c Type is %T\n",c)
fmt.Printf("c Value is %v\n",c)
}
вывод:
c Type is chan int
c Value is 0xc000060060
Приведенный выше код создает канал c и разрешает передачу данных только int. Как правило, канал передается в качестве параметра функции или метода для реализации связи между двумя сопрограммами.Обратили ли вы внимание, что значение канала c является адресом, вы можете напрямую использовать значение c при передаче параметра, вместо того, чтобы взять адрес.
использование канала
читать и записывать данные
Go предоставляет синтаксис для управления каналами:
c := make(chan int)
// 写数据
c <- data
// 读数据
variable <- c // 方式一
<- c // 方式二
При чтении и записи данных обратите внимание на расположение канала,Канал слева от стрелки записывает данные, а справа читает данные из канала. Вышеупомянутый метод разумен для чтения данных во второй раз, а считанные данные отбрасываются и не используются. Уведомление:Операции канала по умолчанию заблокированы.После записи данных в канал текущая сопрограмма блокируется до тех пор, пока другие сопрограммы не прочитают данные. После того, как сопрограмма заблокирована операцией канала, планировщик Go вызовет другие доступные сопрограммы, чтобы программа не блокировалась навсегда.. Это свойство каналов очень полезно, как мы увидим далее. Давайте рассмотрим пример из предыдущей статьи:
func printHello() {
fmt.Println("hello world goroutine")
}
func main() {
go printHello()
time.Sleep(1*time.Second)
fmt.Println("main goroutine")
}
В этом примере сопрограмма main() использует функцию time.Sleep(), чтобы заснуть на 1 с, ожидая завершения printHello(). Это очень темная технология, и ее никогда не следует использовать в производственной среде. Мы используем канал для изменения следующего:
func printHello(c chan bool) {
fmt.Println("hello world goroutine")
<- c // 读取信道的数据
}
func main() {
c := make(chan bool)
go printHello(c)
c <- true // main 协程阻塞
fmt.Println("main goroutine")
}
вывод:
hello world goroutine
main goroutine
В приведенном выше примере после того, как основная сопрограмма создает сопрограмму printHello, 8-я строка записывает данные в канал c, основная сопрограмма блокируется, планировщик Go может использовать сопрограмму printHello для планирования, чтения данных из канала c и Блокировка контактов основной сопрограммы Продолжайте работать. Примечание. Операция чтения не блокируется, поскольку в канале c есть доступные для чтения данные, иначе операция чтения будет заблокирована.
тупик
Как упоминалось ранее, канал будет блокироваться при чтении/записи данных, а планировщик будет планировать другие доступные сопрограммы. Вопрос в том, что произойдет, если нет других доступных сопрограмм? Правильно, знаменитыйтупик. В простейшем случае только записывать данные в канал.
func main() {
c := make(chan bool)
c <- true // 只写不读
fmt.Println("main goroutine")
}
Ошибка:
fatal error: all goroutines are asleep - deadlock!
Точно так же только чтение, но не запись сообщит об одной и той же ошибке.
закрыть канал и цикл
Канал, который отправляет данные, может закрыть канал, и данные не могут быть переданы. Когда данные получены, статус может быть возвращен, чтобы определить, закрыт ли канал:
val, ok := <- channel
val — полученное значение, а ok определяет, закрыт ли канал. Если оно истинно, канал также может выполнять операции чтения и записи, если ложно, то это означает, что канал закрыт и данные не могут быть переданы. Используйте встроенную функцию close(), чтобы закрыть канал.
func printNums(ch chan int) {
for i := 0; i < 4; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go printNums(ch)
for {
v, ok := <-ch
if ok == false { // 通过 ok 判断信道是否关闭
fmt.Println(v, ok)
break
}
fmt.Println(v, ok)
}
}
вывод:
0 true
1 true
2 true
3 true
0 false
Сопрограмма printNums закрывает канал после записи данных и оценивает основную сопрограмму как ОК. Если она ложна, канал закрывается и цикл for завершается. Значение, считанное из закрытого канала, является нулевым значением соответствующего типа, а выходное значение последней строки выше является нулевым значением 0 типа int.
С помощью цикла for нужно вручную определить, закрыт ли канал. Если напрягает, то используйте for range для чтения канала, канал закрывается, а for range автоматически выходит.
func printNums(ch chan int) {
for i := 0; i < 4; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go printNums(ch)
for v := range ch {
fmt.Println(v)
}
}
вывод:
0
1
2
3
Следует отметить одну вещь: используя канал для диапазона, вы должны закрыть () канал после отправки, иначе возникнет взаимоблокировка.
Буферизованные каналы и пропускная способность канала
Ранее созданный канал не буферизуется, и чтение и запись в канал немедленно блокирует текущую сопрограмму. Для буферизованных каналов запись не блокирует текущий канал до тех пор, пока он не заполнится, и аналогичным образом чтение не блокирует текущий канал, пока канал не станет пустым. Создайте буферизованный канал:
ch := make(chan type, capacity)
емкость — это размер буфера, который должен быть больше 0. Встроенные функции len(), cap() умеют вычислять длину и пропускную способность канала.
func main() {
ch := make(chan int,3)
ch <- 7
ch <- 8
ch <- 9
//ch <- 10
// 注释打开的话,协程阻塞,发生死锁
会发生死锁:信道已满且没有其他可用信道读取数据
fmt.Println("main stopped")
}
Выход: основной остановлен Создал канал с буфером 3, канал не блокируется при записи 3 данных. Если комментарий к коду в строке 7 открыт, канал в это время заполнен, сопрограмма заблокирована, и нет другой доступной сопрограммы для чтения данных, произойдет взаимоблокировка. Давайте посмотрим на другой пример:
func printNums(ch chan int) {
ch <- 7
ch <- 8
ch <- 9
fmt.Printf("channel len:%d,capacity:%d\n",len(ch),cap(ch))
fmt.Println("blocking...")
ch <- 10 // 阻塞
close(ch)
}
func main() {
ch := make(chan int,3)
go printNums(ch)
// 休眠 2s
time.Sleep(2*time.Second)
for v := range ch {
fmt.Println(v)
}
fmt.Println("main stopped")
}
вывод:
channel len:3,capacity:3
blocking...
7
8
9
10
main stopped
Цель спящего режима на 2 секунды — заблокировать канал, когда он заполнен данными, как видно из результата печати. Через 2 с основная сопрограмма считывает данные из канала, и блокировка снимается, когда пропускная способность канала становится больше, и продолжает записывать данные.
Если буферизованный канал закрыт, но есть данные, данные все еще можно прочитать:
func main() {
ch := make(chan int,3)
ch <- 7
ch <- 8
//ch <- 9
close(ch)
for v := range ch {
fmt.Println(v)
}
fmt.Println("main stopped")
}
вывод:
7
8
main stopped
односторонний канал
Все предыдущие разработки представляют собой двунаправленные каналы, которые могут как отправлять, так и получать данные. Мы также можем создавать односторонние каналы, только отправляя или только получая данные. грамматика:
sch := make(chan<- int)
rch := make(<-chan int)
sch должен отправлять только канал, rch должен только получать канал. Какая польза от этого одностороннего канала? Мы не можем просто отправить и не получить или только получить и не отправить. Этот тип канала в основном используется, когда канал передается как параметр, Go обеспечивает автоматическое преобразование из двустороннего в одностороннее. Перепишите предыдущий пример:
func printNums(ch chan<- int) {
for i := 0; i < 4; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go printNums(ch)
for v := range ch {
fmt.Println(v)
}
}
вывод:
0
1
2
3
В основной сопрограмме ch является двусторонним каналом, а printNums() автоматически преобразует ch в односторонний канал при получении параметров, только отправляя, но не получая. Но в основной сопрограмме ch все еще может получать данные.Использование одностороннего канала в основном предназначено для повышения безопасности типов программы, и программа не подвержена ошибкам.
Тип данных канала
Канал — это тип значения, похожий на int, string и т. д., который можно использовать где угодно, как и любое другое значение, например, в качестве члена структуры, параметра функции, возвращаемого значения функции или даже в качестве типа другого канала. Давайте взглянемИспользовать канал как тип данных другого канала.
func printWord(ch chan string) {
fmt.Println("Hello " + <-ch)
}
func productCh(ch chan chan string) {
c := make(chan string) // 创建 string type 信道
ch <- c // 传输信道
}
func main() {
// 创建 chan string 类型的信道
ch := make(chan chan string)
go productCh(ch)
// c 是 string type 的信道
c := <-ch
go printWord(c)
c <- "world"
fmt.Println("main stopped")
}
вывод:
Hello world
main stopped
Надеюсь, эта статья поможет вам, Добрый день!
(Конец полного текста)
Оригинал статьи, если нужно перепечатать, указывайте источник!
Добро пожаловать, чтобы отсканировать код и подписаться на официальный аккаунт »Голанг здесь” или двигатьсяseekload.net, см. больше замечательных статей.
Я подготовил для вас книги, связанные с изучением языка Go, и официальный аккаунт будет отвечать на [e-book] в фоновом режиме!