C#线程同步之Lock, Monitor

上一篇 / 下一篇  2014-01-23 14:57:48 / 个人分类:C#

    锁机制的线程状态可分为三类:
1. 占有锁的线程:每个时刻只有一个
2. 就绪队列:当当前占有锁的线程释放锁的时候,只有在就绪队列中的线程才有机会占有锁
3. 等待队列:没有资格占有锁,只有被移动到就绪队列中的才有机会
    Lock——锁住代码块,确保不被其他线程中断,其他线程只能在原地等待(不是跳过去),最主要的是Lock的对象选择问题:
1. 不能Lock值类型和字符串,如:Lock(1)或Lock("string")或Lock(object(1))都是不行的
2. 不能Lock值为null的对象
3. 不要Lock公有类型的对象实例
4. Lock(this)只对当前的对象实例有效,并且Lock了该对象实例的所有方法和成员
5. 建议Lock私有类型的静态对象
    Monitor——主要有以下几个方法:Enter, Wait, Pulse, Exit
Enter和Exit:Enter是获取锁,Exit是释放锁。如果其他线程已对该对象obj执行了Enter,并且未执行Exit,则当前线程被阻塞。Exit调用的次数必须和Enter调用的次数相同,才会释放该锁。如果释放了锁,并且其他线程都处于等待队列中,则它们不会自动移动到就绪队列中的,必须调用Pulse或PulseAll。
Wait:当前线程释放锁,并将自己移到等待队列中,阻塞当前线程,直到重新获取该锁。从阻塞到重新获取该锁的过程中,当前线程一直在原地等待。Wait方法还有个接口为Wait(object, timeout)。timeout单位为毫秒,指定限定时间。如果在限定时间内,当前线程重新获取该锁,则返回true,并且继续执行下去。如果当前线程在限定时间内没有重新获取该锁,则当前线程移到就绪队列中,等重新获取该锁后,返回false,并且继续执行下去。
Pulse:只有当前占有锁的线程可以发出Pulse信号,Pulse将等待队列中的第一个线程(若有)移到就绪队列中,Pulse不释放锁。PulseAll()方法可将所有等待队列中的线程移到就绪队列中。
Wait和Pulse, PulseAll方法必须在同步的代码块中调用,以防止死锁。Monitor可以完全实现Lock的方法:
Lock(obj);
// do something
等价于
try
{
    Monitor.Enter(obj);
    // do something
}
catch
{
    // do something
}
finally
{
    Monitor.Exit(obj);
}
    简单总结一下:一开始,当一个线程尝试Lock一个对象的时候,该线程在就绪队列中。一旦没有线程占有该对象,该线程就从就绪队列中出来占有该对象。为了其他线程的同步,占有对象的线程可以暂时释放该对象,将自己移到等待队列中,这就是Monitor.Wait()。此时,就绪队列中的第一个线程可占有该对象。等待队列中的第一个线程只有接受到Monitor.Pulse()后,才会从等待队列中移到就绪队列中。
    下面举一实例:
public void Main()
{
    CThread ct = new CThread(0);
    ct.Run();
    Console.ReadLine();
}

class CThread
{
    public CThread(int n)
    {
        _count = n;
    }

    public void Run()
    {
        Thread thread_produce = new Thread(new
            ThreadStart(produce));
        Thread thread_consume = new Thread(new
            ThreadStart(consume));
        thread_produce.Start();
        thread_consume.Start();
    }

    private void produce()
    {
        for (int i = 0; i < 10; i++)
        {
            try
            {
                Monitor.Enter(_locker);
                Monitor.Pulse(_locker);
                Monitor.Wait(_locker);
                _count++;
                Console.WriteLine("生产者:" + _count);
                Monitor.Pulse(_locker);
            }
            finally
            {
                Monitor.Exit(_locker);
            }
        }
    }

    private void consume()
    {
        try
        {
            Monitor.Enter(_locker);
            Monitor.Pulse(_locker);
            while(Monitor.Wait(_locker))
            {
                if (_count != 0)
                {
                    Console.WriteLine("消费者:" + _count);
                }
                Monitor.Pulse(_locker);
                if (_count == 10)
                {
                    return;
                }
            }
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }

    private int _count;
    private object _locker = new object();
}
    上述例子实现了生产者和消费者,从1到10。总共两个线程,一个是produce,一个是consume。线程执行先后顺序有两种情况,下面就两种情况分别详说:
1. produce先占有_locker
produce获取锁——consume进入就绪队列——produce进入等待队列,释放锁——consume获取锁——produce进入就绪队列——consume进入等待队列,释放锁——_count加1——console打印“生产者”——consume进入就绪队列——produce释放锁——console打印“消费者”——produce进入就绪队列——consume释放锁——produce再次进入for循环——。。。
2. consume先占有_locker
consume获取锁——produce进入就绪队列——consume进入等待队列,释放锁——produce获取锁——consume进入就绪队列——produce进入等待队列,释放锁——produce进入就绪队列——consume释放锁——_count加1——console打印“生产者”——consume进入就绪队列——produce释放锁——consume获取锁——produce进入就绪队列——consume进入等待队列,释放锁——produce再次进入for循环——。。。




TAG:

 

评分:0

我来说两句

Open Toolbar