四种并发编程模型简介

概述

 

并发往往和并行一起被提及,但是我们应该明确的是“并发”不等同于“并行”

 

•       并发 :同一时间 对待 多件事情 (逻辑层面)

 

•       并行 :同一时间 做(执行) 多件事情 (物理层面)

 

并发可以构造出一种问题解决方法,该方法能够被用于并行化,从而让原本只能串行处理的事务并行化,更好地发挥出当前多核CPU,分布式集群的能力。

 

但是,并发编程和人们正常的思维方式是不一样的,因此才有了各种编程模型的抽象来帮助我们更方便,更不容易出错的方式构建并发程序。下面将简单介绍一些常见的并发编程模型,希望能帮助大家对并发编程有更多的兴趣。这些模型都有各自的优势,需要根据应用场景挑选,而挑选的前提是能够深入地理解它们。

 

多线程编程模型

 

多线程模型是用于处理并发的最通用手段,在 C/C++/JAVA 等语言中广泛存在。主要特性有:

 

l  多个相互独立的执行流.

 

l  共享内存(状态).

 

l  抢占式的调度.

 

l  依赖锁,信号量等同步机制

 

多线程程序容易编写(因为写的是顺序程序),但是难分析,难调试,更容易出错,常见的有竞争条件,死锁,活锁,资源耗尽,优先级反转… 等等。

 

为了降低多线程模型编写难度,很多语言都一直在不断地引入并发编程方面新的特性,例如Java。从最早1996年的JDK1.0 版本起就已经有了Thread,Runnable类,确立了最基础的线程模型,这已经比直接调用POSIX接口构建多线程应用的方式有了很大的提高。然后在JDK5时引入了java.util.concurrent包,其中的线程池(Thread Pool,Executors)等类库,使得Java并发编程的易用性有了更好的提升。

 

到了JDK7, Fork/Join框架被引入,虽然底层一样是基于ExecutorService线程池的实现。但在编写并发逻辑时会比传统多线程方式更加直观,开发者可以将一个大的作业抽象为几个可以并发的子任务的结果整合;而每个子任务又可以继续按此逻辑继续划分,充分发挥现代多核CPU的性能。

同时,Fork/Join框架中还内置了Work-Stealing的任务调度机制,能够在尽量降低线程竞争的同时尝试自动均衡各工作线程之间的任务负载。如下图所示: 

 

Ø  4个线程每个都有独立的工作队列,避免单任务队列竞争

Ø  队列中的任务采用类似LIFO方式进出。由于整体作业都是按照一个大任务fork出多个子任务来抽象,因此可以视为越大粒度的任务会沉在队列的越底部。

Ø  当某个线程(示例中为线程D)的工作队列为空时,该线程就会自动尝试从另一个线程(示例中为线程A)的队列底部”偷“一个任务过来执行。由于是从底部窃取的任务,可以假设这个任务将展开更多的子任务,从而减少窃取动作的产生,降低线程争用频率。

通过这些手段,Fork/Join框架能帮助开发者无需在考虑手动实现并发任务执行时的高效同步逻辑。

 

随后,JDK8中又引入了并行流(Parallel Streams)的概念, 该特性基于Fork/Join框架,但在易用性方面继续有所提升。并行流采用共享线程池的思路,从而连线程/线程池的配置逻辑都帮开发者简化了。当然,正是因为这个共享池( ForkJoinPool.commonPool() )是被JVM管理,同时被JVM内的所有线程共享,也导致了一些隐患,如果开发者并没有了解并行流的底层实现机制,则可能导致应用中利用到并行流的任务产生停滞现象。例如下面的代码示例:

 

由于 WS.url(url).get()会触发HTTP请求,因此执行到这一句代码时,线程池会被阻塞在IO操作上,结果导致了当前JVM中所有并行流中的任务全部被阻塞。

Callback编程模型

“回调”是一个很容易理解的名词。简单来说:某个函数(A)可以接受另一个函数(B)作为参数,在执行流程到某个点时作为参数的函数B就会被函数A调用执行,这个行为就被称为回调。

 

现实中,回调常常用于异步事件。即,函数A一般会在函数B没有被调用的情况下就先返回,而在某个异步事件发生时再触发调用函数B。

 

但是滥用回调嵌套,就会导致著名的”callback hell”问题,代码难以阅读和维护。例如下面的片段:

 

为了避免此类大坑,我们可以参考以下几类解决方案:

 

l  Promises/A+规范: 它是一种用于管理异步回调的代码结构和流程,一种回调的语法糖。可以把原本嵌套的回调函数展平,使得代码逻辑更清楚。例如片段:

 

