我为什么喜欢用C#来做并发编程

硅谷才女朱赟(我的家门)昨天发了一篇文章《为什么用 Java —— 关于并发编程》,让大家学习了Java中如何进行并发编程的一些基本知识。作为一个将近15年的.NET程序员,我觉得有必要给大家补充介绍一下C#进行并发编程的知识(当然不会太深入讲解)。这篇文章无意进行技术比较,毕竟技术只是工具(大同小异,各有千秋),主要还是看用工具的人。

并发(英文Concurrency),其实是一个很泛的概念,字面意思就是“同时做多件事”,不过方式有所不同。在.NET的世界里面,并发一般涉及如下几个方面:

  1. 多线程编程(已过时,不介绍)
  2. 异步编程
  3. 并行编程
  4. 响应式编程
  5. 数据流编程

为了支持以上编程,.NET提供了很多基础功能,比如:委托,匿名函数,Lambda表达式,线程池,Task模型,支持并发的集合(线程安全集合和不可变集合) ,调度器,同步功能。在这里,就不对这些内容进行介绍了,大家可以自行搜索学习。另外,对于Actor模型,.NET中也有支持,但我不认为它属于语言/运行时层面的并发,它更像架构层面的并发,我最后会简单介绍。


1,异步编程

异步编程就是使用future模式(又称promise)或者回调机制来实现(Non-blocking on waiting)。

如果使用回调或事件来实现(容易callback hell),不仅编写这样的代码不直观,很快就容易把代码搞得一团糟。不过在.NET 4.5(C# 5)中引入的async/await关键字(在.NET 4.0中通过添加Microsoft.Bcl.Async包也可以使用),让编写异步代码变得容易和优雅。通过使用async/await关键字,可以像写同步代码那样编写异步代码,所有的回调和事件处理都交给编译器和运行时帮你处理了。

使用异步编程有两个好处:不阻塞主线程(比如UI线程),提高服务端应用的吞吐量。所以微软推荐ASP.NET中默认使用异步来处理请求。

要详细了解异步编程,可以参考官方文档:https://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx和《Async in C# 5.0》这本书。另外,在这个官方文档中,微软还特意把异步编程分作了3种不同的模型:基于任务的模式(TAP)就是我上面推荐的这种,基于事件的模式(EAP)和异步编程模型(APM)我上面不推荐的事件和回调。

2,并行编程

并行编程的出现实际上是随着CPU有多核而兴起的,目的是充分利用多核CPU的计算能力。并行编程由于会提高CPU的利用率,更适合客户端的一些应用,对于服务端的应用可能会造成负面影响(因为服务器本身就具有并行处理的特点,比如IIS会并行的处理多个请求)。我自己使用并行编程最多的场景是之前分析环境数据不确定度的时候,使用并行的方式计算蒙特卡洛模拟(计算上千次之后拟合),当然后来我使用泰勒级数展开来计算不确定度,没有这么多的计算量就无需并行了。当然在计算多方案结果比较的情况下,还是继续使用了并发计算。

在.NET中,并行的支持主要靠.NET 4.0引入的任务并行库和并行LINQ。通过这些库可以实现数据并行处理(处理方式相同,输入数据不同,比如我上面提到的应用场景)或者任务并行处理(处理方式不同,且数据隔离)。通过使用并行处理库,你不用关心Task的创建和管理(当然更不用说底层的线程了),只需要关注处理任务本身就行了。

具体的用法还是参考官方文档:https://msdn.microsoft.com/en-us/library/dd460693(v=vs.110).aspx,当然《Parallel Programming with Microsoft .NET》这本书也行。

3,响应式编程

响应式编程最近成为了一个Buzzword,其实微软6年前就开始给.NET提供一个Reactive Extensions了。一开始要理解响应式编程有点困难,但是一旦理解了,你就会对它的强大功能爱不释手。简单来说,响应式编程把事件流看作数据流,不过数据流是从IEnumable中拉取的,而数据流是从IObservable推送给你的。为什么响应式编程可以实现并发呢?这是因为Rx做到线程不可知,每次事件触发,后续的处理会从线程池中任意取出一个线程来处理。且可以对事件设置窗口期和限流。举个例子,你可以用Rx来让搜索文本框进行延迟处理(而不用类似我很早的时候用个定时器来延迟了)。

要详细了解Rx最好的方式就是浏览 IntroToRx.com 这个网站,当然还有官方文档:https://msdn.microsoft.com/en-us/data/gg577609

4,数据流编程

数据流(DataFlow)编程可能大家就更陌生了,不过还是有些常用场景可以使用数据流来解决。数据流其实是在任务并行库(TPL)上衍生出来的一套处理数据的扩展(也结合了异步的特性),TPL也是处理并行编程中任务并行和数据并行的基础库。

望文生义,TPL DataFlow就是对数据进行一连串处理,首先为这样的处理定义一套网格(mesh),网格中可以定义分叉(fork)、连接(join)、循环(loop)。数据流入这样的处理网格就能够并行的被处理。你可以认为网格是一种升级版的管道,实际上很多时候就是被当作管道来使用。使用场景可以是“分析文本文件中词频”,也可以是“处理生产者/消费者问题”。

参考资料当然也是官方文档:https://msdn.microsoft.com/en-us/library/hh228603(v=vs.110).aspx

5,Actor模型

Scala有Akka,其实微软研究院也推出了Orleans来支持了Actor模型的实现,当然也有Akka.NET可用。Orleans设计的目标是为了方便程序员开发需要大规模扩展的云服务, 可用于实现DDD+EventSourcing/CQRS系统。

官方网站是:http://dotnet.github.io/orleans/,善友也有介绍:http://www.cnblogs.com/shanyou/p/4295523.html


那么,我为什么喜欢使用C#来做并发编程呢?显而易见,有上面这些唾手可得的工具,使用C#同样可以轻易开发并发程序。