协程信道基础知识简单总结
Goroutines go协程
并发go程序活动
- 一个程序开始时,只有唯一的协程调用main 函数
- 新的协程通过 go 语句创建
- 与普通函数地区别,go协程不等待
-
f() // call f(); wait for it to return
-
go f() // create a new goroutine that calls f(); don't wait
- 结束方式: 从main 返回或从退出程序
Channel 信道
-
并发go程序活动间的连接 定义:
-
一种允许一个go协程发送值到另一个go协程的通讯机制
-
每个channel都是一个特定类型(channel的元素类型)值的信道
-
创建:ch := make(chan int) // ch has type 'chan int'
-
channel是引用传递,拷贝一个channel或者作为参数传递给函数,caller和callee涉及的都是同样的数据结构。nil为零值
-
两个基础方法+一个关闭方法——通讯方式
-
send 通过channel从一个go协程传递值到另一个执行了对应receive操作的go协程
-
receive
-
close(ch),设置一个标志表明不再有值会发送到这个channel,否则产生panic。而接受这个信道的,接收完之后会产生这个信道的空类型数据。
-
-
操作符 <-
-
ch <- x // 发送语句
-
x = <- ch // 接收语句
-
-
单向管道声明
-
out chan<- int 只写管道
-
in <-chan int 只读管道
-
-
无缓冲channels
- 一个无缓冲channel上的发送操作会阻塞发送协程,直到对应的协程在该channel上执行对应的receive操作。
接受操作先被执行,则阻塞接受协程,直到其它协程执行发送操作。 - 一个无缓冲channel上的通讯使得发送和接受协程进行同步。所以,无缓冲channel也叫同步channel
- 一个无缓冲channel上的发送操作会阻塞发送协程,直到对应的协程在该channel上执行对应的receive操作。
-
Pipelines 流水线
- 流水线:一个go协程的输出是另一个go协程的输入。
- channel关闭后,剩下的值被消费完之后,再消费时会生成一个0值,就好像有一个永不停止的0值流。
- x, ok := <-naturals ;ok为True则正确接收,false则是一个关闭且消费完的流
- 没必要每个channel都关闭,无论关闭与否,无法到达的channel会被go垃圾回收
- 在关闭的channel再次使用close会造成panic,因为在尝试关闭一个nil值
-
缓冲channels
提供一个缓冲队列,声明时指定channel容量即可。如ch := make(chan int, 10)
并发的循环
- Embarrassingly parallel 易平行。问题包含的子问题完全相互独立。
- sync.WaitGroup 用于计数。计算为0可结束。
- var wg sync.WaitGroup // 声明
- wg.Add(1) // 进入+1
- defer wg.Done() // 与Add数目相同, 释放
- wg.Wait() // 所有均结束
- 计算信号量
- var tokens = make(chan struct{}, 20) // token是一个计算信号量,限制了最多20个并发请求
- tokens <- struct{}{} // 获取一个token
- list, err := links.Extract(url) // 执行函数
- <-tokens // release the token // 释放一个token
- 爬取没见过的链接的两种方式
- 一、外层变量n控制是否继续。内层没爬过的链接启动协程去爬
- 二。启动20个协程去爬没爬过的链接(unseenLinks)。发现的链接送入信道worklist。主程序循环worklist,没爬过的送入unseenLinks信道
使用select实现多路技术
- select就像switch一样,包含若干case情况和一个可选的default
- 每个case定义了一种通讯(一个发送或接受操作在某种信道channel上)以及一个相关的语句块。
- case <-ch1: //…
- case x := <-ch2: //… use x …
- 通讯过程
- select会等待直到某个case的一个通讯就绪。(多个同时就绪随机选一个)
- 然后执行通讯,和相关语句;其它通讯不会发生。
- select{} 无case的情况下会一直等待。
- select是一个非阻塞通讯。当信道没准备好时,尝试在信道上接收或发送信息时,不会阻塞信道。
- 信道的空值为nil。发送和接受操作在一个nil信道上会永久阻塞。这种情况select语句下的一个case中nil的信道永远不会选到。这个特性使得我们可以使用nil在打开或关闭select中的case情况。
实现取消协程的方式
类似广播。
- 实现方式
- select方式轮询一个不接收参数的信道。
- 关闭信道后会产生nil。var done = make(chan struct{})
- 一个函数cancelled 轮询到done返回true,否则为false。
- 用于子函数用来关闭子协程 if cancelled() {return}
- 主函数轮询到done时,获取抛弃所有信道数据后返回。
练习8.15 及详细注释
练习 8.15: 如果一个客户端没有及时地读取数据可能会导致所有的客户端被阻塞。修改 broadcaster来跳过一条消息,而不是等待这个客户端一直到其准备好写。或者为每一个客户 端的消息发出channel建立缓冲区,这样大部分的消息便不会被丢掉;broadcaster应该用一个非阻塞的send向这个channel中发消息。
注:代码来源 https://github.com/kdama/gopl
package main
import (
"bufio"
"fmt"