Go-CSP并发机制

目录

一、CSP

1、什么是CSP

2、CSP VS Actor

二、Channel 消息处理机制

1、经典消息处理机制

2、buffer channel 机制

三、异步返回

1、同步执行的效果

2、经典 channel 机制异步返回效果

3、buffer channel 实现异步返回结果

四、总结


一、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 里面存放数据都用这个"<-"符号

声明一个channelmake(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-多路选择和超时控制

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值