代码实现的功能如下:一个生产者线程,一个消费者程,生产一个产品,消费一个,再生产,再消费...
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{}又是一个问题...
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{}又是一个问题...
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版本才是Monitor.Wait和Monitor.Pulse的正确用法,参考
http://msdn.microsoft.com/zh-cn/library/system.threading.monitor.pulse.aspx
lock是保护其中的代码不会同时被不同线程调用。lock块中使用while(ture)???除了第1个调用的线程,其它调用这部分代码的线程会卡在lock的地方,这段代码无法实现真正的多线程同时和多线程消费。
你的ProduceOneA和ConsumeOneA都只有一个,lock事实上没有起没有任何作用,所以没报问题。lock块中的代码确实越少越好,不过至少包住读数据、处理数据、写数据的整个过程。
另外lock放在循环体内,单个处理线程效率降低、多个线程同时处理同一功能的,总效率会提高。但是如果循环变量的同步处理得不好,也容易出现问题。Monitor才是起协调ProduceOneA和ConsumeOneA的生产和消费的功能的对象。
isHave可以去掉。它的功能看上去与Monitor完全一梓,而这种用于自行控制线程同步的全局状态,会影响后续扩展代码应用的。
然后lock(m_objForLock)...
Monitor.Wait 方法 (Object)
释放对象上的锁并阻止当前线程,直到它重新获取该锁。lock块中使用while(ture)怎么了?
跑到Wait的时候,locker就被释放了
谁说必须要等到第一个线程退出lock,其他线程才能获得locker的?
Msdn上没有用while(true),是因为生产/消费有限个
而A版本用while(true)是因为要无限生产/消费而已
try
{
Monitor.Enter(locker);
…;
}
finally { Monitor.Exit();}由此看来性能方面,还是把while放在lock里面好一些。
Monitor.Pulse(locker);
提到if里面更清晰一些呢?变成if (isHave)
{
Monitor.Pulse(locker);
Monitor.Wait(locker);
}因为:
Monitor.Wait释放对象上的锁并阻止当前线程,直到它重新获取该锁(相当于先调用 Monitor.Exit 然后再调用 Monitor.Enter)。
Monitor.Pulse通知下一个等待线程在当前线程释放锁后可以获得锁。
所以我觉得Monitor.Pulse放在Monitor.Wait之前,代码可读性更好一些,避免在这里出bug的可能性。
大家可以检测一下,把微软的例子考下来运行一下,如果运气比较好的话,就会发现运行两三次就会死锁一次。
我简单的分析了一下原因。如果SecondeThread先锁m_smplQueues,那么FirstThread就会陷入死锁的状态,因为只有等到SecondeThread执行Monitor.Wait(m_smplQueue, 1000),FirstThread才能执行,但是线程一永远收不到Monitor.Pulse(m_smplQueue);就会死锁在那里,因为SecondeThread等不到线程一调用 Monitor.Pulse(m_smplQueue);等待一秒钟后就推出了,所以FirstThread永远的只有等在哪里了。大家不要太相信微软的例子,实践才是硬道理
10楼的建议很好。
8楼的代码比A版的代码更好,效率高而且代码清晰。我是从C++转到C#的,所以更喜欢用Mutex,Event这些和Win32近似的类,不喜欢Monitor,我觉得Monitor和lock容易使初学者犯错。
A版、B版和8楼的代码都存在一个问题:如果在消费者消费产品之前,生产者已经生产多于1个的产品,比如说已生产了2个,则消费者只能消费第1个产品,然后就等待生产者再生产。实际上这时已经有产品了,就是第2个产品。而消费者必须等待生产品产生第3个产品时才能继续消费。也就是说,第2个产品永远不会被消费!
这个问题可以通过将A代码的bHave改成一个计数器,或将8楼代码的bRun改成一个计数器,来解决。
10楼的建议很好。A代码加上10楼的建议就比较完美了。
8楼的代码比A版的代码更好,效率高而且代码清晰。我是从C++转到C#的,所以更喜欢用Mutex,Event这些和Win32近似的类,不喜欢Monitor,我觉得Monitor和lock容易使初学者犯错。
A版、B版和8楼的代码都存在一个问题:如果在消费者消费产品之前,生产者已经生产多于1个的产品,比如说已生产了2个,则消费者只能消费第1个产品,然后就等待生产者再生产。实际上这时已经有产品了,就是第2个产品。而消费者必须等待生产者产生第3个产品时才能继续消费。也就是说,第2个产品永远不会被消费!
这个问题可以通过将A代码的bHave改成一个计数器,或将8楼代码的bRun改成一个计数器,来解决。