Golang中的CSP并发机制

Flutter、Golang、Python、编译原理、算法、Chrome原理学习系列文章抢先看请关注【码农帮派】

【Golang学习系列文章,请扫二维码】

CSP是Go语言中特有的一种并发控制机制,相比于共享内存并发控制机制更为高效。CSP是Communicating Sequential Process的缩写。

 

CSP的设计理念,是依赖一个信息通道来完成两个通信实体之间的通信协调。

一些编程语言中使用的一种典型并发机制是Actor Model:

Golang中的CSP并发机制_第1张图片

在Actor Model中,会维护一个容量无限的MailBox(Message Queue),消息发送实体A会将Message添加到MailBox中,其EventLoop会循环检查MailBox中的信息,当监听到新消息的时候发送给接受实体B的MailBox中,接受实体B在其EventLoop的驱动下被动的处理消息。

 

CSP与Actor Model的不同点:

  • CSP模式中,消息是通过Channel来通讯的,Channel相当于一个消息通讯的中间人,这样可以让两个通讯实体的耦合更松一些;

  • Golang的Channel容量是可以设置的,并独立于协程的;

  • Actor Model中,接受进程是被动的处理消息,Golang中的协程可以主动的获取Channel中的信息,并进行相关的处理;

 

Golang中Channel的基本机制:

第一种:阻塞式通讯Channel

通信双方必须同时在Channel上,才能够完成本次交互,要是任意一方不在Channel上,那么一方就会被阻塞的等待,直到另一方完成本次交互。

Golang中的CSP并发机制_第2张图片

对于消息发送者A,要是消息接收者B不在Channel上,那么发送者A会被一直阻塞在那里,直到接收者B出现,才能够完成本次交互,发送者A才能够继续执行后续的代码;

 

对于消息接收者B,要是发送者A不在Channel上,那么接收者B会被一直阻塞在那里等待,直到发送者A出现,完成本次交互,接收者B才能够继续执行下面的任务。

 

第二种:Buffer Channel

Buffer Channel使得消息发送者和接收者之间有一种更为松的耦合性,Channel可以设置一个消息容量。

Golang中的CSP并发机制_第3张图片

在Channel容量没有满的情况下,消息发送者A是可以一直往Channel中非阻塞的放入消息的,而不需要等待接收者B必须同时出现在Channel上。一旦Channel的容量满了,那么消息发送者A就必须阻塞的等待,直到接收者B取出消息,将Channel空出来,发送者A才可以继续非阻塞的放入消息。

 

同样的,对于接收者B,只要Channel中有消息,接收者B就可以非阻塞的取消息,不需要阻塞的等待消息发送者A同时出现在Channel上。但要是Channel上是空的,那接收者B就必须阻塞的等待发送者A放入消息,接收者B才能够取到消息,并继续执行下面的任务。

 

类比Java中的Future机制

Java的Future机制是异步通信的机制,在主线程中可以启动一个FutureTask,启动之后要是不想阻塞的等待FutureTask的执行结果,可以在FutureTask执行的同时,非阻塞的干其他的事情,当我们要获得FutureTask结果的时候,调用Task的get方法获取结果,在get的时候,要是FutureTask已经执行完毕,就可以立即拿到结果,但要是FutureTask尚未执行完毕,就会阻塞的等待,直到FutureTask执行完毕,才能够继续执行下面的代码:

Golang中的CSP并发机制_第4张图片

 

下面我们使用Golang的CSP机制实现Java的FutureTask一样的效果:

首先,我们有两个Task,在同一个协程中完成两个Task:

Golang中的CSP并发机制_第5张图片

打印结果是顺序执行的,两个Task串行的执行:

Golang中的CSP并发机制_第6张图片

下面我们将ServiceTask进行包装,将其放在一个协程中:

Golang中的CSP并发机制_第7张图片

打印结果:

Golang中的CSP并发机制_第8张图片

上面的代码我们可以看到,AsyncServiceTask启动了一个协程异步执行ServiceTask的任务,并返回了一个Channel,在主协程中执行OtherTask的时候,并发的执行ServiceTask的任务,经过协程并发处理的两个Task执行的总时间缩短了。

 

但是需要注意的是,AsyncServiceTask中的子协程执行完任务之后(打印了 AsyncServiceTask GetResult), 之后便可以立即将结果放到Channel中,但由于主协程(接收者)还在阻塞的执行其他任务,不在Channel上,所以子协程(发送者)会被一直阻塞的等待在向Channel中放入消息的地方,直到主协程(接收者)通过<-retCh从Channel中获取到消息的时候(打印 ServiceTask Done. )之后,子协程才被释放继续执行下面的代码(打印 AsyncServiceTask Exited)。

 

可以看出,上面使用的Channel就是阻塞式通信的Channel,保证通信双方都在Channel上,通信才会完成,否则先到Channel的一方需要阻塞的等待另一方到Channel上,才会完成本次通信,双方通信实体才会继续执行之后的任务。

 

那么如何实现Buffer Channel呢,Buffer Channel的声明方式和普通的Channel的声明方式相似,只是需要声明Channel的capacity:

// 声明了一个容量为1个消息的ChannelretCh := make(chan string, 1)

完整的代码如下,只修改了Channel的声明:

Golang中的CSP并发机制_第9张图片

运行代码,打印的结果:

Golang中的CSP并发机制_第10张图片

上面的结果可以看出,AsyncServiceTask在获得ServiceTask的结果之后(打印 AsyncServiceTask GetResult),由于使用了Buffer Channel,子协程(消息发送者)可以立即将结果放入到Channel中,而此时主协程(消息接收者)还在执行其他的任务(OtherTask),主协程不在Channel上并没有造成子协程的阻塞等待。当主协程完成OtherTask之后,通过Channel获取到了子协程早已放好的消息(打印 ServiceTask Done.)。

你可能感兴趣的:(Go,Golang,Golang)