当多线程并发遇到 Actor

本贴最后更新于 2379 天前,其中的信息可能已经沧海桑田

1

多线程并发的难题

张大胖在做一个银行相关的项目,写了一个 Account 的类,用来表示一个用户的银行账号,根据银行的常规业务,自然要提供两个方法,存款(deposit)和取款(withdraw)。

为了防止多线程并发时导致的数据不一致问题,张大胖给每个方法都加了 synchronized, 那意思很清楚,想进入某个方法执行存款或取款操作,必须得先获得一把锁才行。

(注:为了简化,这里没有做边界条件检查。)

但是在做转账操作的时候,为了保证一致性,必须得把两个账户都加上锁,然后才可以操作,于是张大胖写下了这样的代码,他觉得很简单,立刻就提交给 Bill ,让他 Review。

富有经验的 Bill 立刻就发现了问题,马上对张大胖说:“这样会出现死锁!”

张大胖说:“这么简单的代码,怎么可能有死锁?”

“假设线程 1 做的操作是账户 A 给账户 B 转账, 先锁住了 A 账户, 接下来试图申请 B 账户的锁;

与此同时线程 2 在从 账户 B 给账户 A 转账, 先锁住了 B 账户的锁, 接下来试图申请 A 账户的锁。

两个线程各自持有资源, 然后等待获取对方的资源, 都无法执行下去, 死锁就出现了!”

张大胖无言以对,不得不承认 Bill 是正确的。他问道:“那怎么解决这个问题?”

“非常简单,加锁的时候按次序来就可以了,例如所有的线程,无论是从 A 向 B 转账,还是从 B 向 A 转账,都先获得账号 A 的锁,成功后再获得账户 B 的锁,这样就没问题了。”

张大胖说:“那样代码会变得很古怪啊,还得给两个账户排个顺序,如果不知道背后的思想读起来很痛苦,怪不得人家说多线程编程很难啊。”

Bill 说:“是啊, 其实线程这个东西,就是一段代码的执行而已, 是操作系统层面的概念,可是我们苦逼的程序员不得不来面对它,来背这个多线程并发的锅了。”

2

黑盒子

下班后,张大胖一直在思考这个问题:既然线程是操作系统层面的概念,能不能把线程的概念隐藏起来,然后所有的操作都不用加锁呢? 这样以来编程就会容易得多啊!

本质的问题是什么?

首先是共享的状态,例如 Account 中的 balance ,多个线程都要读写, 其次就是多个线程乱序、并发执行。

能不能换个思路,把这个 Account 对象看成一个黑盒子,你想存款了,就发一个存款的消息过来,想取款就发一个取款的消息过来。

不管是有一个消息,还是有 100 个消息,我统统放到黑盒子的一个队例中,然后让 Account 对象一个个顺序处理不就可以了? 根本不用在方法上加锁!

这样做,其实就是把并发的操作变成了串行的操作而已!

不对,如果调用方把取款消息放下就走, 不等待返回结果, 那就不是同步操作,而是异步操作了!

但是如果取款的时候发现余额不足,怎么通知调用方?嗯,调用方也必须是个黑盒子对象,也向它发送异步消息,这个消息也会在消息队列中存下来,调用方“黑盒子”也会一个个处理。

想到这一层,张大胖激动起来:取款和存款的操作就不用在加锁了,码农们只要考虑黑盒子对消息的处理即可:取出消息,处理消息,向别的黑盒子发送消息, 根本不用考虑线程这样底层的概念了。

3

Actor 模型

第二天张大胖赶紧找到 Bill, 向他炫耀自己的“新发明”。

Bill 不动声色:“小伙子,不错啊,重新发明了轮子!”

“重新发明?”

“是啊,你这个所谓黑盒子,就是所谓 Actor 模型啊! 它最早由 Carl Hewitt 在 1973 定义,其消息传递的方式更加符合面向对象的原始意图, 这一点我想你也体会到了,要不你怎么把他们叫做黑盒子啊。”

“1973 年? 我还没出生。唉,看来这些概念已经被老前辈们都发明完了啊。”

“Actor 属于并发组件模型 ,可以把程序员从多线程并发或线程池等基础概念中解放出来。它有这么几个特点:”

Actor:

就是你说的黑盒子,系统是由很多 Actor 组成。 Actor 之间不共享状态,但是会接收别的 Actor 发送的异步消息,处理的过程中,会改变内部状态,也可能向别的 Actor 发送消息。

Message:

