三分钟掌握Actor和CSP模型

8045db33690bd5473078cdc4ba8fd2f4.gif点击上方蓝字进行关注

前文传送门:《三分钟掌握共享内存模型和 Actor模型》, 一直想比较Actor模型与golang的CSP模型,经过一段时间的实战记录了本文。

Actor vs CSP模型

  • • 传统多线程的的共享内存(ShareMemory)模型使用lock,condition等同步原语来强行规定进程的执行顺序。

  • • Actor模型,是基于消息传递的并发模型,   强调的是Actor这个工作实体,每个Actor自行决定消息传递的方向(要传递的ActorB),通过消息传递形成流水线。

cc4376245f97d4c586480aa0f93eef63.png

本文现在要记录的是另一种基于消息传递的并发模型:CSP(communicating sequential process顺序通信过程)。

在CSP模型,worker之间不直接彼此联系,强调信道在消息传递中的作用,不谋求形成流水线。

消息的发送者和接受者通过该信道松耦合,发送者不知道自己消息被哪个接受者消费了,接受者也不知道是从哪个发送者发送的消息。

739f7822ee5781ba7c4405ad9b96655f.png

go的信道

go的信道[1]是golang协程同步和通信的原生方式。

同map,slice一样,channel通过make内置函数初始化并返回引用,引用可认为是常量指针[2]

两种信道:

  1. 1. 无缓冲区信道:读写两端就绪后,才能通信(一方没就绪就阻塞)

    这种方式可以用来在goroutine中进行同步,而不必显式锁或者条件变量。
  2. 2. 有缓冲区信道:就有可能不阻塞, 只有buffer满了,写入才会阻塞;只有buffer空了,读才会阻塞。

go的信道暂时先聊到这里。

我们来用以上背景做一道 有意思的面试题吧 。

c0d4d252956e69787cf4dd5701a27c04.gif

调试多线程的都懂.gif

两个线程轮流打印0到100?

我不会啥算法,思路比较弱智:#两线程#, #打印奇/偶数#, 我先复刻这两个标签。

通过go的无缓冲信道的同步阻塞的能力对齐每一次循环。
package main

import (
 "fmt"
 "strconv"
 "sync"
)

var wg sync.WaitGroup
var ch1 = make(chan struct{})

func main() {
 wg.Add(2)

 go func() {
  defer wg.Done()
  for i := 0; i <= 100; i++ {
   ch1 <- struct{}{}
   if i%2 == 0 { // 偶数
    fmt.Println("g0  " + strconv.Itoa(i))
   }
  }
 }()

 go func() {
  defer wg.Done()
  for i := 0; i <= 100; i++ {
   <-ch1
   if i%2 == 1 { // 奇数
    fmt.Println("g1 " + strconv.Itoa(i))
   }
  }
 }()
 wg.Wait()
}

题解:两个协程都执行0到100次循环,但是不管哪个线程跑的快,在每次循环输出时均会同步对齐, 每次循环时只输出一个奇/偶值, 这样也不用考虑两个协程的启动顺序。

31f957db517f312c50cbfbb3f373969f.png
思考我的老牌劲语C#要完成本题要怎么做?

依旧是#两线程#、#打印奇偶数#, 我没找到C#中能多次对齐线程的能力, 于是使用两线程相互通知的方式。

volatile static int i = 0;

static AutoResetEvent are = new AutoResetEvent(true);
static AutoResetEvent are2 = new AutoResetEvent(false);
public static void Main(String[] args)
{
     Thread thread1 = new Thread(() =>
     {
          for (var i=0;i<=100;i++)
          {
             are.WaitOne();
             if (i % 2 == 0)
             {
                 Console.WriteLine(i + "== 偶数");
             }
            are2.Set();
          }
     });
     Thread thread2 = new Thread(() =>
     {
         for (var i = 0; i <= 100; i++)
         {
           are2.WaitOne();
           if (i % 2 == 1)
           {
               Console.WriteLine(i + "== 奇数");
           }
           are.Set();
         }
});
            
  thread1.Start();
  thread2.Start();
  Console.ReadKey();
}

注意:

  • • volatile:提醒编译器或运行时不对字段做优化(处于性能,编译器/runtime会对同时执行的线程访问的同一字段进行优化,加volatile忽略这种优化 )。

  • • Object-->MarshalByRefObject-->WaitHandle-->EventWaitHandle--->AutoResetEvent[3] 本次使用了2个自动重置事件来切换通知,由一个线程通知另外一个线程执行。

  • (这个思路是群内各大佬讨论的结果, @yi念之间  @AutoCSer,  欢迎童鞋们提供更多方法。)

引用链接

[1] go的信道: https://www.runoob.com/w3cnote/go-channel-intro.html
[2] 常量指针: https://zhuanlan.zhihu.com/p/133225100
[3] AutoResetEvent: https://docs.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-6.0

50990bd77b96be8addac68601caf63dd.gif

本文内容和制图均为原创,文章永久更新地址请参阅左下角原文,如对您有所帮助,请不吝分享 。

专题相关:一网打尽

年终总结:2021技术文大盘点  |  打包过去,面向未来

项目总结:麻雀虽小,五脏俱全

理念总结:实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

云原生系列: 什么是云原生?

扫码关注我们

不会让您失望的。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有态度的马甲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值