go语言学习6:channel

172 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

channel 通道

channel通道是用来实现多个协程 Goroutines之间的通讯的,通道相当于一个管道,管道里面放的是数据, 管道一头放 则另一头取。Go语言虽然提供了传统的同步机制,但是Go语言强烈建议还是使用通道来实现Goroutines之间的通讯。Go语言强调 "不要通过共享内存来通信,而应该通过通信来共享内存"

当存在多个goroutine要传递某一个数据时,可以把这个数据封装成一个对象,然后把对象的指针传入channel通道中,另一个goroutine 从通道中读取这个指针。同一时间只允许一个goroutine访问channel通道里面的数据。所以go就是把数据放在了通道中来传递,而不是共享内存来传递。

//通道的声明 
var channel chan int 
//如果通道时nil 
则要通过make创建通道 channel= make(chan int)

通道的使用

通道中是如何发送和接收数据的。

不管是发送数据还是获取数据, 他们都是阻塞的,当一个goroutine 向另一个goroutine发送数据的时候,他就是阻塞的,直到有另外一个goroutine来取数据,则解除阻塞。相反的 读取数据也是阻塞的,直到另一个goroutine向他来写数据来解除阻塞。channel 本身就是同步的。也就是同一时间只允许一条goroutine来操作。要使用通道最少有两个goroutine来操作。一个goroutine是用不到channel的。

通过通道来传输数据的时候,读写操作必须是一一对应的,也就是说,有一个读的操作必须对应的有一个写的操作,否则程序会造成程序死锁。

通道的关闭

当发送者或者接收者把数据发生完毕,发送者可以关闭通道,通知接收方不会再有数据发送到channel上了然后发送方调用 close()方法关闭通道。接收者可以获取来自通道数据时候额外的变量,来检测通道是否已经关闭。

可以通过for range来循环取通道中的数据。for range 就是遍历所有数据,当没有数据的时候也就循环结束。所以可以 替代 v,ok:=<-ch1

缓冲通道

缓冲通道指的是有一个缓冲区,对于发送数据是将数据发送到缓冲区。当缓冲区满了之后才会被阻塞。

对于接收数据方,当没有数据可以接收的时候也会被阻塞。

定向通道

之前的通道都是双向通道,可以同时通过子goroutine向通道中发送数据和接收数据,或者从主goroutine中发送或者接收数据。而定向通道表示: 要么是只读通道,要么是只写通道。

  • chan <- T 只写通道
  • <- chan T 只读通道

死锁

死锁是指两个或两个以上的协程的执行过程中,由于竞争资源而阻塞的现象,如果没有外力介入,则无法继续进行下去。死锁的出现的情况有很多种,但都大多数都是因为资源竞争和数据通信的时候引起的。

常见的几种死锁场景

使用同步等待组创建协程,主协程中设置同步等待组的数量为4,而只加进去了3条协程,最终都执行完成之后,还有一条未执行,当程序进入阻塞状态的时候无法解锁,就造成了死锁。

还有一种是,一个通道在一个主goroutine协程里同时进行读和写。也会造成死锁。

协程开启之前就放数据,还没有准备好,就放数据,就会造成死锁。

通道1中调用了通道2,通道2中调用通道1,相互等着要对方的数据,造成死锁

select 语句实现通道的多路复用

我们在聊天过程中,有两条通道,一条专门负责发送消息给对方,另一条通道专门负责接收消息。虽然可以使用for循环来遍历每个通道的数据,达到同时接收到多个通道的数据,但是效率就比较差了。在Go语言中提供select关键字,可以同时响应多个通道的操作。

Go语言的并发模型 GPM

所有的并发模型在操作系统中,都是以线程模式存在的。操作系统根据访问的资源权限不同,又分为用户空间和内核空间,内核空间主要操作访问计算机CPU资源、I/O资源、内存资源、等硬件资源。用户空间不能直接访问计算机内核资源,必须通过系统调用库或者函数或者其他脚本来访问内核空间提供的资源。往往程序中所指的线程都是一个用户空间的线程,不是操作系统内的内核线程。用户线程与内核线程都是一对一的关系,大部分编程语言的线程库都是对操作系统的内核线程做了一层封装,创建出来的线程与内核线程静态进行关联,所以这种方式实现起来简单,但是完全要依靠内核线程来处理,并且不同的用户线程之间的创建销毁等操作都是由内核亲自来完成。如果需要大量的线程,则性能完全由内核来决定。

GPM的概念

  • G 就是goroutine的简称,代表一段需要被并发执行的go代码,其中保存了goroutine运行的一些状态信息。
  • M 就是真正服务工作的工作线程,是一个内核级线程,对内核级线程的封装,数量对应真实的CPU数,G需要依赖M才可以运行。M 保存了自己使用时候的栈信息、当前正在执行的G信息、以及与之绑定的P。
  • P 为M的执行提供了上下文,保存了M执行G的一些基本信息,保存了等待执行的G队列。