消息是不可变的, 它的发送都是异步的,Actor 内部有个“MailBox”来缓存消息。

MailBox:

Actor 内部缓存消息的邮箱, 其他 Actor 发送的消息都放到这里,然后被本 Actor 处理,类似有多个生成者和一个消费者的队例。

张大胖说:“和我之前的图差不多,看来我确实是重新发明了轮子啊。”

4

用 Actor 实现转账

Bill 笑道:“这个 Actor 看起来很美,但是编程的时候你得刷新一下你的思维才行。 大胖,之前你的转账操作在多线程下不是会出现死锁吗? 你考虑下,如果用 Actor 的思路该怎么写?”

“首先,得有两个 Actor, 这两个 Actor 表示了两个账户,我把它们叫做旺财和小强。”

“然后呢,转账的逻辑怎么处理?”

张大胖想了一会:“既然转账是在两个 Actor 之间发生的,那可以引入一个协调者 Actor,叫做转账管家吧。不过,由于消息都是异步的,转账管家向旺财这个 Actor 发起扣款请求以后,不知道什么时候才能真正执行扣款,也不能立刻知道是否成功,必须得等待啊,这就有点麻烦了。”

Bill 说:“我给你画个流程图,你看看。”

张大胖感慨地说:“原来的多线程并发模型,需要同时锁住两个账户,然后才能进行转账。现在每个 Actor 都独立,也把这个转账给搞定了。”

Bill 说:“其实对于转账管家来说,对每个转账的消息,内部是隐含一个流程状态的,就是先向某个账户扣款,成功以后再向另一个账户增加,最后给调用者返回状态,这个次序是不能乱的。看到图中那个 Transaction ID 没有(Tx01),就是用来跟踪这个转账的事务。”

4

漏洞

“我发现了一个漏洞,你这个转账虽然看起来很美,没有加锁,但是和原来的是有区别的,原来多线程思路是会把旺财和小强的账户同时锁住,然后转账,在这个过程中,别人是不能操作这两个账号的! 而你的 Actor 方案中,当转账管家给旺财发消息扣款的时候,小强其实是自由的,如果这时候小强的账户被冻结,那你的转账管家还得回滚旺财的扣款,这多麻烦啊。”

Bill:“哈哈,你小子还挺机灵的嘛,看出了这个问题,Actor 模型非常适用于多个组件独立工作,相互之间仅仅依靠消息传递的情况。如果想在多个组件之间维持一致的状态(比如咱们例子中的转账),那就不爽了。”

“那怎么解决这个问题?”

“那必须得用一些特殊手段了,有些实现 Actor 的框架,例如 Akka,专门提供了像 Coordinated /Transactor 这样的机制来处理这个问题。有空的话给你仔细讲讲。”

“好吧,我回头看看这个 Akka, 对了, Actor 虽然对用户隐藏了线程, 但是总得有线程来处理消息吧。” 张大胖问道。

“那是肯定的,线程本质上就是一段代码的执行,每个 Actor 在处理消息的时候,肯定得和线程关联才行,只不过 Actor 系统把线程这个概念给隐藏了。

“有哪些系统实现了 Actor?” 张大胖接着问。

“其实最著名的就是 Erlang 了,Actor 模型可以说是它的基础,除了我们上面所说的,还可以让 Actor 之间建立关联,例如让一个 Actor 去监控另外一些 Actor 工作,如果那些 Actor 崩溃了,就新建一个 Actor 继续工作。在 Java 领域,刚才提到的 Akka 是比较知名的一个 Actor 框架。

转发自:https://mp.weixin.qq.com/s/mzZatZ10Rh19IEgQvbhGUg

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 160 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3198 引用 • 8215 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    345 引用 • 742 回帖
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    180 引用 • 408 回帖 • 489 关注
  • 安全

    安全永远都不是一个小问题。

    203 引用 • 818 回帖
  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 568 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖 • 1 关注
  • abitmean

    有点意思就行了

    37 关注
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 177 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    146 引用 • 972 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 120 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖 • 3 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    88 引用 • 139 回帖 • 1 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    367 引用 • 1844 回帖 • 4 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1280 回帖 • 4 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 57 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 198 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 55 关注
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 6 关注
  • V2Ray
    1 引用 • 15 回帖
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    37 引用 • 157 回帖 • 1 关注
  • CodeMirror
    2 引用 • 17 回帖 • 161 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    133 引用 • 1124 回帖 • 115 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 812 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 30 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 786 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 547 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    54 引用 • 37 回帖