Привет всем, меня зовут Мин.
Во время самостоятельного изучения Golang я написал подробные учебные заметки и разместил их в своем личном общедоступном аккаунте WeChat «Время программирования Go». вы только изучаете язык Go, уделяйте ему внимание, учитесь и развивайтесь вместе.
Мой онлайн-блог:golang.iswbm.comМой Github: github.com/iswbm/GolangCodingTime
Большая часть причин, по которым язык Go стал популярным, связана с его собственным механизмом параллелизма.
Если горутина — это параллельное тело программы на языке Go, то канал (канал) — это механизм связи между ними. Канал — это канал, который позволяет одной горутине передавать информацию другой горутине. Я называю это каналом, а некоторые люди переводят его в канал. И то, и другое является концепцией.
Канал — это канал, который соединяет несколько горутин-программ, это структура данных, похожая на очередь, которая следует правилу «первым пришел — первым обслужен».
1. Определение и использование каналов
Каждый канал может передавать данные только одного типа данных, поэтому при его объявлении необходимо указать тип данных (string int и т. д.)
var 信道实例 chan 信道类型
// 定义容量为10的信道
var 信道实例 [10]chan 信道类型
Объявленный канал, нулевое значение которого равно nil, не может использоваться напрямую и должен быть инициализирован с помощью функции make.
信道实例 = make(chan 信道类型)
В качестве альтернативы две приведенные выше строки можно объединить в одно предложение, и я использую этот метод для объявления канала ниже.
信道实例 := make(chan 信道类型)
Если я хочу создать канал, который может передавать тип int, я могу написать это так.
// 定义信道
pipline := make(chan int)
В канале есть только два вида операций с данными: отправка данных и чтение данных.
// 往信道中发送数据
pipline<- 200
// 从信道中取出数据,并赋值给mydata
mydata := <-pipline
Когда канал израсходован, его можно закрыть, чтобы люди не ждали все время. Но после того, как вы закроете канал, приемник все еще может получать данные из канала, но это всегда будет 0.
close(pipline)
Закрытие канала, который уже был закрыт, приведет к ошибке. Итак, мы должны научиться, как определить, закрыт ли канал?
При чтении данных из канала может быть несколько возвращаемых значений, второе из которых может указывать на то, закрыт ли канал, если был закрыт, то ok — false, если не закрылся, то ok — true.
x, ok := <-pipline
2. Пропускная способность и длина канала
Функция make обычно используется для создания канала, и функция make получает два параметра.
- Первый параметр: обязательный, укажите тип канала
- Второй параметр: необязательный, если не заполнен, по умолчанию 0, указанный каналемкость(сколько данных можно кэшировать)
Для пропускной способности канала это очень важно, вот еще несколько моментов:
- Когда емкость равна 0, это означает, что никакие данные не могут быть сохранены в канале.При отправке данных кто-то должен получить их немедленно, иначе будет сообщено об ошибке. Канал в это время называетсянебуферизованный канал.
- Когда емкость равна 1, это означает, что канал может буферизовать только одни данные.Если в канале уже есть одни данные, отправка данных на него в это время вызовет блокировку программы. Используя это, вы можете использовать канал в качестве замка.
- Когда емкость больше 1, в канале может храниться несколько данных, которые можно использовать для конвейеров связи между несколькими сопрограммами для совместного использования ресурсов.
Итак, мы знаем, что канал — это контейнер.
Если сравнивать с картонной коробкой
- Он может вместить 10 книг, что означает, что его вместимость составляет 10
- В настоящее время загружена только 1 книга, что означает, что ее текущая длина равна 1
Пропускную способность канала можно получить с помощью функции cap, а длину канала можно получить с помощью длины len.
package main
import "fmt"
func main() {
pipline := make(chan int, 10)
fmt.Printf("信道可缓冲 %d 个数据\n", cap(pipline))
pipline<- 1
fmt.Printf("信道中当前有 %d 个数据", len(pipline))
}
Вывод выглядит следующим образом
信道可缓冲 10 个数据
信道中当前有 1 个数据
3. Буферизованные и небуферизованные каналы
В зависимости от того, можно ли буферизовать данные, их можно разделить на:буферный канала такженебуферизованный канал
буферный канал
Позволяет хранить в канале один или несколько данных, что означает, что после установки буфера отправитель и получатель могут находиться в асинхронном состоянии.
pipline := make(chan int, 10)
небуферизованный канал
Никакие данные не могут храниться в канале, а это значит, что получатель должен быть готов раньше отправителя, чтобы гарантировать, что кто-то получит данные сразу после того, как вы отправите данные, иначе отправитель будет заблокирован.Причина очень проста, канал не может сохранить его данные. То есть отправитель и получатель работают синхронно.
pipline := make(chan int)
// 或者
pipline := make(chan int, 0)
4. Двунаправленный канал и однонаправленный канал
Обычно каналы, которые мы определяем, являются двунаправленными каналами, которые могут отправлять и получать данные.
Но иногда мы хотим контролировать поток данных канала, например, этот канал может только получать данные или этот канал может только отправлять данные.
Следовательно, естьдвунаправленный канала такжеодносторонний каналдве категории.
двунаправленный канал
По умолчанию определяемые вами каналы являются двунаправленными, например следующий код
import (
"fmt"
"time"
)
func main() {
pipline := make(chan int)
go func() {
fmt.Println("准备发送数据: 100")
pipline <- 100
}()
go func() {
num := <-pipline
fmt.Printf("接收到的数据是: %d", num)
}()
// 主函数sleep,使得上面两个goroutine有机会执行
time.Sleep(1)
}
односторонний канал
Односторонние каналы, которые можно разделить наканал только для чтенияа такжеканал только для записи.
Определить канал только для чтения
var pipline = make(chan int)
type Receiver = <-chan int // 关键代码:定义别名类型
var receiver Receiver = pipline
Определить канал только для записи
var pipline = make(chan int)
type Sender = chan<- int // 关键代码:定义别名类型
var sender Sender = pipline
Посмотрите внимательно, разница<-
символ в ключевом словеchan
влево или вправо.
-
<-chan
Указывает, что этот канал может отправлять данные только с него, который доступен только для чтения для программы. -
chan<-
Указывает, что этот канал может только получать данные извне, а для программы только запись
Некоторые учащиеся могут спросить: зачем сначала объявлять двусторонний канал, а затем определять односторонний? например, писать
type Sender = chan<- int
sender := make(Sender)
Код в порядке, но надо понимать, в чем смысл канала? (Вот мое личное мнение
Сам канал существует для передачи данных.Если есть только получатели или только отправители, то канал становится только вход и выход или только вход и выход, он бесполезен. Следовательно, каналы только для чтения и каналы только для записи незаменимы.
Конечно, если вы записываете данные в канал только для чтения или читаете данные из канала только для записи, возникнут ошибки.
Для справки полный пример кода выглядит следующим образом:
import (
"fmt"
"time"
)
//定义只写信道类型
type Sender = chan<- int
//定义只读信道类型
type Receiver = <-chan int
func main() {
var pipline = make(chan int)
go func() {
var sender Sender = pipline
fmt.Println("准备发送数据: 100")
sender <- 100
}()
go func() {
var receiver Receiver = pipline
num := <-receiver
fmt.Printf("接收到的数据是: %d", num)
}()
// 主函数sleep,使得上面两个goroutine有机会执行
time.Sleep(1)
}
5. Пересеките канал
Чтобы обойти канал, вы можете использовать for с ключевым словом range.Находясь в диапазоне, убедитесь, что канал закрыт, иначе цикл заблокируется.
import "fmt"
func fibonacci(mychan chan int) {
n := cap(mychan)
x, y := 1, 1
for i := 0; i < n; i++ {
mychan <- x
x, y = y, x+y
}
// 记得 close 信道
// 不然主函数中遍历完并不会结束,而是会阻塞。
close(mychan)
}
func main() {
pipline := make(chan int, 10)
go fibonacci(pipline)
for k := range pipline {
fmt.Println(k)
}
}
6. Используйте каналы в качестве замков
Когда количество данных в канале достигло установленной емкости, отправка данных на него в это время заблокирует всю программу.
Используя эту функцию, его можно использовать в качестве блокировки программы.
Пример выглядит следующим образом, подробности см. в примечаниях.
package main
import {
"fmt"
"time"
}
// 由于 x=x+1 不是原子操作
// 所以应避免多个协程对x进行操作
// 使用容量为1的信道可以达到锁的效果
func increment(ch chan bool, x *int) {
ch <- true
*x = *x + 1
<- ch
}
func main() {
// 注意要设置容量为 1 的缓冲信道
pipline := make(chan bool, 1)
var x int
for i:=0;i<1000;i++{
go increment(pipline, &x)
}
// 确保所有的协程都已完成
// 以后会介绍一种更合适的方法(Mutex),这里暂时使用sleep
time.Sleep(3)
fmt.Println("x 的值:", x)
}
Вывод выглядит следующим образом
x 的值:1000
Если не заблокировано, вывод будет меньше 1000.
Руководство по серии
01. Построение среды разработки (Goland & VS Code)
02. Изучите пять методов создания переменных
03. Подробные типы данных:**Целое число и число с плавающей запятой**
04. Подробные типы данных: байт, руна и строка
05. Подробно объясните типы данных: массивы и срезы
06. Подробно объясните типы данных: словари и логические значения
07. Подробно объясните типы данных: указатели
08. Объектно-ориентированное программирование: структуры и наследование
09. Статья для понимания функций в Go
10. Управление потоком языка Go: условный оператор if-else
11. Управление потоком языка Go: оператор выбора switch-case
12. Управление потоком языка Go: оператор цикла for
13. Перейти к управлению языковым потоком: перейти к безусловному переходу
14. Перейти к управлению языковым потоком: отложить отложенный вызов
15. Объектно-ориентированное программирование: интерфейсы и полиморфизм
16. Ключевые слова: В чем разница между make и new?
17. Статья о понимании блоков и области видимости в Go
18. Изучите Go Coroutines: горутины
19. Изучите сопрограммы Go: подробно объясните каналы/каналы
20. Подробное объяснение нескольких классических случаев ошибки взаимоблокировки канала.
21. Изучите сопрограммы Go: группа ожидания
22. Изучите процедуры Go: мьютексы и блокировки чтения-записи
23. Обработка исключений в Go: паника и восстановление
24. Сверхдетальная интерпретация прошлой жизни Go Modules и вводное использование
25. 8 важных знаний об импорте пакетов на языке Go
26. Как открыть исходный код модулей, которые я написал для использования другими?
27. Расскажите об утверждениях типов в Go?
28. Эти пять пунктов помогут вам понять, как использовать select в языке Go.