l  Generator: 生成器/半协程方式: 可以将一个函数执行暂停,并保存上下文, 将控制权交还给调用者;当再次被调用时,能够恢复当时的暂停状态继续执行。所以generator函数的行为表现和迭代器很类似,每次触发它的时候可以获取到新的结果,而不是像传统函数全部执行结束后一口气返回一系列值。 代码片段:

 

l  Async/Await:  可以视为Generator方式的语法糖,能够更好地展示异步调用的语义: async关键字用于表示该函数中有异步操作;await关键字表示需要等待(异步方式)后继表达式的结果。

 

Actor编程模型

Actor模型首先是由Carl Hewitt在1973年提出定义, 随后由Erlang OTP (Open Telecom Platform) 推广开来。Actor属于并发组件模型, 通过组件方式定义并发编程范式的高级阶段,避免使用者直接接触多线程并发或线程池等基础概念,其消息传递更加符合面向对象的原始意图。

传统多数流行的语言并发是基于多线程之间的共享内存,使用同步机制来防止写争夺。而Actors使用消息模型,每个Actors在同一时间处理最多一个消息,可以发送消息给其他Actors,保证了单独写原则,从而巧妙避免了多线程的写争夺。

Actor模型不仅仅对于单机的并发应用开发有意义,对于分布式应用的开发也是一个可以大展手脚的场景: 节点之间互相独立,只能靠消息通讯,异步消息避免节点瓶颈等特性都非常贴合Actor模型的使用。

Actor模型的特点是:

l  万物皆是Actor

l  Actor之间完全独立,只允许消息传递,不允许其他”任何”共享

l  每个Actor最多同时只能进行一样工作

l  每个Actor都有一个专属的命名Mailbox(非匿名)

l  消息的传递是完全异步的;

 

l  消息是不可变的

在Java中,可以利用Akka进行Actor编程模型的应用开发。Akka 将自身定义为一套用于构建JVM上高并发,容错式,分布式,消息驱动特性应用开发的工具包和运行环境。详细介绍可参见官网: http://akka.io/。

下面用代码片段来展示下基于AKKA开发示例:

我们定义了两个Actor: HelloWorld 和 Greeter.

l  HelloWorld会处理几个消息

n  启动消息(可以将preStart方法的调用视为收到一个专属启动事件的处理): 主动向Greeter(ActorRef可以视为对应Actor的专属Mailbox)发送一个Msg.GREET消息

n  Msg.Done消息: 接收完该消息后,停止当前Actor

n  其他消息: 调用unhandled() 处理

l  Greeter会处理这些消息:

n  Msg.GREET消息: 向System.out输出字符串, 并向消息的发送者回复一个Msg.Done消息

n  其他消息: 调用unhandled() 处理

 

HelloWorld,Greeter可以根据需要实例化在多个线程中执行,编码过程中不需要考虑传统多线程中的Lock/Wait/Notify等同步手段就能让这两个Actor之间分别指示对方完成相应动作。

 

  

CSP编程模型

CSP(Communicating Sequential Processes)是由Tony Hoare在1978的论文上首次提出的。 它是处理并发编程的一种设计模式或者模型,指导并发程序的设计,提供了一种并发程序可实践的组织方法或者设计范式。通过此方法,可以减少并发程序引入的其它缺点,减少和规避并发程序的常见缺点和bug,并且可以被数学理论所论证。

CSP将程序分成两种模块,Processor 与 Channel:Processor 代表了执行任务的顺序单元,它们内部没有并发,而Channel代表了并发流之间的信息交互,如共享数据的交换、修改、消息传递等等。

除了Channel,Processor之间再无联系,这样就将并发同步作用缩小在Channel之处,使得问题得到了约束、集中。同步操作与争用并没有消失,只是聚焦在Channel之上。Processor之间的协作,Channel提供原语来支持,如Barrier等。

CSP 的好处是使得系统较为清晰,Processor 之间是解耦合的,职责也非常清楚,容易理解和维护。

l  工作者之间不直接进行通信

l  工作者向不同的通道中发布自己的消息(事件)。其他工作者们可以在这些通道上监听消息,发送者不知道具体谁在执行(匿名)

 

l  消息交互是同步方式

在Java中对于CSP模型的实现库有JCSP。 同时在JDK中的SynchronousQueue,和CSP中的Channel有异曲同工之妙。Executors.newCachedThreadPool()中就利用到了SynchronousQueue,任务提交者是并不清楚底层哪个线程会处理提交的任务,并且当提交任务操作完成时必然已经有某个线程接受了该任务(并不代表线程开始执行),因此提交操作这次消息交互是同步的方式。这和Executors.newFixedThreadPool()之类创建的线程池是截然不同的,其他线程池在提交操作完成时,任务分配给线程这个动作是异步的。

