从零单排之golang:channel使用及源码详解

从零单排之golang:channel使用及源码详解_第1张图片

  • channel 的特性:不要通过共享内存来通信,而要通过通信来实现内存共享
1. channel是一个先进先出的队列,go语言提倡使用管道来来通信
2. channel在协程间通信是安全的,因为chan的底层数据结构维护一个互斥锁,在读取或者写入资源前会先获取锁
3. channel分为有缓存和无缓存的管道:有缓存的管道数据写满后阻塞管道,直到有其他的协程读取管道内的数据,无缓存的管道必须有其他的协程先读取,因为无缓存的管道写入一个数据就把程序永远的阻塞住了,这时候就会产生一个死锁,无论有缓冲还是无缓冲读取空管道的数据都会阻塞
4. 根据管道的阻塞性可以实现互斥锁的功能,也可以控制协程的执行顺序
5. 管道从流向又可分为:双向管道和单项管道,我们一般申请的资源都是双向的管道,做参数的传递的时候把双向的管道强转为单项的管道来保证数据的安全性,这样就可以很轻松的实现生产消费者的模式
6. 使用完的管道必须关闭
  • 管道的声明和定义
// 有缓存,len=0 cap=1
ch := make(chan int,1)

// 无缓存
ch := make(chan int)
// 只读的管道
onlyReadChan := make(<-chan int)

// 只写的管道
onlyWriteChan := make(chan <-int)
// 写入数据
ch <- 1

// 读取数据

v := <-ch
v, ok := <-ch
select {
case v, ok = <-ch:
 //  ... foo
default:
 //  ... bar
}

for m := range ch {
    // ...   do something
}
  • chan源码
type hchan struct {
 qcount   uint           // queue 里面有效用户元素,这个字段是在元素出对,入队改变的;可以理解为len(chan)
 dataqsiz uint           // 初始化的时候赋值,之后不再改变,指明数组 buffer 的大小;可以理解为cap(chan)
 buf      unsafe.Pointer // 指明 buffer 数组的地址,初始化赋值,之后不会再改变;
 elemsize uint16  // 指明元素的大小,和 dataqsiz 配合使用就能知道 buffer 内存块的大小了;
 closed   uint32
 elemtype *_type // 元素类型,初始化赋值;
 sendx    uint   // send index
 recvx    uint   // receive index
 recvq    waitq  // 等待 recv 响应的对象列表,抽象成 waiters
 sendq    waitq  // 等待 sedn 响应的对象列表,抽象成 waiters

 // 互斥资源的保护锁,官方特意说明,在持有本互斥锁的时候,绝对不要修改 Goroutine 的状态,不能很有可能在栈扩缩容的时候,出现死锁
 lock mutex
}

在 makechan 我们就看到初始化的时候其实只会初始化四个核心字段:

buf :指明 buffer 地址
elemsize :指明元素大小
elemtype :指明元素类型
dataqsiz :指明数组大小

熟悉这几个属性后结合特性基本可以推导它的底层实现
参考:
https://zhuanlan.zhihu.com/p/297053654
https://zhuanlan.zhihu.com/p/118329711

你可能感兴趣的:(go,golang,开发语言,后端)