二维码 短网址 cnBeta

多线程学习之路(1)-简介及线程锁

参考:

http://blog.csdn.net/hll814/article/details/50816268

http://blog.csdn.net/blues1021/article/details/44336835

http://www.cnblogs.com/goody9807/archive/2010/06/17/1759645.html

http://blog.chinaunix.net/uid-26946560-id-3438795.html

一、通俗解释

1.多线程用于堆积处理,就像1个大土堆,1个推土机很慢,那么10个推土机一起来处理,当然速度就快了,不过由于位置的限制,如果20个推土机,Name推土机之间会产生相互避让,相互摩擦,相互拥挤,反而不如10个处理的好,所以,多线程处理,线程数要开的恰当,就可以提高效率。

二、典型应用

1.tomcat:内部采用多线程,上百个客户端访问同一个web应用,tomcat接入后把后续的处理扔给一个新的线程来处理,这个新的线程最后调用到我们的servlet程序,比如doGet或者doPost方法。如果不采用多线程机制,上百个人同时访问一个web应用的时候,tomcat就得排队串行处理了,那样客户端根本是无法忍受那种访问速度的。

三、使用情景

1.用户需要同时得到多个反馈;

  例如下载过程中进度条的改变,读取文件的时候显示结果。

2.提高程序执行性能,提高CPU使用率;

  多线程的主要处理大量的IO操作或者需花费大量的时间等等,比如读写文件,网络数据接受,视频图像的采集等操作缓慢的情形和需大幅度提高性能的程序中使用。

  但也不是都需要使用多线程,因为线程过多一般会导致数据共享问题,多线程切换也是会影响性能的,所以一般不须采用多线程的不用多线程效果更好

四、线程锁

  锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要呢?

      1)只有共享资源才需要锁定
      只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。

      2)多使用lock,少用Mutex
      如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清 楚的了解到他们的不同和适用范围。

      3)了解你的程序是怎么运行的
      实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁定,当然对于ASP.NET中的Application对象中的数据,我们就要考虑加锁了。

      4)把锁定交给数据库
      数据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。

      5)业务逻辑对事务和线程安全的要求
      这条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲一些性能,和很多的开发时间来做这方面的工作。而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一, 对结果无伤大雅的情况下,我们就可以不用去管它。

五、线程锁实例(C语言)

  假设我们在一个程序中有一个全局变量cnt,初始值为0,接下去我们创建了两个线程,完成的功能都是在一个循环中对这个变量进行+1操作,想象一下在这两个线程操作完成后会出现什么状况。

    int cnt=0;
    void *thread(void* value)
    {
         int max = *((int*)value)
     
         for(int i=0;i<max;i++)
         {
               cnt++;
         }
         return NULL;
    }

   假设我们这里的max为10000,那么我们想要得到的结果的结果当然是20000,可是在执行之后结果并不是我们所期望的20000,而是一个小于20000的值。为什么会出现这个现象呢?
  这里就是我们为什么需要对线程进行同步了。
  因为在C语言的层面来说,cnt++就是一条语句,实际上我们在心里默认把它当作了一个原子操作,事实上,就这么一条操作语句,在汇编代码中是分三步执行的:
1)、将这个值从内存中取出来,放入一个寄存器中;
2)、将寄存器中的值进行+1操作;
3)、将寄存器中的值放入内存中去。

  因为对与多线程来说我们不知道何时会执行哪个线程,所以执行的顺序是不可知的。我们所想的是先让一边执行完,然后再开始执行另外一边。
现在我们不妨将这个问题极端化,也就是两线程交叉执行,假设左边的执行线程为A,右边为B,假设A先执行,A从内存中取出cnt的值,那么现在在R1里的值为0,接下去,A线程被B线程打断,A停止执行,B开始执行,B又从内存中取出cnt的值,现在在R2中的值也为0。然后又轮到A执行,进行加1操作,则R1为1,接下去轮到B执行,进行加1操作,则R2为1。然后A将值写回到内存中,B也将值写回到内存中。这次我们知道内存中的值为1而并非我们所期望的2。
  那么怎么能让它进行正确的执行顺序呢?同步,可以用加锁来完成同步操作。

    for(int i=0;i<max;i++)
    {
        P(&mutex);
        cnt++;
        V(&mutex);
    }

   在对cnt加1的操作时,对这个操作加锁,这就意味着当下只有这一个线程执行这个操作,其它的线程都得等在外面,等这个线程解锁出来,其他的线程才可以有机会进去。
加锁之后我们再来看看上面的那张图的执行过程,也假设是在一个极端的情况:
A先加锁,然后完成那三个步骤(因为此时只有它一个线程有操作的权限),解锁;现在内存中的值为1,A加锁,然后一样完成三个步骤,解锁;现在内存中的值为2。与所期望的相同。当然了,对于加锁的问题还要防止出现死锁现象,这里就不讨论了。

posted @ 2017-02-17 16:24  何苦而乐  阅读(156)  评论(0编辑  收藏  举报
返回顶部