还记得 Go 并发的风采吗?一起来回顾一下 go 并发的基础语法
并发编程 程序有若干个自主的活动单元组成 从分利用现代多喝计算机的性能
Go 并发风格
支持_通信顺序进程_(Communicating Sequential Process, CSP),CSP 是一个并发的模式,在不同的执行体(e.g. goroutine)之间传递值,但是变量本身局限于单一的执行体
goroutine
协程
- Go 程序并发的执行体
goroutine
: Go 程序中的每一个并发活动- 当程序启动时,只有一个
goroutine
来调用main
函数,成为主协程 - 通过
go
关键字创建goroutine
func main() {
go spinner(100 * time.Millisecond) // 开启一个新的 goroutine
const n = 45
fibN := fib(n)
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for { // 死循环
// 循环输出 -\|/
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
// 计算斐波那契数列
func fib(x int) int {
if x < 2 {
return
}
return fib(x - 1) + fib(x - 2)
}
channel
通道/管道
goroutine
之间的连接channel
让一个goroutine
发送特定值道另一个goroutine
的通信机制channel
是一个具有具体类型的通道,成为通道的元素类型**channel**
** 支持的操作**- make 创建
// 无缓冲通道
ch := make(chan int)
ch := make(chan int, 0)
// 容量为 3 的缓冲管道
ch := make(chan int, 3)
- send 发送
- 在
channel
关闭后,任何后续的发送操作都会导致崩溃在channel
关闭后,任何后续的发送操作都会导致崩溃
- 在
ch <- x
- receive 接收
- 在
channel
关闭后,任何接收操作都可以正常执行,接收到的值都是零值
- 在
// 赋值语句中的接收表达式
x = <- ch
// 丢弃结果
<- ch
- close 关闭
- 通道的关闭并不是必须的,通道可以通过垃圾回收机制根据是否可以访问来决定是否会回收,而不是是否被关闭
- 关闭一个已经关闭的通道会导致宕机
close(ch)
如何判断一个通道是否关闭 接收操作返回两个结果:
- 接收到的通道元素
- 一个布尔值
- true - 接收成功
- false 接受操作在一个关闭并且读取完的通道上
go func() {
for {
x, ok := <- naturals
if !ok {
break
}
squares <- x * x
}
close(squares)
}()
无缓冲通道
无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine
在对应的通道上执行接收操作
如果接受操作先执行,接收方的 goroutine
也会阻塞,直到另一个 goroutine
在同一个通道发送值
意义 使发送和接收的
**goroutine**
同步化 因此无缓冲通道也被称为同步通道
func main() {
// 建立网络连接
conn, err := net.Dial("tcp", "localhost:8888")
if err != nil {
log.Fatal(err)
}
// 构建一个无缓冲通道
done := make(chan struct{})
// 新建一个 goroutine
go func() {
// 输出拷贝到请求
io.Copy(os.Stdout, conn)
// 执行结束
log.Println("done")
done <- struct{}{}
}()
// 拷贝请求到输入
mustCopy(conn, os,Stdin)
// 关闭请求
conn.Close()
// 等待 goroutine 完成
<- done
}
func mustCopy(dst io.Wtirer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
单向通道
当一个通道用作函数形参是,通常会被限制不能发送或者接收
- 双向通道可以转换为单向通道,但单向通道不能转换为双向通道
- 关闭一个仅能接受的通道在编译时会报错
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squareer(squares, naturals)
printer(squares)
}
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
}
func printer(in <- chan int) {
for := range in {
fmt.Println(v)
}
}
缓冲通道
ch = make(chan string, 3) // 创建一个缓冲区为 3 的缓冲通道
- 缓冲通道上的发送操作会在队列尾部插入一个元素,接受操作从队列的同步移除一个元素
- 如果通道缓冲区为0(通道已满),发送操作会阻塞,直至接受操作执行
- 如果通道为0(通道为空),则接收操作会阻塞,直至发送操作执行
- 获取缓冲区容量
cap()
- 获取通道内元素个数
len()
goroutine 泄漏
func mirroredQuery() string {
responses := make(chan string, 3)
go func() { responses <- request("asia.gopl.io") }()
go func() { responses <- request("asia.gopl.io") }()
go func() { responses <- request("asia.gopl.io") }()
return <- responses // 返回相应最快的结果
}
func request (hostname string) (response string) { /* ... */ }
在上述事例中,如果使用一个无缓冲通道,由于发送相应结果到通道时,没有goroutine 来接收,因此比较慢的两个goroutine 将被卡住,这种情况称为goroutine泄漏
缓冲通道与无缓冲通道的选择
- 无缓冲通道提供强同步保证
- 缓冲通道可以解藕操作,但是在内存无法提供缓冲容量的情况下,可能导致程序死锁
- 缓冲可能会影响程序性能
转载自:https://juejin.cn/post/7377683138238038079