论并发程序控制与天通苑交通阻塞之关系(原创)

         今天上班,比平时要早20分钟起床(因为怕堵车)。可是等做着751路巴士到天通苑时,不
愿看到的情象还是出现了。只见去往立水桥方向道路(并排4 车道)在天通西三区就开始排大队。
看着大家都下车步行四站地去立水桥做城铁,马路上人流黑压压一片。虽然我在的公司对准时上
班打卡要求不那么严,但如果经常晚到本人也觉得不好意思了。但因为上周刚加完班,这周又要
上正常班,感觉比较累所以就放弃了步行到城铁的想法,正当在车上座着无事可做时,一个想法
忽然进入脑海。因为很长时间没写随笔了,正好在路上想想写点什么好。而眼前的情况不禁让我
浮想连篇,这就是写这篇文章的初衷。

    其实把并发程序开发与道路交通“生拉硬套”在一起的确很憋扭。但也有一些相似之处。下
面我就把一些共同点乱谈一通。

    1. 并行4车道 vs 并行四个进程

    2. 马路红绿灯 vs lock锁对象

    3. 车道里的汽车 vs 正在运行的线程

    4. 十字路口东西南北同时阻塞 vs  死锁

    5. 交通警察 vs 解除死锁

    6. 年底要通的城铁  vs 多核(N核)的服务器(集群)

    先说第一个,因为本人做web开发相对时间长一些,所以就拿iis中的web园举例。
    每个web园其实就是一个进程,这一点相信大家都认可吧。那好,当http请求到来时,iis
通过进程轮循的方式处理这个请求,并创建相应的线程执行相应的代码并处理相关的资源。这些进
程互不影响,即使一个进程crash掉,其它的进程依然可以正常执行自己的逻辑。而进程中的资源也
是供的运行在本进程中的线程所共享的。比如说我们在c#中所定义的static类型的变量或函数就是一
种资源,我们可以用static 对象去lock代码边界,以控制线程的并发访问。而并行的4条车道,每条
车道就是一个进程,在本车道中的车辆(即线程)拥有当前道路的行驶权(资源)。
    特殊情况:同一车道两车共占,这里只有让其中一辆车退后(排队)才能让交通通畅,但总有不
自觉的小轿车司机在从旁边的车道一把轮就掰过来抢行(这里可以理解为跨进程访问资源),这里只
有两种情况,一是其意图得逞,抢了其它车的行驶权。另一种就是刮上了另一辆车,这下傻了吧。只
能停下来等警察叔叔来解决了。同时后面的车也不停的按喇叭,必竟大家都急着上班呀。这时就是一
种进程间的阻塞了。

    
    再说第二个相似点。
    车辆到了红绿灯下司机都要看一下当前是不是绿灯,只有是绿灯才能通过。我们可以把十字路口
想像成共享的资源,所有的程序在访问这种资源时都要考虑并发的问题,否则程序的运行结果会出现
很难预料的结果。有关这方面的资料网上很多,这里就不举例了,以免让人有凑字之感。
    可以想像如果没有红绿灯路口会是什么样子。交通的大阻塞很大程度上是从十字路口开始的。但
