浅谈.NET下的多线程和并行计算(十四)并行计算前言
之前的文章中我们介绍了如何在.NET下运用相关类库进行多线程编程的基础,我们知道.NET 4.0已经正式推出了,带来的重要特性是并行库。本文就谈谈对并行计算的一些理解和看法。并行计算不是一个很新的概念,其实它就是通过多线程把同一个任务分割成多个子任务并行的执行的过程。.NET 4.0并行库不但提供了这方面的支持,而且还封装了多线程开发的各种场景,使得我们不需要依赖Thread/同步基元等“底层”的对象就可以进行多线程开发。没有.NET 4.0的并行计算库我们同样可以进行并行计算,只不过我们需要手动分割任务所依赖的数据结构和算法,放到不同的线程中去,然后再使用线程同步的方法来统一汇总和处理这些线程的执行结果。之所以会需要并行计算是因为随着我们电脑的CPU不在是升级频率而是横向扩展核心,我们的程序也就达不到随着CPU的Scale-out而Scale-up的能力,因此,需要调整程序的逻辑使得一段原本不分割的任务也分割成多个片段同时执行,这样就可以利用到多个核心从而提供程序的性能。
微软每一次推出一个新的框架都会有很多宣传,但我们并没有从这些宣传中看到一些建议,在什么时候我们不应该去使用这个框架。在Linq to sql推出之后,很多人开始使用并放弃了存储过程,但在使用的时候毫不关心背后框架做了什么,因为微软的东西实在是太容易使用了,不了解ORM的人可以很方便的学会使用Linq to sql:
1) 随便把数据库访问包在循环中,或者是GridView类似控件的ItemDataBound类似循环触发事件的方法中进行数据库访问操作,导致一个页面上百个上千个数据库连接,在以前使用存储过程的时候谁也不会这么干。
2) 即使读取一个字段一条记录也把所有字段,以及行所关联的字表的数千行记录都取出来。
这就导致Linq to sql变成一个危险的产品,因为在没有使用前,古老的方式让大家都不会犯错误,一旦变成自动化了,大家就很容易犯错误。其实我想说的是.NET 4.0的并行框架也可能会这样,为什么这样说呢? 其实,对于一个Windows应用程序的程序员来说,即使没有.NET 4.0并行库,他们也非常清楚怎么去使用线程,进行多线程的编程,.NET 4.0并行库的到来只是简化了多线程编程方式,以及让我们更容易进行并行计算。我想说的是,如果程序需要优化需要利用多核的话,在并行库到来之前他也就这么干了。但对于ASP.NET程序员来说就不一样了,很多ASP.NET程序员在.NET 4.0并行库或并行计算这个词出现之前没接触过多线程开发,也很少在网站中直接去使用线程,看到.NET 4.0并行库之后很容易以为这是提高性能的一个良方。而且也确实很容易使用,我们只要加几行代码就可以让我们的循环遍历变成一个并行的行为,就可以让我们的同一个方法内的不同代码段在多个线程中并行执行。
其实,对于ASP.NET程序来说这种做法不一定能提高性能,可能会降低性能,甚至会导致网站瘫痪,很可能会造成很多莫名其妙的BUG。主要原因有两点:
1) 之前没有多线程背景,由于太容易使用了,盲目把程序改造成并行程序,但看不到其中潜在的问题。
2) WEB程序本身就是处于一个多线程环境中的,和Windows程序不一样,我们的用户不是一个,我们的程序已经由WEB服务器的线程池中的若干线程同时执行了。换一句话说,WEB应用程序即使一个页面从头到尾从处理UI到读取数据到格式化UI只是一个线程的话,我们同样能充分利用CPU资源,利用到多核,因为操作系统会把不同的线程调度到不同的核上去。
现在就这两点问题进行一下展开,首先是并行计算(多线程)会有哪些潜在问题?
1) 多个线程共享相同的内存分配,很典型的对值进行累加,如果多个线程同时访问的话累加还准确吗?
2) 过多的线程,即使不存在共享内存,对于一百个一个短时间的操作使用一百个新线程执行的话最终运行的时间很可能大于在一个线程中循环顺序执行这个操作一百次。
3) 即使我们自己的程序是线程安全的,在多线程环境下调用.NET类库或其它类库的方法是不是线程安全的?即使是线程安全的,我们还要意识到一点,如果它本来就采用锁方式确保线程安全的话即使我们多线程去调用这个方法也不能带来性能的提升。
4) 多线程操作UI的问题。
5) 互相等待或死锁的问题。
6) 调试/平台适应等问题。
第二个问题是,ASP.NET 应用程序或说WEB应用程序是否应该使用并行库的问题?
1) 如果我们的操作涉及到IO(磁盘/数据库/网络),操作的时间长并且可以分割,那么我们可以使用多个线程来进行一个大操作,或让多个操作同时执行,加快主线程完成操作的时间。加快时间不但能带来用户体验上的改善,而且能提高系统吞吐,可以想一下,同样的负载,线程池100个工作线程(等IO),100个IO线程好一点还是25个工作线程,100个IO线程好一点?
2) 如果我们的操作很多是CPU绑定的那么要看情况了,如果连接数很高,并且我们的操作都是很短的话,使用并行效果并不会很好,因为我们的CPU没闲着,已经在不断处理任务,线程池也打开了任何线程,再把一个线程能做的事情分成几个线程去做,加大了线程切换的负担,加大了线程池需要使用的线程,加大了系统负担也就减少了吞吐。只有在我们的程序比较复杂并且连接很少(比如是内部的ERP),甚至类似Windows程序的情况下,使用并行才会有好处,增加CPU利用率,提高执行速度。
3) 还要注意一点,我们在开发类库的时候如果知道类库将来的使用群体可能是ASP.NET应用程序,那么我们要慎重考虑是不是需要在类库中引入大量的并行度非常高的操作。
4) 有的时候还是要使用测试数据做依据,不能想当然,比如我们可以想到使用多线程同时下载10个网站的数据会提高效率,那么我们也会理所当然认为使用10个线程访问数据库更新第i*100(i=0-9)条数据会比串行更新1000条数据快一点。但是我们清楚数据库在背后会有什么锁操作吗?
我想说的是,在请求很少甚至只有一两个请求的情况下确实可能会提高速度,但是请求多了谁说的清楚呢,比如在一条不宽的传送带上并行传送我们的行李,分成四堆(四道),如果只有一个人的行李需要传送可能是快一点。但是如果是一千人呢,每个人的行李都铺开传还是每个人的行李都只占用一条道快?难说!对于大量访问的ASP.NET应用程序来说还是慎重点吧,咱一个请求只用一个工作线程一个IO线程够了。
不管怎么说,.NET 4.0的并行库给我们提供了工具,怎么去用好它合理使用,还需要根据需求来判断。况且并行库除了之前说了数据并行,任务并行之外,还提供了很多适合多线程环境的集合数据结构(在这之前要实现一个Lock-Free性能优良的适合多线程的数据结构也不是这么简单的事情)。
最后引申一下,并行计算是否能不局限于使用CPU,使用GPU呢?http://www.infoq.com/cn/news/2010/05/Brahma
可否把整个方法指令栈和数据栈作为参数传递给多个服务器,让服务器处理完之后返回结果呢?