Go CSP并发模型

Python018

Go CSP并发模型,第1张

Go的CSP并发模型

Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。

CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,由 Tony Hoare 于 1977 年提出。简单来说,CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 channel。CSP 模型的关键是关注 channel,而不关注发送消息的实体。 Go 语言实现了 CSP 部分理论

“ 不要以共享内存的方式来通信,相反, 要通过通信来共享内存。”

Go的CSP并发模型,是通过 goroutine和channel 来实现的。

goroutine 是Go语言中并发的执行单位。其实就是协程。

channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

Channel

Goroutine

goroutine和channel是Go语言非常棒的特色,它们提供了一种非常轻便易用的并发能力。但是当您的应用进程中有很多goroutine的时候,如何在主流程中等待所有的goroutine 退出呢?

1 通过Channel传递退出信号

Go的一大设计哲学就是:通过Channel共享数据,而不是通过共享内存共享数据。主流程可以通过channel向任何goroutine发送停止信号,就像下面这样:

func run(done chan int) {

for {

select {

case <-done:

fmt.Println("exiting...")

done <- 1

break

default:

}

time.Sleep(time.Second * 1)

fmt.Println("do something")

}

}

不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享。这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。

channel 可以看成一个 FIFO 队列,对 FIFO 队列的读写都是原子的操作,不需要加锁。对 channel 的操作行为结果总结如下:

读取一个已关闭的 channel 时,总是能读取到对应类型的零值,为了和读取非空未关闭 channel 的行为区别,可以使用两个接收值:

golang 中大部分类型都是值类型(只有 slice / channel / map 是引用类型),读/写类型是值类型的 channel 时, 如果元素 size 比较大时,应该使用指针代替,避免频繁的内存拷贝开销

main方法里创建了一个string类型的Channel,实现主协程与子协程 go SendMessage 进行通信。主协程执行到 <-values 时发生阻塞,等待读取 values Channel的值,而子协程执行 SendMessage 方法时写入 values Channel。即,主协程发生阻塞,等待子协程执行完毕后再继续执行。

执行的结果如下:

从日志可以看出,打印val语句必须在子协程 go SendMessage 执行完成后才执行。

如果在此基础上添加多一个协程写入 values Channel会发生什么?

在主协程中,启动两个子协程给Channel写数据。而在 SendMessage 方法里,为了达到显示效果,在写入Channel前先睡眠1秒,在主协程也添加睡眠时间。

执行日志打印如下:

发现只有其中一个协程完成写入Channel的操作。因为此Channel没有设置缓存,所有只能保存一个写入值。

那么如何才能保证两个子协程能正常写入Channel呢?

为了保证上面的两个子协程能顺利地把值写入Channel,我们创建一个带缓冲的Channel。

新创建的Channel缓冲两个值,这样就能保证两个子协程能正常写入到Channel中。下面看看打印日志:

如我们猜想一样,两个子协程都能顺利结束。

晚安~