问题是即使有了红绿灯也时常出现阻车的情况,必竟造成交通阻塞的问题有着多方面的因素。这也就
像是我们的代码,明明加了lock锁代码,可是还会有时出现程序并发执行错误,这很有可能是在程序
的其它方向存面这样或那样的问题造成的。
    另外在使用lock时,我们要少锁定一些代码(主要是那些不访问共享资源的代码,下面用normal
code标注),如下:
public functionA()
{
    normal code ...

    lock(static object)
    {
         visit share souce code ...
    }

    normal code ...
  

而不要是这样:
public functionB()
{
    lock(static object)
    {
       normal code ...
 
       This is visit share souce code ...

       normal code ...
    }
   

    这主要是利于并发支持。必定lock代码会让别的线程处于等待直到当前线程运行这个函数结束
才会release lock。
    这就好比把红绿灯放在离十字路口1000米的地方,司机在1000米外就开始排队等待信号一样。
这无疑是非常荒唐可笑的。
    还有就是红绿灯的个数问题,如果灯多了,马路上排队的情况肯定要成正比。同样在程序中锁
太多也会影响系统性能。所以程序员要全面权衡这里面的利益得失,以使程序即能承受大并发的访
问同时又能让程序在我们的控制下正常执行。
    不幸的是天通苑这个路上红绿灯的个数相对比较多,平均一站地一个。导致车辆刚一起步没开
几米就匆匆刹车排队。郁闷中.......

   
    第三个相似点
    在应用程序中的线程都是在进程运行的, 这就像是在车道里行驶的汽车, 每个线程都有各自的
任务。而每辆汽车也有各自的目的地。不管是上班也好,还是出去旅游也罢,快速安全是出行的基
本要求。就像线程运行也是这样,快速运行当前程序代码,并释放当前占用的资源(因为其它线程
正在等待这些资源的释放以便运行各自的代码)。

    第四个相似点
    十字路口东西南北同时阻塞,我想住在大城市(特别像北京)都会经常遇见这种情况。有时交
警来了也要耐心疏导半天。基本上一天上班路上只要遇上一两起,这个月的全勤奖就要泡汤了,但
又能有什么办法呢。而并发程序中的死锁同样也是这样让人头痛,难以预料和处理的,对于这种情
况基本上要靠经验和测试工具的帮助了。
    考虑如下死锁的情况:
 
public deadlockfunctionA()
{
   lock(A)
   {
     ...          //代码段1
     lock(B)
     {
     ....         //代码段2
     }    
   }
}

public deadlockfunctionB()
{
   lock(B)
   {
     ...         //代码段3
     lock(A)
     {
     ....        //代码段4
     }    
   }
}
    可以看出这是一个典型的死锁问题,因为这里假设有线程1开始执行deadlockfunctionA函数,
并已lock(A),但当运行完代码段1时, 这时有另一个线程(这里暂称为线程2)开始运行deadlock-
functionB(), 并已lock(B), 那线程1只能处于等待状态直到线程2 release lock(B), 但问题在
于这时线程2 的运行前提是要线程1 release lock(A) , 这下好了,两个线程谁也不让,都把希
望寄托给对方, 让对方先释放自己正在申请的资源。这时死锁就不可避免的出现了。
    这种代码大家还是能够很容易避免的,只要两个线程以相同的顺序加锁就可行了。但问题是
实际开发中问题会比这个例子要复杂的多,比如:
public functionD()
{
   lock(a)
   {
     ....
     fuctionF();
   }
}

public functionF()
{
   lock(b)
   {
     ...
   }
}

     这时是否还会发生死锁就很难说了,只是统观整个应用程序代码来进行综合判断了。
     我们再回到“ 十字路口东西南北同时阻塞”这个话题上来, 可以把来自东西南北的汽车
想成阻塞在一起的线程,东西方向的司机都希望南北方向的司机把车让一下,以便自己能过去,
同样南北方向的司机也是这么想的。所以大家互不相让,导致路口堵的越来越死。最后只能让
交警来帮忙。


    第五个相似点
    交通警察"太有才了", 好的交警就像是好的程序员,他们能够很快解决交通阻塞的原因,
并很快解决路口的阻塞状况。但一些素质差的交警表现的就很难说了,这些人只要不是造成阻
车的根源我就要烧高香了。同样优秀的程序员能够很快找出代码中的 bad smell, 并找出正确
的方法降低甚至解决死锁的发生。

    说了半天死锁的问题。再谈谈购如何增强程序的并发性能,这也是最后一个相似点。
    购买性能强大的服务器基本上是通常的解决方式之一,前提是我们的代码中没有(或非常
少)死锁的发生。并且并发功能也到了目前(个人或行业)的技术极限。正所谓“巧妇难为无
米之炊”, 当程序已无优化余地那也只是求助于硬件了。真希望年底城铁能如期完工,到时相
信会分散大量的客流,必定这类设施才是解决北京市公共交通问题的根本。

    好了,不忽悠大家了,仅以此文帮助自己和大家消谴堵车的时间,让大家有个好心情。顺便说
一句,本人强烈建议把奥运会运行员公寓迁到天通苑,在这个“堵神社区”里面,当别的国家运动
员因为堵车而未能及时到达比赛场馆而失去参赛资格。而我国的奥动健儿如刘翔独单一人让在百米
跨栏的起跑线上,我想就是他老人家倒着跑也能稳拿金牌了:)
 
    不说了,就到这里了......

 

   

  

   


 

posted @ 2007-04-13 12:49  代震军  阅读(3280)  评论(8编辑  收藏  举报