读书人

分析一个线程同步代码片段优劣

发布时间: 2011-12-14 23:20:17 作者: rapoo

高手进,分析一个线程同步代码片段优劣
代码实现的功能如下:一个生产者线程,一个消费者程,生产一个产品,消费一个,再生产,再消费...
A版代码如下:
static void ProduceOneA()
{
lock (locker)
{
while (true)
{
if (isHave)
Monitor.Wait(locker);

Thread.Sleep(1000);
Console.WriteLine("生产一个");
isHave = true;
Monitor.Pulse(locker);
}
}
}

static void ConsumeOneA()
{
lock (locker)
{
while (true)
{
if(!isHave)
Monitor.Wait(locker);

Thread.Sleep(500);
Console.WriteLine("消费一个");
isHave = false;
Monitor.Pulse(locker);
}
}
}

static object locker = new object();
volatile static bool isHave = false;

static void Main(string[] args)
{
new Thread(ProduceOneA).Start();
new Thread(ConsumeOneA).Start();
}

是比较常见的lock{while(){}}结构,B版代码如下:
static void ProduceOneB()
{
while (true)
{
lock (locker)
{
if (isHave)
Monitor.Wait(locker);
Thread.Sleep(1000);
Console.WriteLine("生产一个");
isHave = true;
Monitor.Pulse(locker);
}
}
}

static void ConsumeOneB()
{
while (true)
{
lock (locker)
{
if (!isHave)
Monitor.Wait(locker);
Thread.Sleep(500);
Console.WriteLine("消费一个");
isHave = false;
Monitor.Pulse(locker);
}
}
}

注意这里变成了while(){lock{}},这两个版本的代码都能达到目的,但孰优孰劣,高手给分析下,个人感觉lock{}里的同步代码应该越短约好如B版代码,但B版的结构频繁调用lock{}又是一个问题...

[解决办法]

C# code
        void ProduceOneA()         {                 while (Interlocked.Read(ref bRun) == 1)                 {                     try                      {                                ProduceHandler.waitone();                                            Console.WriteLine("生产一个");                             ConsumeHandler.set();                       }                      finally                      {                          Thread.Sleep(1000);                      }                }        }         void ConsumeOneA()         {                 while (Interlocked.Read(ref bRun) == 1)                 {                       try                      {     ConsumeHandler.waitone();                            Console.WriteLine("消费一个");                             ProduceHandler.set();                       }                      finally                      {                          Thread.Sleep(1000);                      }                }             }         }         private ReaderWriterLock m_RWHandlers = new ReaderWriterLock();        long m_Run = 0;        AutoResetEvent m_ConsumeHandler = new AutoResetEvent(false);        AutoResetEvent m_ProduceHandler = new AutoResetEvent(true);        static void Main(string[] args)         {             Interlocked.Exchange(ref m_Run, 1);            new Thread(new threadstart(ProduceOneA)).Start();             new Thread(new threadstart(ConsumeOneA)).Start();         } 


[解决办法]
A版本是错的。示例代码中可没使用while(true)。
lock是保护其中的代码不会同时被不同线程调用。lock块中使用while(ture)???除了第1个调用的线程,其它调用这部分代码的线程会卡在lock的地方,这段代码无法实现真正的多线程同时和多线程消费。
你的ProduceOneA和ConsumeOneA都只有一个,lock事实上没有起没有任何作用,所以没报问题。

lock块中的代码确实越少越好,不过至少包住读数据、处理数据、写数据的整个过程。
另外lock放在循环体内,单个处理线程效率降低、多个线程同时处理同一功能的,总效率会提高。但是如果循环变量的同步处理得不好,也容易出现问题。

Monitor才是起协调ProduceOneA和ConsumeOneA的生产和消费的功能的对象。
isHave可以去掉。它的功能看上去与Monitor完全一梓,而这种用于自行控制线程同步的全局状态,会影响后续扩展代码应用的。
[解决办法]
我发现微软给的列子有问题
大家可以检测一下,把微软的例子考下来运行一下,如果运气比较好的话,就会发现运行两三次就会死锁一次。
我简单的分析了一下原因。如果SecondeThread先锁m_smplQueues,那么FirstThread就会陷入死锁的状态,因为只有等到SecondeThread执行Monitor.Wait(m_smplQueue, 1000),FirstThread才能执行,但是线程一永远收不到Monitor.Pulse(m_smplQueue);就会死锁在那里,因为SecondeThread等不到线程一调用 Monitor.Pulse(m_smplQueue);等待一秒钟后就推出了,所以FirstThread永远的只有等在哪里了。

大家不要太相信微软的例子,实践才是硬道理

读书人网 >C#

热点推荐