深入浅出多线程系列之十一:生产者/消费者队列

简介:

上次我们使用AutoResetEvent实现了一个生产/消费者队列。这一次我们要使用WaitPulse方法来实现一个更强大的版本,它允许多个消费者,每一个消费者都在自己的线程中运行。

 

我们使用数组来跟踪线程。

Thread[] _workers;

 

通过跟踪线程可以让我们在所有的线程都结束后再结束我们的队列任务。 

每一个消费者线程都执行一个叫做Consume的方法,在一个for循环中,我们可以创建和启动线程。例如:

复制代码
       public  PCQueue( int  workerCount)
        {
            _workers 
=   new  Thread[workerCount];
            
for  ( int  i  =   0 ; i  <  workerCount; i ++ )
                (_workers[i] 
=   new  Thread(Consume)).Start();
        }
复制代码

 

上次我们使用的是一个字符串来代表任务,这次我们使用Action委托,它的定义如下:

Public delegate void Action();

为了表示一系列的任务,我们使用Queue<T> 集合,例如:

Queue<Action> _itemQ = new Queue<Action>();

 

在我们调用生产(EnqueueItem)和消费(Consume)方法前,还是完整的看一看代码吧:

 

复制代码
class  PCQueue
    {
        
readonly   object  _locker  =   new   object ();
        Thread[] _workers;
        Queue
< Action >  _itemQ  =   new  Queue < Action > ();  // 保存任务的队列
         public  PCQueue( int  workerCount)
        {
            _workers 
=   new  Thread[workerCount];
            
for  ( int  i  =   0 ; i  <  workerCount; i ++ )
                (_workers[i] 
=   new  Thread(Consume)).Start();
        }

        
public   void  Shutdown( bool  waitForWorkers)
        {
           
// 为每一个线程插入一个null item,可以是每一个worker 退出
             foreach  (Thread worker  in  _workers)
                EnqueueItem(
null );

           
// 等待所有的线程退出。
             if  (waitForWorkers)
                
foreach  (Thread worker  in  _workers)
                    worker.Join();
        }

        
public   void  EnqueueItem(Action item)
        {
            
lock  (_locker)
            {
                _itemQ.Enqueue(item);
                Monitor.Pulse(_locker); 
// 通知等待队列中的线程
            }
        }

        
void  Consume()
        {
            
while  ( true )
            {
                Action item;
                
lock  (_locker)
                {
                    
while  (_itemQ.Count  ==   0 )
                    {
                        Monitor.Wait(_locker); 
// 释放锁,并阻止当前线程,直到其他线程发送pulse信号。                    }
                    item  =  _itemQ.Dequeue();
                }

                
if  (item  ==   null return // 退出的信号
                item();
            }
        }
    }
复制代码

 

我们可以有一个退出策略,插入一个null item作为consumer退出的信号。如果我们想要快速的退出,可以使用一个独立的”cancel” 标记,因为我们支持多个consumers,所以我们必须为每一个consumer插入一个null item

 

下面是Main方法。使用两个consumer线程,然后让这两个consumers执行10个委托。

 

复制代码
        public   static   void  Main()
        {
            PCQueue q 
=   new  PCQueue( 2 );
            Console.WriteLine(
" Enqueuing 10 items... " );

            
for  ( int  i  =   0 ; i  <   10 ; i ++ )
            {
                
int  itemNumber  =  i;
                q.EnqueueItem(() 
=>
                    {
                        Thread.Sleep(
1000 );  // 模拟耗时的工作
                        Console.WriteLine( "  Task  "   +  itemNumber);
                    });
            }

            q.Shutdown(
true ); //等待关闭
            Console.WriteLine();
            Console.WriteLine(
" Workers complete! " );
        }
复制代码

 

 

下面让我们细致的看一看EnqueueItem方法:

复制代码
public   void  EnqueueItem(Action item)
        {
            
lock  (_locker)
            {
                _itemQ.Enqueue(item);
                Monitor.Pulse(_locker); 
// 通知等待队列中的线程
            }
        }
复制代码

因为我们的队列_itemQ被多线程环境使用,因此在对_itemQ进行读取的时候需要加锁lock.

因为我们插入了一个新的任务,我们必须修改阻塞条件,也就是调用pulse方法,来唤醒调用了wait方法的线程。

 

出于对效率的考虑,当插入一个Item的时候使用Pulse来代替PulseAll方法,因为大部分时候每一个Item只需要一个consumer来执行。如果你有一个冰淇淋,你不可能叫30个睡眠的孩子都起来吃它,同样,对于一个item,同时唤醒30consumers一点好处都没有。

 

让我们再看看Consumer方法。

我们希望当没什么事情做的时候,线程阻塞就可以了,换句话说,队列中没有item的时候,线程就应该阻塞。因此我们的阻塞条件是_itemQ.Count ==0;

 

复制代码
                Action item;
                
lock  (_locker)
                {
                    
while  (_itemQ.Count  ==   0 )
                    {
                        Monitor.Wait(_locker); 
// 释放锁,并阻止当前线程,直到其他线程发送pulse信号。                    }
                    item  =  _itemQ.Dequeue();
                }

                
if  (item  ==   null return // 退出的信号
                item();
复制代码

 

while循环退出的时候也意味着_itemQ 至少有一个item。我们必须在释放锁之前调用你哦个dequeue方法来获取item,考虑下下面的代码:

复制代码
                lock  (_locker)
                {
                    
while  (_itemQ.Count  ==   0 )
                    {
                        Monitor.Wait(_locker); 
// 释放锁,并阻止当前线程,直到其他线程发送pulse信号。                    }
                }
                
// 现在在这里可能被抢占,_itemQ可能被修改
                 lock  (_locker)
                {
                    item 
=  _itemQ.Dequeue();
                }
复制代码

 

itemDequeued后,我们就应该立即释放锁了,如果我们在执行task的时候,一直持有锁,则会没有必要的阻塞其他线程来获取任务。

 

Wait Timeouts

在调用Wait方法的时候可以传递一个毫秒或Timespan的时间来设置超时。如果Wait超时了,那么Wait方法就会返回false

带有超时功能的Wait方法的主要步骤:

  1. 释放锁。
  2. 阻塞 直到 pulsed 或者超时。
  3. 重新获取锁。

超时就好像CLR 在超时到了的时候自动的调用了 pulse方法一样。

 

下面是使用超时的Wait的主要代码:

         lock(_locker)

         while(<阻塞条件>)

                   Monitor.Wait(_locker,<超时时间>);

 

Monitor.Wait 方法返回一个bool值来代表是调用了pulse还是已经超时了。

如果是true 代表调用了pulse

如果是false:代表超时了。

这对记录日志很有用。

 

 






本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/06/01/2060857.html,如需转载请自行联系原作者
目录
相关文章
|
6月前
|
容器
多线程学习之生产者和消费者与阻塞队列的关系
多线程学习之生产者和消费者与阻塞队列的关系
24 0
|
4月前
|
数据处理
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
44 1
|
14天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
20天前
|
Java Linux
【linux线程(三)】生产者消费者模型详解(多版本)
【linux线程(三)】生产者消费者模型详解(多版本)
|
3月前
|
消息中间件 安全 Java
多线程(初阶七:阻塞队列和生产者消费者模型)
多线程(初阶七:阻塞队列和生产者消费者模型)
32 0
|
4月前
|
Java C++
线程池-手写线程池C++11版本(生产者-消费者模型)
线程池-手写线程池C++11版本(生产者-消费者模型)
83 0
|
4月前
|
Java Linux C语言
线程池-手写线程池Linux C简单版本(生产者-消费者模型)
线程池-手写线程池Linux C简单版本(生产者-消费者模型)
46 0
|
4月前
线程同步之 生产者消费者模型详解
前言 博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产者消费者模型讲一讲,希望大家能看懂(没有现成和锁知识的朋友不要急,这部分是写给有基础的朋友看的,这些知识博主都会慢慢的讲到)。 前言 博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产
26 0
|
5月前
|
SQL 供应链 安全
Linux多线程【生产者消费者模型】
Linux多线程【生产者消费者模型】
62 0
|
5月前
|
Java
Java之多线程的生产者消费者问题的详细解析
3.生产者消费者 3.1生产者和消费者模式概述【应用】 概述 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
49 0
Java之多线程的生产者消费者问题的详细解析