目录
一、CSP
1、什么是CSP
CSP(Communicating Sequential Processes)通信顺序进程,是一种消息传递模型,通过通道 channel 在 Goroutine 之间传递数据来传递消息,而不是对数据加锁来实现对数据的同步访问。
2、CSP VS Actor
- Actor 的机制是直接进行通讯,CPS模式则是通过 channel 进行通讯的,更松耦合一些。
- Actor 是通过 mailbox 来进行消息存储的,mailbox 的容量是无限的,Go 的 channel 的容量是有一定的限制的。
- Actor 的接收进程总是被动的去处理消息,Go 的协程会主动去处理从 channel 里面传过来的消息。
二、Channel 消息处理机制
1、经典消息处理机制
进行通讯的发送方和接收方必须同时在 channel 上才能完成这次交互,任何一方不在都会导致另一方阻塞等待。
《望夫石》大家都背过吧,望夫石,江悠悠。化为石,不回头。上头日日风复雨。行人归来石应语。就是这种感觉,
在来一幅消息交互图就更加清晰啦。
2、buffer channel 机制
这种机制下,消息的发送方和接收方是更加松耦合的一种机制,我们可以给 channel 设定一个容量,只要在这个容量还没有满的情况下,放消息的人都是可以把消息放进去的,如果容量满了,则需要阻塞等待了,等到收消息的人拿走一个消息,放消息的人才能继续往里面放。同理,对收消息的人来说呢,只要这个 channel 里面有消息,就可以一直拿到,直到 channel 里面一个消息都没有了,就会阻塞等待,直到有新的消息进来。
这种机制就跟我们小区楼下的蜂巢快递柜差不多,快递员有包裹就往柜子里面放,只要柜子没满,他就可以一直放,如果柜子满了,就需要等取件人去取一个快递出来,快递员才能继续往里面放快递。
也来一幅消息交互图就更加清晰啦。
三、异步返回
当我们调用一个任务,并不需要马上拿到它的返回结果,可以先去执行其它的逻辑,直到我们需要这个结果的时候,在去 get 这个结果。这将大大减少程序的整体运行时间,提升程序的效率。如果我们 get 这个任务的结果时,任务的结果还没有出来,就会堵塞在那里,直到拿到结果为止。
1、同步执行的效果
我们在 TestService() 里面先调用 service() 方法,在调用 otherTask() 方法,理论上最后程序的执行时间为二者相加。
package csp
import (
"fmt"
"testing"
"time"
)
func service() string {
time.Sleep(time.Millisecond * 50)
return "service执行完成"
}
func otherTask() {
fmt.Println("otherTask的各种执行逻辑代码")
time.Sleep(time.Millisecond * 100)
fmt.Println("otherTask执行完成")
}
//测试同步执行效果, 先调用 service() 方法,在调用 otherTask() 方法,
//理论上最后程序的执行时间为二者相加。
func TestService(t *testing.T) {
fmt.Println(service())
otherTask()
}
/*
=== RUN TestService
service执行完成
otherTask的各种执行逻辑代码
otherTask执行完成
--- PASS: TestService (0.16s)
PASS
*/
最后的执行结果却是如我们所愿,为 0.16 秒,service()执行了 0.05 秒,otherTask() 执行了 0.1秒,加上主程序耗时,共 0.16秒。
2、经典 channel 机制异步返回效果
package csp
import (
"fmt"
"testing"
"time"
)
func syncService() chan string {
//声明一个 channel, 数据存放只能为 string 类型
resCh := make(chan string)
go func() {
ret := service()
fmt.Println("service 结果已返回")
//因为不是用的 buffer channel, 所以,协程会被阻塞在这一步的消息传递过程中,
//只有接受者拿到了 channel 中的消息,channel 放完消息后面的逻辑才会被执行。
resCh <- ret
fmt.Println("channel 放完消息后面的逻辑")
}()
return resCh
}
func otherTask() {
fmt.Println("otherTask的各种执行逻辑代码")
time.Sleep(time.Millisecond * 100)
fmt.Println("otherTask执行完成")
}
//异步返回执行结果,先调用 SyncService(), 把它放入channel,用协程去执行,
//然后主程序继续执行 otherTask(),最后吧 SyncService() 的返回结果
//从 channel 里面取出来。
func TestSyncService(t *testing.T) {
resCh := syncService()
otherTask()
fmt.Println(<-resCh)
}
/*
=== RUN TestSyncService
otherTask的各种执行逻辑代码
service 结果已返回
otherTask执行完成
service执行完成
channel 放完消息后面的逻辑
--- PASS: TestSyncService (0.10s)
PASS
*/
我么会发现最后的执行结果只花了 0.10 秒,说明在 otherTask() 执行的 0.1秒,service() 因为只需要 0.05秒,所以就提前执行完了,只需要在需要的地方取出结果就行,极大的减少了程序的整体执行时间。
从 channel 里面存放数据都用这个"<-"符号
声明一个channel:make(chan string)
3、buffer channel 实现异步返回结果
我们会发现上面这中机制仍然有一个小问题,那就是 service() 执行完毕后,往 channel 里面放数据,此时协程就阻塞在这里了,需要等到接收者拿到消息,协程才会继续往下走,我们可不可以让协程不阻塞呢?当service() 执行完毕后,我们将消息放入 channel 中,然后继续执行其它的逻辑。答案是可以的,此时我们的 buffer channel 就派上用场了。
package csp
import (
"fmt"
"testing"
"time"
)
//异步执行 service(), 并将结果放入 buffer channel
func syncServiceBufferChannel() chan string {
//声明一个 channel, 数据存放只能为 string 类型,
//后面的数字表示 buffer 的容量
resCh := make(chan string, 1)
go func() {
ret := service()
fmt.Println("service 结果已返回")
//此时使用的是 buffer channel, 所以只要 service() 结果返回了,buffer容量未满
//channel放完消息后面的逻辑就会被执行,不会被阻塞。
resCh <- ret
fmt.Println("channel 放完消息后面的逻辑")
}()
return resCh
}
func otherTask() {
fmt.Println("otherTask的各种执行逻辑代码")
time.Sleep(time.Millisecond * 100)
fmt.Println("otherTask执行完成")
}
//异步返回执行结果,先调用 SyncService(), 把它放入 buffer channel,用协程去执行,
//此时协程不会被阻塞,然后主程序继续执行 otherTask(),
//最后把 TestSyncServiceBufferChannel() 的返回结果从 channel 里面取出来。
func TestSyncServiceBufferChannel(t *testing.T) {
resCh := syncServiceBufferChannel()
otherTask()
//从 channel 里面存放数据都用这个"<-"符号
fmt.Println(<-resCh)
}
/*
=== RUN TestSyncService
otherTask的各种执行逻辑代码
service 结果已返回
channel 放完消息后面的逻辑
otherTask执行完成
service执行完成
--- PASS: TestSyncService (0.10s)
PASS
*/
我们会发现采用了 buffer channel 后,当 service() 的返回结果放入 buffer channel 后,协程并没有阻塞,而是继续执行了 “channel 放完消息后面的逻辑”,其它的结果和经典 channel 一致。
声明一个buffer channel 很简单,只有在后面定义一下容量即可。
声明一个buffer channel :make(chan string, 1)
四、总结
- CSP是一种通过通道 channel 在 Goroutine 之间传递数据来传递消息的机制。
- 声明一个channel:make(chan string);
- 声明一个buffer channel :make(chan string, 1);
- 从 channel 里面存放数据都用这个"<-"符号。
注:这篇博文是我学习中的总结,如有转载请注明出处:
https://blog.csdn.net/DaiChuanrong/article/details/118281505
上一篇:Go-共享内存并发机制
下一篇:Go-多路选择和超时控制