多线程操作

有CSDN的朋友问一个问题,“Lock关键字不是有获取锁、释放锁的功能吗?...为什么还需要执行Pulse?”
也有朋友有些疑点,“用lock就不要用monitor了”,“Monitor.Wait完全没必要”,“为什么Pulse和Wait方法必须从同步的代码块内调用?”

这些疑问很自然。在大部分情况下,lock确实能基本达到我们要求资源同步的目的,加上配合其他同步工具,比如事件(AutoResetEvent)等的应用,日常工作中确实没有太多机会需要用到Monitor.Wait和Pulse。不过,虽然较少机会用到,事实上Wait和Pulse跟lock完全不是一回事。他们提供了更细腻的同步功能,能达到lock作不来的功能。

为更好的回答和解释这些疑问,该帖将首先介绍Wait和Pulse的用途,通过一个简单例子逐条分析同步的过程;然后提供一个用轻量级的lock,Wait和Pulse来实现一个事件通知的实例;最后谈谈DotNet4对lock编译展开的一点有趣变化。

让我们首先看看MSDN对Monitor.Wait的解释(链接见注释):
释放对象上的锁并阻止当前线程,直到它重新获取该锁。...

该解释的确很粗糙,很难理解。让我们来看看它下面的备注:
同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列的引用和对等待队列的引用。

这个多少还给了点东西,现在我们脑海中想像这么一幅图画:

Assembly code

?


1

2

3

         |- 拥有锁的线程

lockObj->|- 就绪队列(ready queue)

         |- 等待队列(wait queue)

当一个线程尝试着lock一个同步对象的时候,该线程就在就绪队列中排队。一旦没人拥有该同步对象,就绪队列中的线程就可以占有该同步对象。这也是我们平时最经常用的lock方法。
为了其他的同步目的,占有同步对象的线程也可以暂时放弃同步对象,并把自己流放到等待队列中去。这就是Monitor.Wait。由于该线程放弃了同步对象,其他在就绪队列的排队者就可以进而拥有同步对象。
比起就绪队列来说,在等待队列中排队的线程更像是二等公民:他们不能自动得到同步对象,甚至不能自动升舱到就绪队列。而Monitor.Pulse的作用就是开一次门,使得一个正在等待队列中的线程升舱到就绪队列;相应的Monitor.PulseAll则打开门放所有等待队列中的线程到就绪队列。

比如下面的程序:

class Program
{
    static void Main(string[] args)
    {
        new Thread(A).Start();
        new Thread(B).Start();
        new Thread(C).Start();
        Console.ReadLine();
    }

    static object lockObj = new object();
    static void A()
    {
        lock (lockObj)               //进入就绪队列
        {
            Thread.Sleep(1000);
            Monitor.Pulse(lockObj);
            Monitor.Wait(lockObj);   //自我流放到等待队列
        }
        Console.WriteLine("A exit...");
    }
    static void B()
    {
        Thread.Sleep(500);
        lock (lockObj)               //进入就绪队列
        {
            Monitor.Pulse(lockObj);
        }
        Console.WriteLine("B exit...");
    }
    static void C()
    {
        Thread.Sleep(800);
        lock (lockObj)               //进入就绪队列
        {
        }
        Console.WriteLine("C exit...");
    }
}
T  线程A
0  lock( lockObj )
1  {
2     //...           线程B                   线程C
3     //...           lock( lockObj )       lock( lockObj )
4     //...           {                     {
5     //...              //...
6     //...              //...
7     Monitor.Pulse      //...
8     Monitor.Wait       //...
9     //...              Monitor.Pulse
10    //...           }                     }
11 }

时间点0,假设线程A先得到了同步对象,它就登记到同步对象lockObj的“拥有者引用”中。
时间点3,线程B和C要求拥有同步对象,他们将在“就绪队列”排队:
            |--(拥有锁的线程) A
            |
3  lockObj--|--(就绪队列)   B,C
            |
            |--(等待队列)

时间点7,线程A用Pulse发出信号,允许第一个正在"等待队列"中的线程进入到”就绪队列“。但由于就绪队列是空的,什么事也没有发生。
时间点8,线程A用Wait放弃同步对象,并把自己放入"等待队列"。B,C已经在就绪队列中,因此其中的一个得以获得同步对象(假定是B)。B成了同步

对象的拥有者。C现在还是候补委员,可以自动获得空缺。而A则被关在门外,不能自动获得空缺。
            |--(拥有锁的线程) B
            |
8  lockObj--|--(就绪队列)   C
            |
            |--(等待队列)   A

时间点9,线程B用Pulse发出信号开门,第一个被关在门外的A被允许放入到就绪队列,现在C和A都成了候补委员,一旦同步对象空闲,都有机会得它。
            |--(拥有锁的线程) B
            |
9  lockObj--|--(就绪队列)   C,A
            |
            |--(等待队列)

时间点10,线程B退出Lock区块,同步对象闲置,就绪队列队列中的C或A就可以转正为拥有者(假设C得到了同步对象)。
            |--(拥有锁的线程) C
            |
10 lockObj--|--(就绪队列)   A
            |
            |--(等待队列)

随后C也退出Lock区块,同步对象闲置,A就重新得到了同步对象,并从Monitor.Wait中返回...
最终的执行结果就是:
B exit...
C exit...
A exit...

参考地址:http://bbs.csdn.net/topics/380095508

时间: 2024-10-07 18:49:13

多线程操作的相关文章

iOS 数据库多线程操作

