深入浅出多线程系列之十二:双向信号和竞赛

本文探讨了在多线程环境中使用Monitor.PulseAll进行同步时遇到的问题,并提出了一种改进方案,确保主线程与worker线程之间的正确同步。

双向信号和竞赛(Two-Way Signaling and Races)

 

Monitor.Pulse方法的一个重要特性是它是异步执行的,这意味着调用pulse方法并不会阻塞自己等待Monitor.Pulse返回。如果任何一个线程在pulsed 对象上等待,它是不会阻塞的,换句话说,调用Monitor.Pulse对程序不会有什么作用,你可以认为Monitor.Pulse方法被忽略了。

这样Pulse提供了一个单向通信:一个 pulsing线程悄悄的向一个waiting 线程发送信号。

Pulse并不会返回一个值来告诉你waiting线程是否收到信号。

 

但是有时候我们需要知道waiting线程是否受到信号,例如下面的例子:

 

复制代码
class Race
    {
        
static readonly object _locker = new object();
        
static bool  _go;

        
public static void MainThread()
        {
            
new Thread(SaySomething).Start();

            
for (int i = 0; i < 5; i++)
            {
                
lock (_locker)
                {
                    _go 
= true;
                    Monitor.PulseAll(_locker); 
//通知等待的队列
                }
            }
        }
        
static void SaySomething()
        {
            
for (int i = 0; i < 5; i++)
            {
                
lock (_locker)
                {
                    
while (!_go) Monitor.Wait(_locker); //如果_go 为false,那么开始阻塞。
                    _go = false;
                    Console.WriteLine(
"Wassup?");
                }
            }
        }
    }
复制代码

期待的输出:

Wassup?

Wassup?

Wassup?

Wassup?

Wassup?

 

实际的输出:

Wassup? (线程等待)

 

SaySomething方法中,for循环执行到while,此时_gofalse,所以Monitor.Wait开始等待。在MainThread中,for循环设置_gotrue。然后PulseAll.但是PulseAll方法是异步的。

所以在SaySomething线程被唤醒前,mainThread中的for循环可能已经执行完毕。所以SaySomething方法中的第一个Wait线程收到消息词是_gotrue,所以往下执行,再次将_go字段设置为false。输出”Wassup”,但是下次循环由于_gofalse,所以需要再次wait.所以实际的输出打印了一个Wassup,然后开始等待。

 

我们需要主线程在每一次迭代中如果worker仍然在执行上一个任务,那么主线程阻塞。等到worker执行完毕,那么主线程恢复执行,然后执行迭代。

 

我们可以增加一个_ready 标志,从而控制主线程在设置_go 标志之前worker线程已经ready了。也就是说主线程在设置_go之前,会等待worker完成任务,然后等待workerready设为true,当workerready设置为true后,通过pulse来通知主线程。

 

复制代码
class Race
    {
        
static readonly object _locker = new object();
        
static bool _ready, _go;

        
public static void MainThread()
        {
            
new Thread(SaySomething).Start();

            
for (int i = 0; i < 5; i++)
            {
                
lock (_locker)
                {
                    
while (!_ready) Monitor.Wait(_locker); //如果worker的ready为false,则等待worker。
                    _ready = false//重置标志

                    _go 
= true;
                    Monitor.PulseAll(_locker);
                }
            }
        }
        
static void SaySomething()
        {
            
for (int i = 0; i < 5; i++)
            {
                
lock (_locker)
                {
                    _ready 
= true//将ready设置为true
                    Monitor.PulseAll(_locker); //通知主线程,worker已经ready了,可以执行任务了。

                    
while (!_go) Monitor.Wait(_locker);
                    _go 
= false;
                    Console.WriteLine(
"Wassup?");
                }
            }
        }
    }
复制代码





本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/06/02/2060872.html,如需转载请自行联系原作者
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值