此外,Go语言内置的goroutines & channels并发模型就是参考了CSP的思想,因此Go的并发编程强调不要利用共享内存来进行线程通讯,而应该依靠通讯来共享数据(Do not communicate by sharing memory; instead, share memory by communicating),尽量避免锁和线程争用。

参考资料

l  http://web.stanford.edu/~ouster/cgi-bin/papers/threads.pdf

l  https://en.wikipedia.org/wiki/Actor_model

l  https://en.wikipedia.org/wiki/Communicating_sequential_processes

l  https://talks.golang.org/2012/waza.slide#1

l  https://www.quora.com/What-are-the-differences-between-parallel-concurrent-and-asynchronous-programming

l  http://wiki.commonjs.org/wiki/Promises/A

l  http://www.ibm.com/developerworks/cn/java/j-csp1.html

l  http://blog.takipi.com/forkjoin-framework-vs-parallel-streams-vs-executorservice-the-ultimate-benchmark/

l  https://www.cs.kent.ac.uk/projects/ofa/jcsp/cpa2007-jcsp.pdf

l  http://tutorials.jenkov.com/java-concurrency/index.html

l  http://www.raychase.net/698

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438214.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从编译到执行,C++如何开发SIMD友好的代码?

一:名词解释 Flynn分类法 Flynn于1972年提出了计算平台的Flynn分类法,主要根据指令流和数据流来分类。按照Flynn分类法,计算平台共分为四种类型。 1.单指令流单数据流机器(SISD) 2.单指令流多数据流机器(SIMD) 3.多指令流单数据流机器(MISD) 4.多指令流…

Nacos介绍

Nacos 英文全称为 Dynamic Naming and Configuration Service,是一个由阿里巴巴团队使用 Java 语言开发的开源项目。 参考:home (nacos.io) Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,可以将 Nacos 理解成服务注册中心…

在部署 C#项目时转换 App.config 配置文件

问题 部署项目时,常常需要根据不同的环境使用不同的配置文件。例如,在部署网站时可能希望禁用调试选项,并更改连接字符串以使其指向不同的数据库。在创建 Web 项目时,Visual Studio 自动生成了 Web.config、Web.Debug.config、We…

设计模式之Factory

设计模式之Factory 2016-08-04 11:57 设计模式总共有23种模式这种都仅仅是为了一个目的:解耦解耦解耦...(高内聚低耦合满足开闭原则) 介绍: Factory Pattern有3种当然是全部是creational pattern。 1.Simple Factory Pattern 2.Factory…

C#多线程之旅(七)——终止线程

阅读目录 一、什么时候用Thread.Abort();二、Thread.Abort的用法三、无法终止线程的情形四、Catch块中抛出异常五、Finally块中抛出异常六、Abort调用的时间先交代下背景,写《C#多线程之旅》这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中…

ASP.NET MVC 入门7、Hellper与数据的提交与绑定

本系列文章基于ASP.NET MVC Preview5. ASP.NET MVC提供了很多Hellper的方法,Hellper就是一些生成HTML代码的方法,方便我们书写HTML代码(有一部分的朋友更喜欢直接写HTML代码)。我们也可以利用.NET 3.5的扩展方法来书写我们自己的Hellper。 例如&#x…

ASP.NET MVC 入门8、ModelState与数据验证

ViewData有一个ModelState的属性,这是一个类型为ModelStateDictionary的ModelState类型的字典集合。在进行数据验证的时候这个属性是比较有用的。在使用Html.ValidationMessage()的时候,就是从ViewData.ModelState中检测是否有指定的KEY,如果…

ASP.NET MVC 入门9、Action Filter 与 内置的Filter实现(介绍)

本系列文章基于ASP.NET MVC Preview5. 有时候你想在调用action方法之前或者action方法之后处理一些逻辑,为了支持这个,ASP.NET MVC允许你创建action过滤器。Action过滤器是自定义的Attributes,用来标记添加Action方法之前或者Action方法之后…

ASP.NET MVC 入门10、Action Filter 与 内置的Filter实现(实例-防盗链)

本系列文章基于ASP.NET MVC Preview5. 前一篇中我们已经了解了Action Filter 与 内置的Filter实现&#xff0c;现在我们就来写一个实例。就写一个防盗链的Filter吧。 首先继承自FilterAttribute类同时实现IActionFilter接口&#xff0c;代码如下&#xff1a; /// <summary…