SQLite是支持多线程操作的, 需要在初始化database是做一个线程安全的config,参考下面链接,看起来比较复杂. http://www.cnblogs.com/wfwenchao/p/3964213.html 很多iOS项目中都使用FMDB这个第三方开源库,但FMDB不能在多个线程中共同一个FMDatabase对象,因为这个类本身不是线程安全的,如果这样使用会造成数据混乱等问题. 如要需要多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了. 首先用一个数据

黑马程序员-Java多线程操作

--Java培训.Android培训.iOS培训..Net培训.期待与您交流!--- Java中的线程 一个程序的运行需要启动一个应用进程,一个进程可以创建多个线程,帮助应用完成多任务操作,实现并发运行.在Java中线程是被封装成Thread类,进行多线程操作时只需要继承一个Thread类,实现自己的功能即可,然后开启此线程,或者你也可以实现一个Runnable接口,然后将其传递给Thread对象,然后再启动它. 线程的创建于启动 继承Thread 创建一个类并继承Thread类,然后实现Thr

IE exp中的多线程操作

最近在写cve-2014-0321这个漏洞的poc,分析的时候发现这流程简直就是个坑(越写越坑!!),没办法只能照着古河的思路,利用多线程进行操作.虽然知道多线程操作用window.open去创建,但调用了之后发现一直创建的都是个进程,根本就没有线程啊,简直就是个坑啊.后来无数次的尝试之后才发现,之前一直本地进行IE漏洞的调试,这样子使用window.open打开的总是一个进程,这样子也就没办法实现利用或者是占位.之后,用python架了简易的web服务,web的方式来访问,才发现window.

C#中级-常用多线程操作(持续更新)

一.前言       多线程操作一直是编程的常用操作,掌握好基本的操作可以让程序运行的更加有效.本文不求大而全,只是将我自己工作中常常用到的多线程操作做个分类和总结.平时记性不好的时候还能看看.本文参考了多篇园子里的精彩博文,在文章最后会贴出具体来源,感谢他们的无私奉献. 二.关于线程 (1) 为何使用线程: 可以使用线程将代码同其他代码隔离,提高应用程序的可靠性: 可以使用线程来简化编码: 可以使用线程来实现并发执行. (2) 进程.应用程序域以及线程的关系: 进程(Process)是Wind

python threading模块使用 以及python多线程操作的实践(使用Queue队列模块)

今天花了近乎一天的时间研究python关于多线程的问题,查看了大量源码 自己也实践了一个生产消费者模型,所以把一天的收获总结一下. 由于GIL(Global Interpreter Lock)锁的关系,纯的python代码处理一般逻辑的确无法活动性能上的极大提升,但是在处理需要等待外部资源返回或多用户的应用程序中,多线程仍然可以作为一个比较好的工具来进行使用. python提供了两个模块thread和threading 来支持python的多线程操作.通俗的讲一般现在我们只使用threading

DataTable多线程操作报错情况

最近在写一个http接口时用了DataTable这个强大的利器,接口用浏览器跑起来没任何问题.当时也没考虑并发问题,后来用一个压力测试工具做大并发测试,1000+/s次速度测试.发现程序报错了.程序报错了第一反应还是去检查代码,是不是代码出现问题.发现逻辑都是对的,然后用浏览器打开接口,发现一切OK;然后心想肯定是并发时多个线程操作导致的. 我们都知道在多线程的时候不同的线程访问同一个资源的时候,用lock方法来达到线程同步,也就是同一个时刻同一个资源只能被一个线程操作. 我开始在操作DataT

MFC的多线程操作

记得用MFC做了一个图像自动修复软件,当时没有多线程操作这一概念,由于图像修复算法比较复杂,因此,当执行图像修复时,程序就像卡死了似得而不能做其他操作.其实MFC对这种情况有一种很好地解决方案,那就是采用多线程编程技术.以图像修复算法为例,由于其本身需要耗费大量时间,因此我们可以单独开一个线程让他执行修复而不影响主程序的操作. 关于线程的一些概念,以及在VS2013下的实例: MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的

Java多线程操作

Java中的线程 一个程序的运行须要启动一个应用进程,一个进程能够创建多个线程.帮助应用完毕多任务操作.实现并发运行.在Java中线程是被封装成Thread类.进行多线程操作时仅仅须要继承一个Thread类,实现自己的功能就可以.然后开启此线程,或者你也能够实现一个Runnable接口.然后将其传递给Thread对象.然后再启动它. 线程的创建于启动 继承Thread 创建一个类并继承Thread类,然后实现Thread类的run方法,在run方法内填上自己的代码.然后创建这个自己定义类,并调用

C# 多线程操作之异步委托

标签: 多线程任务nullstringhtml工作 2012-06-29 23:00 1276人阅读 评论(0) 收藏 举报  分类: C/C++/C#/dotnet(126)  目录(?)[+] 在应届生找工作的时候,多线程操作几乎是所有的公司都会问及的一个基本问题. 这里做了一个多线程操作的总结,这里总结了通过异步委托来实现多线程操作. 定义一个委托,是创建一个线程的最简单的方法,并且异步调用它.委托是方法的类型安全的引用.同时委托还智齿异步调用方法. 委托使用线程池来完成异步任务. 当自己

多线程操作(循环往listbox中添加数据)

一.先造一个窗体,其中就开始按钮,暂停按钮,以及listbox文本框 二.当点击开始的时候,数据会无限的往listbox中加,为了防止卡住和提升效率,便造了一个新的线程来执行开始操作 namespace 线程Again { //首先要定义一个委托(委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递) public delegate void Weituo();//利用这个委托来用方法调用方法 public partial class Form1 : Form { //