Golang是从语言层面支持并发的编程语言。Golang支持协程,协程可以类比为Java中的线程,解决并发问题的难点在于线程之间的协作。
Golang提供了两种方案解决协作问题:
- 支持协程之间以共享内存的方式通信,Golang提供了管程和原子类来对协程进行同步控制,这个方案与Java语言类似;
- 支持协程之间以消息传递(Message-Passing)的方式通信,本质上是要避免共享,Golang的这个方案是基于CSP(Communicating Sequential Processes)模型实现的。
CSP模型定于
Actor模型中Actor之间是不嫩个共享内存的,彼此之间通信只能依靠消息传递的方式。Golang实现的CSP模型和Actor模型看上去非常相似,“不要以共享内存方式通信,要以通信方式共享内存(Don’t communicate by sharing memory, share momory by communicating)”。 Golang中协程之间,推荐以通信的方式共享内存,实际上指的就是协程之间以消息传递的方式来通信。
1 | import ( |
示例代码的目标是打印从1累加到100亿的结果,如果使用单个协程来计算,大概需要4秒多的时间。单个协程,只能用到CPU中的一个核,为了提高性能,可以用多个协程来并行计算,发挥多核的优势。
用4个子协程来并行执行,分别计算[1, 25亿]、(25亿, 50亿]、(50亿, 75亿]、(75亿, 100亿],最后再在主协程中汇总4个子协程的计算结果。主协程要汇总4个子协程的计算结果,势必要和4个子协程之间通信,Golang中协程之间通信推荐的是使用channel,可以理解为现实世界中的管道。calc()方法的返回值是一个只能接收数据的channel ch,它创建的子协程会把计算结果发送到这个ch中,而主协程也会将这个计算结果通过ch读取出来。
CSP模型与生产者-消费者模式
Golang实现的CSP模型可以类比为生产者-消费者模式,channel可以类比为生产者-消费者模式中的阻塞队列。
Golang中的channel的容量可以是0,容量为0的channel在Golang中称为无缓冲的channel,容量大于0的则称为有缓冲的channel。
无缓冲的channel类似于Java中提供的SynchronousQueue,主要用途是在两个协程之间做数据交换。(例如累加器示例中calc()方法内部创建的channel就是无缓冲的channel)
有缓冲的示例如下,创建一个容量为4的channel,同时创建4个协程作为生产者、4个协程作为消费者。
1 | //创建一个容量为4的channel |
Golang中的channel是语言层面支持的,可以使用一个左向箭头(<-)来完成向channel发送数据和读取数据的任务。Golang中的channel是支持双向传输的-一个协程既可以通过它发送数据,也可以通过它接收数据。
Golang中可以将一个双向的channel变成一个单向的channel,累加器示例中calc()方法中创建了一个双向channel,返回的是一个只能接收数据的单项channel,主协程中只能通过它接收数据,而不能通过它发送数据,如果试图通过它发送数据,编译器会提示错误。
CSP模型与Actor模型的区别
以消息传递的方式来避免共享。
Actor模型中没有channel。Actor模型中的mailbox和channel非常像,都像个FIFO队列。
Actor模型中的mailbox对于程序员来说是透明的,mailbox明确归属于一个特定的Actor,是Actor模型中的内部机制;而且Actor之间是可以直接通信的,不需要通信中介。但是CSP模型中的channel对于程序员来说是可见的,是通信中介,传递的消息都是直接发送到channel中的。Actor模型中发送消息是非阻塞的,而CSP模型中是阻塞的。Golang实现的CSP模型,channel是一个阻塞队列,当阻塞队列已满的时候,想channel中发送数据,会导致发送消息的协程阻塞。
关于消息送达。Actor模型理论上不保证消息百分百送达,而在Golang实现的CSP模型中,是能保证消息百分百送达的。这种百分百送达的代价是可能会导致死锁。
1
2
3
4
5
6
7
8//创建一个无缓冲的channel-ch,然后从ch中接收数据,此时主协程阻塞
//main()方法中的主协程阻塞,整个应用就阻塞了-死锁
func main() {
//创建一个无缓冲的channel
ch := make(chan int)
//主协程会阻塞在此处,发生死锁
<- ch
}
总结
Golang支持传统的共享内存的协程间通信方式,也支持使用CSP模型,以通信的方式共享内存。
Golang中实现的CSP模型,支持select语句-类似网络编程里的多路复用函数select(),只要有一个channel能够发送成功或者接收到数据就可以跳出阻塞状态。
Java领域可以借助第三方的类库JCSP来支持CSP模型。
CSP模型-托尼·霍尔(Tony Hoare)
霍尔管程模型-Hoare