base64原理及其编解码的python实现

base64原理及其编解码的python实现base64base64简介base64编码表base64编码原理base64编解码的python实现其他base编码base16base32base36、base58、 base62、 base85、base91、 base92base64 base64简介 base64是一种基于64个可打印字符来表示二进制数据的表示方法。2664&am…

REVERSE-PRACTICE-JarvisOJ-1

REVERSE-PRACTICE-JarvisOJ-1[61dctf]androideasy[61dctf]stheasyDD - Android NormalDD - Android Easy[61dctf]androideasy apk文件&#xff0c;用jadx-gui打开 主要的逻辑为&#xff0c;获取输入&#xff0c;检验输入的长度&#xff0c;输入异或23后与已知数组比较&#xf…

NuGet学习笔记(1) 初识NuGet及快速安装使用

关于NuGet园子里已经有不少介绍及使用经验&#xff0c;本文仅作为自己研究学习NuGet一个记录。 初次认识NuGet是在去年把项目升级为MVC3的时候&#xff0c;当时看到工具菜单多一项Library Package Manager&#xff0c;右键项目文件多了一项Manage Nuget Packages...&#xff0c…

NuGet学习笔记(2) 使用图形化界面打包自己的类库

上文NuGet学习笔记(1) 初识NuGet及快速安装使用说到NuGet相对于我们最重要的功能是能够搭建自己的NuGet服务器&#xff0c;实现公司内部类库的轻松共享更新。在安装好NuGet扩展后&#xff0c;我们已经能够通过NuGet轻松下载自己需要的类库&#xff0c;下面来说一说如何将自己的…

REVERSE-PRACTICE-JarvisOJ-2

REVERSE-PRACTICE-JarvisOJ-2DD - HelloAPK_500DebugMeFindPassDD - Hello macos文件&#xff0c;无壳&#xff0c;ida分析 start函数和sub_100000C90函数没什么作用 主要的逻辑在sub_100000CE0函数&#xff0c;反调试检测和byte_100001040数组的循环变换&#xff0c;最后打印…

NuGet学习笔记(3) 搭建属于自己的NuGet服务器

文章导读 创建NuGetServer Web站点 发布站点到IIS 添加本地站点到包包数据源 在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库&#xff0c;接下来进行最重要的一步&#xff0c;从零开始搭建属于自己的NuGet服务器&#xff0c;诚然园子里…

REVERSE-PRACTICE-JarvisOJ-3

REVERSE-PRACTICE-JarvisOJ-3爬楼梯软件密码破解-1Classical CrackMe2Smali爬楼梯 apk文件&#xff0c;放到模拟器里运行一下 “爬一层楼”按钮可按&#xff0c;每按一下&#xff0c;“已爬的楼层”加1层 “爬到了&#xff0c;看FLAG”按钮不可按&#xff0c;应该是“已爬的楼…

管理全局包、缓存和临时文件夹

每当安装、更新或还原包时&#xff0c;NuGet 将管理项目结构多个文件夹之外的包和包信息&#xff1a; name说明和位置&#xff08;每个用户&#xff09;global‑packagesglobal-packages 文件夹是 NuGet 安装任何下载包的位置。 每个包完全展开到匹配包标识符和版本号的子文件…

REVERSE-PRACTICE-JarvisOJ-4

REVERSE-PRACTICE-JarvisOJ-4Classical CrackmeFindKeyClassical Crackme exe程序&#xff0c;输入注册码&#xff0c;输入错误弹窗 查壳发现是.Net程序&#xff0c;dnSpy打开&#xff08;要不要用de4dot unpack无所谓&#xff0c;这里打开的是解包过的程序&#xff09; 按下“…

在解决方案中所使用 NuGet 管理软件包依赖

使用程序包恢复功能可以在提交源代码时, 不需要将 packages 中的程序集文件提交到源代码管理中&#xff0c;大幅减少项目的尺寸。所有NuGet程序包都存储在解决方案的Packages文件夹中。 要启用程序包恢复功能&#xff0c;可右键单击解决方案&#xff08;注意&#xff0c;不是右…

REVERSE-PRACTICE-BUUCTF-1

REVERSE-PRACTICE-BUUCTF-1easyrereverse1reverse2内涵的软件easyre exe程序&#xff0c;无壳&#xff0c;ida分析 左侧函数窗找到main函数&#xff0c;flag提交即可 reverse1 exe程序&#xff0c;运行后提示输入flag&#xff0c;ida分析 左侧函数窗无明显的main函数&#…