CSP并发编程

CSP(Communicating Sequential Processes,通信顺序进程):是用户描述并发系统中交互模式的形式化语言,其通过通道传递消息。核心思想是:不要通过共享内存来通信,通过通信来共享内存

通道结构与环形队列

通道在运行时是一个特殊的hchan结构体:runtime/chan.go

type hchan struct {
	qcount   uint           // total data in the queue
	dataqsiz uint           // size of the circular queue
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	closed   uint32
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

每个字段的含义:

对于有缓冲的通道,存储在buf中的数据虽然是线性的数组,但是用数组和序号recvx, recvq模拟了一个环形队列:

  • recvx:可以找到从buf哪个位置获取通道中的元素
  • sendx:能够找到写入时放入buf的位置,这样做主要是为了重用已使用过的空间。
  • recvx到sendx的距离,代表通道队列中元素的数量

通道操作

初始化

在运行时调用了makechan函数 makechan(t *chantype, size int) *hchan

  • 当size为0,只在内存中分配hchan结构体的大小
  • 当通道元素不包含指针,连续分配hchan结构体大小 + size大小
  • 当通道元素包含指针,需要单独分配内存空间

写入

当向通道写入数据,调用了chansend ,写入元素时分3种不同的情况

  1. 有正在等待的读取协程

    recvq字段存储了正在等待的协程链表,每个协程对应一个sudog结构,它是对协程的封装。当有读取的协程正在等待时,直接从等待的读取协程链表中获取第一个协程,并将元素之间复制到对应的协程中,再唤醒该协程。

  2. 缓冲区有空余

    向当前缓冲区写入当前元素

  3. 缓冲区无空余

    将当前协程的sudog结构体放入sendq链表末尾,并且当前协程陷入休眠状态,等待被唤醒重新执行

读取

调用了chanrecv 函数,也分3中不同的情况

  1. 有正在等待的写入协程

    直接从等待的写入协程链表获取第一个协程,并将写入的元素直接复制到当前协程中,再唤醒被阻塞的写入协程,这样当前协程不需要陷入休眠。

  2. 缓冲区有元素

    读取缓冲区的数据,写入当前读取的协程中

  3. 缓冲区无数据

    将当前协程的sudog结构体放入recvq链表末尾,当前协程陷入休眠状态,等待被唤醒重新执行