java 并发 csp_CSP与并发编程

原标题:CSP与并发编程

随着Go语言的逐渐走红,CSP(Communicating Sequential Process)并发模型也更多地被程序员所谈论。

计算机科学中,CSP是一种描述并发系统交互模式的形式化语言,其交互模式是通过channel进行消息传递。Go语言借鉴了CSP模型的理论,使用goroutine(一种运行在用户态的协程)和在goroutine之间收发消息的channel实现了高效并发。为什么Go语言要使用CSP模型来实现并发呢?

本文将做一些粗浅的探讨,最后用支持CSP并发模型的编程语言实现简短的并发程序,并与Java实现的并发程序做性能比较。

先来看看几种并发编程模型。

多线程

提起并发编程,最常见的就是多线程编程。线程是操作系统能够进行调度的最小单位,共享同一进程的数据和资源,有内核线程和用户线程之分,由操作系统或用户进程调度。多线程的程序可以利用多核CPU,并行地处理多个任务。随着并发量增大,线程数增加,多线程的并发模型面临一些问题。

内存占用

64位JVM线程默认栈空间是1M,启动1024个线程理论上消耗1G的栈空间。由于线程需要内存较多,为避免内存耗尽,应用程序不应该大量创建线程。

线程调度

操作系统对线程进行调度也需要成本。线程挂起前会保存线程上下文到栈空间,再切换到可执行线程。线程数很多时,线程上下文切换会导致CPU开销变大。

使用线程池技术可以对多线程并发进行优化,线程池实现线程复用,避免频繁创建新线程和线程切换,控制了线程数量,减少了内存的消耗。但是在竞争共享数据的时候,需要用加锁来保护共享数据,这样也降低了程序的并发效率。

异步回调

为了充分利用CPU,不让线程空等待,在线程阻塞的时候,注册一个回调方法,让当前线程不再阻塞,去处理新的请求。等结果准备好,调度器把结果传给回调方法,在回调方法中继续处理结果。然而回调方法并不在发起请求的线程里执行。

异步回调的缺点是所谓的callback hell。原本顺序同步的执行逻辑拆分到回调方法中,而且回调方法中可能再嵌套回调方法。这种写程序的方式还是让很多程序员不太习惯。异步回调的典型实现是NodeJS,目前也有一些第三方模块将异步代码同步化。

协程(纤程)

协程(纤程)也是一种异步方案。在代码IO阻塞时,当前协程让出CPU执行权,让其它协程执行。待IO操作完毕,阻塞的协程继续执行。虽然代码是异步执行,但写代码看起来像是同步的。协程是用户态的轻量级线程,现在的机器可以启动百万数量的协程。支持协程的编程语言实现了协程的调度器,提供了channel机制进行协程间通信(CSP模型中消息传递的实现)。基于CSP模型的协程方案,实现了无共享内存无锁的并发,可以匹配异步回调的性能。

CSP模型最初于Tony Hoare的1977年的论文中被描述,影响了许多编程语言的设计。

下面从被CSP模型所影响的编程语言中选择一个无名小卒,写一段简短的例子程序完成一个计算任务。

任务:计算0, 1, 2, ... 9999999的和。

硬件环境:4核i7-3520M 2.90GHz, MemTotal: 16121944 kB

N = 10* 1000* 1000

M = N / 4t1 = Time.nowchannel = Channel(Int32). new(M)#使用单线 程4协程

4.times { |i| spawn { M.times { |j| channel.send(i * M + j) } }}sum = 0_i64N.times { sum += channel.receive}t2 = Time.nowputs "telapsed time: #{t2 - t1}, sum=#{sum}"

运行结果:

c4443b20a4fa6b182145a128d576837a.png

Java1.8.0_121版本的例子程序

publicclassJava8Language{

privatestaticfinalintTIMES = 10* 1000* 1000;

publicstaticvoidmain(String[] args)throwsException{

finalCountDownLatch c = newCountDownLatch(TIMES);

finalAccumulator a = newAccumulator(c); longt1 = System.currentTimeMillis(); ExecutorService service = Executors.newFixedThreadPool( 4);

for( longi= 0;i

finalLong k = i; service.submit(() -> { a.add(k); c.countDown(); }); } c.await();

longt2 = System.currentTimeMillis(); System.out.printf( "telapsed time: %.3fs, sum=%dn", (t2-t1)/ 1000f,

a.getSum().longValue()); service.shutdown(); }}

classAccumulator{

privateAtomicLong sum = newAtomicLong( 0L);

privateCountDownLatch c;

publicAccumulator(CountDownLatch c){

this.c = c; }

AtomicLong getSum(){

returnsum; }

publicvoidadd(longk){ sum.addAndGet(k); }}

运行结果:

05e9dd96edc9c72e19920b3ebf5bb51c.png

从结果来看,时间差距还是挺大的。

END

有兴趣的读者还可以用Go语言实现程序来比较。虽然Java在执行时间上落败,但这只是语言特性的原因。从解决实际的高并发问题来看,Java还有第三方库和框架可用。

d6153b8afd0e85bdf9f843623fbb0db4.png

2ce8a2f3c00f079d1705c5646b16f893.png

bc853d98b0cf6ec8074edf02e9a3e03e.png

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值