Go并发编程实战课(Note.7:Channel)
2021/11/15 1:10:45
本文主要是介绍Go并发编程实战课(Note.7:Channel),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 13.chan:另辟蹊径,解决并发问题
- channel的发展
- Channel的应用场景
- Channel的基本用法
- 1.发送数据
- 2.接收数据
- 3.其他操作
- chan的实现原理
- chan的数据结构
- 初始化
- send
- recv
- close
- 使用chan容易犯的错
- 14.透过代码看典型的应用模式
- 使用反射操作channel
- 典型的应用场景
- 1.消息交流
- 2.数据传递
- 3.信号通知
- 4.锁
- 5.任务编排
- 1.Or-Done模式
- 2.扇入模式
- 3.扇出模式
- 4.Stream
- 5.map-reduce
- 15.内存模型:GO如何保证并发读写的顺序
- 重排和可见性的问题
- happens-before
- Go语言中保证的happens-before关系
- init函数
- goroutine
- Channel
- Mutex/RWMutex
- WaitGroup
- Once
- atomic
13.chan:另辟蹊径,解决并发问题
channel的发展
CSP:Communicating Sequential Process,通信顺序进程。允许使用进程组件来描述进程,他们独立运行并且只通过消息传递的方式通信。
Channel的应用场景
Don’t communicate by sharing memory,share memory by communicating.
不用通过共享内存的方式通信,而是通过通信共享内存。
channel的应用场景分为五种类型,可以有目的地去学习channel的基本原理。
1:数据交流:当作并发的buffer或者queue。解决生产者-消费者问题。
2:数据传递:一个goroutine将数据交给另一个goroutine,相当于把数据的拥有权托付出去。
3:信号通知:一个goroutine可以将信号(closing、closed、data ready等)传递给另一个或者另一组goroutine。
4:任务编排:可以让一组goroutine按照一定的顺序并发或者串行的执行,这就是编排的功能。
5:锁:利用channel也可以实现互斥锁的逻辑。
Channel的基本用法
chan string //可以接收发送string chan <- struct{} //可以发送struct <-chan int //只能从chan接收int
1.发送数据
ch <- 200
2.接收数据
x := <-ch
3.其他操作
close:关闭,cap:计算容量,len:计算长度
发送和接收,都可以作为select语句的case clause
chan还可以用在for range语句中
chan的实现原理
chan的数据结构
runtime.hchan qcount uint //chan中已经接收但还没有被取走的元素的个数。len()可以返回这个字段的值 dataqsiz //队列的大小。chan中使用一个循环队列来存放元素。循环队列很适合这种生产者-消费者的场景 buf //循环队列的buffer eletype //chan中元素的类型,chan一旦声明,它的元素类型是固定的 elesize //chan中元素的大小,普通类型或者指针类型,所以元素大小是固定的 sendx //处理发送数据的指针在buf中的位置。一旦接收到新的数据,指针就会加上elesize,移向下一个位置。buf的总大小是elesize的整数倍,而且buf是一个循环列表 recvx //处理接收请求时的指针在buf中的位置。一旦取出数据,此指针会移动到下一个位置 recvq //chan是多生产者多消费者的模式,如果消费者因为没有数据可读而被阻塞了,就会被加入到recvq队列中 sendq //如果生产者因为buf满了而阻塞,会被加入到sendq队列中
初始化
使用makechan创建hchan对象。
send
1.如果chan为nil的话,就把调用者goroutine阻塞休眠,调用者就永远被阻塞住了。
2.如果chan没有被close,并且chan满了,直接返回
3.如果chan已经close,再往里面发送数据的话触发panic
4.如果等待队列中有等待的recevier,那么这段代码就把它从队列中弹出,然后直接把数据交给它,而不需要放入到buf中,速度可以更快一些
5.当前没有receiver,需要把数据放入buf中,放入之后就返回成功了
6.如果buf满了,发送者的goroutine就会发送者的等待队列(sendq)中
recv
1.chan为nil的情况,和send一样,从nil chan中接收数据时,调用者会一直阻塞
2.第二部分暂时不分析
3.如果chan已经被close了,并且队列中没有缓存的元素,那么返回true、false
4.处理buf满了的情况,如果是unbuffer的chan,直接将sender的数据复制给recevier;否则就从队列头部读取一个值,并把这个sender的值加入到队列尾部。
5.处理没有等待的sender的情况。和send共用一把锁以防并发。如果buf有元素就取一个元素给recevier
6.处理buf中没有元素的情况。如果没有元素,那么当前的recevier就会被阻塞,直到它从sender中接收到数据,或者chan被close才返回。
close
1.如果chan为nil,close会panic
2.如果chan已经closed,再次close也会panic
3.如果chan不为nil,chan也没有closed,就把等待队列中的sender和recevier从队列中全部移除并唤醒。
使用chan容易犯的错
使用chan最常见的错误是panic和goroutine泄露。
panic的情况有3种:
1.close为nil的chan
2.send已经close的chan
3.close已经close的chan
go的并发原语选择方法
1.共享资源的并发访问使用传统并发原语
2.复杂的任务编排和消息传递使用channel
3.消息通知机制使用chan,除非只想signal一个goroutine,才使用cond
4.简单等待所有任务的完成使用WaitGroup,也可以使用Channel,都可以
5.需要和select结合,使用Channel
6.需要和超时配合时,使用Channel和context
14.透过代码看典型的应用模式
补充一个知识点:通过反射的方式执行select语句,在处理很多case clause,尤其是不定长的case clause时非常有用
使用反射操作channel
select语句可以同时处理chan的send和recv,send和recv都可以作为case clause。
如果需要处理一个slice of chan,应该如何处理?通过使用reflect.Select函数。
典型的应用场景
1.消息交流
把它当成一个线程安全的队列和buffer使用。
例子1:worker池的例子。例子2:etcd的node实现
2.数据传递
击鼓传花的游戏。可以定义一个令牌在多个goroutine中传递。
3.信号通知
chan类型有这样一个特点。chan如果为空,那么receiver接收数据的时候就会阻塞等待,直到chan被关闭或者有的数据到来。利用这个机制可以实现wait/notify的设计模式。传统并发原语cond也可以实现,只是比较复杂,容易出错。另外可以做一些类似优雅关闭的实现。
4.锁
在chan的内部,就有一把互斥锁保护着它的所有字段。chan的发送和接收之间也存在着happens-before的关系。
happens-before,是指时间发生的先后顺序关系。
实现方式至少有两种:
1.初始化一个capacity为1的channel,放入元素。谁取到这个元素就代表获取到了锁。
2.初始化一个capacity为1的channel,空槽代表锁,谁成功发送元素到channel代表获取到了锁。
5.任务编排
使用channel可以实现WaitGroup的功能,这里不做展开。介绍几种更复杂的场景。
1.Or-Done模式
2.扇入模式
3.扇出模式
4.Stream
5.map-reduce
15.内存模型:GO如何保证并发读写的顺序
Go官方文档中专门介绍了Go的内存模型,它并不是指Go对象的内存分配、内存回收和内存管理的规范,它描述的是并发环境下多goroutine读取相同变量的时候,变量的可见性条件。
也就是指,在什么条件下多goroutine在读取一个变量的值时,能够看到其他goroutine对这个变量进行的写的结果。
由于CPU指令重排和多级cache的存在,保证多核访问同一个变量这件事变的非常复杂。
编程语言需要一个规范,来明确多线程同时访问同一个变量的可见性和顺序,
这个规范被叫做内存模型。
定义内存模型的目的:
1.在程序员开发程序时,面对同一个数据被多个goroutine访问时,可以做一些串行化访问的控制,比如使用channel或者sync包和sync/atomic包中的并发原语。
2.允许编译器和硬件对程序做一些优化。
重排和可见性的问题
由于代码重排,代码并不一定会按照你写的顺序执行。
var a,b int func f(){ a = 1 b = 2 } func g(){ print(b) print(a) } func main(){ go f() g() } //可能出现的结果,b=2的时候,a还是为0的。
程序在运行的时候,两个操作的顺序可能不会得到保证,那么就需要happens-before。
happens-before用来描述两个时间的顺序关系,如果某些操作能提供happens-before关系,那么我们就可以100%保证它们之间的顺序。
happens-before
在一个goroutine内部,程序的执行顺序和它们的代码指定的顺序是一样的,即使编译器或者CPU重排了读写顺序,从行为上来看,也和代码指定的顺序一样。
对单个goroutine来说,它有一个特殊的happens-before关系:
在单个goroutine内部,happens-before的关系和代码编写的顺序是一致的。
在Go语言中,可以使用并发原语为读写操作建立happens-before关系,这样就可以保证顺序了。
1.在Go语言中,对变量进行零值的初始化就是一个写操作
2.如果对超过机器word大小的值进行读写,可以看作是对拆成word大小的几个读写无序进行
3.Go并不提供直接的CPU屏障来提示编译器或者CPU保证顺序性,而是使用不同架构的内存屏障指令来实现统一的并发原语。
Go语言中保证的happens-before关系
除了单个goroutine内部提供的happens-before保证,Go语言中还提供了一些其他的happens-before关系的保证。
init函数
应用程序的初始化是在单一的goroutine执行的。如果包p导入了包q,那么q的init函数的执行一定happens-before p的任何初始化代码。main函数一定在导入的包的init函数之后执行。
同一个包下的多个文件,会按照文件名的排列顺序进行初始化。
goroutine
启动goroutine的go语句的执行,一定happens before此goroutine内的代码执行。
var a string func f(){ print(a) } func hello(){ a = "hello world" go f() } //输出hello world
Channel
Channel是goroutine同步交流的主要方法。往一个Channel中发送一条数据,通常对应着另一个goroutine从这个Channel中接收一条数据。
Channel happens-before关系保证有4条规则
1.第n个send一定happens before第n个receive的完成
2.close一个Channel的调用,一定happens before从关闭的Channel中读取一个零值。
3.对于容量为0的Channel,从此Channel中读取数据的调用一定happens before往此Channel发送数据的调用完成。
4.如果Channel的容量是m,那么第n个receive一定happens before第n+m个send的完成。
Mutex/RWMutex
3条happens before关系的保证
1.第n次的m.Unlock一定happens before第n+1 m.Lock方法的返回
2.只有释放了持有的写锁,等待的读请求才能请求到读锁
3.如果第n个m.RLock方法的调用已返回,那么它的第k(k<=n)个成功的m.RUnlock方法的返回一定happens before任意的m.Rulock方法调用。
WaitGroup
Wait方法等到计数值归零之后才返回
Once
函数f一定会在Do方法返回之前执行
atomic
可以保证使用atomic的Load/Store的变量之间的顺序性,但是过于复杂,现阶段不建议使用atomic保证顺序性
这篇关于Go并发编程实战课(Note.7:Channel)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15SendGrid 的 Go 客户端库怎么实现同时向多个邮箱发送邮件?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么设置header 和 标签tag 呢?-icode9专业技术文章分享
- 2024-11-12Cargo deny安装指路
- 2024-11-02MongoDB项目实战:从入门到初级应用
- 2024-11-01随时随地一键转录,Google Cloud 新模型 Chirp 2 让语音识别更上一层楼
- 2024-10-25Google Cloud动手实验详解:如何在Cloud Run上开发无服务器应用
- 2024-10-24AI ?先驱齐聚 BAAI 2024,发布大规模语言、多模态、具身、生物计算以及 FlagOpen 2.0 等 AI 模型创新成果。
- 2024-10-20goland工具下,如修改一个项目的标准库SDK的版本-icode9专业技术文章分享
- 2024-10-17Go学习:初学者的简单教程
- 2024-10-17Go学习:新手入门完全指南