C ++多线程,简单的消费者/生产者线程,LIFO,通知,计数器

时间:2012-02-26 05:43:43

标签: c++ multithreading

我是多线程编程的新手,我想实现以下功能。

  1. 有2个线程,生产者和消费者。
  2. 消费者仅处理最新值,即先进先出(LIFO)。
  3. 生产者有时会以比消费者更快更快的速度生成新值 处理。例如,生产者可以在1中生成2个新值 毫秒,但大约需要消费者5毫秒才能处理。
  4. 如果消费者在处理旧的过程中收到新值 值,没有必要打断。换句话说,消费者将完成当前 首先执行,然后开始执行最新值。
  5. 这是我的设计过程,如果我错了,请纠正我。

    1. 不需要队列,因为只有最新的值 由消费者处理。
    2. 生产者发送的通知是否自动排队???
    3. 我会改用计数器。
    4. ConsumerThread()检查最后的计数器,以确保生产者 没有产生新的价值。
    5. 但是,如果生产者在消费者之前生成新值,会发生什么 去睡觉(),但检查了计数器???
    6. 这是一些伪代码。

      boost::mutex mutex;
      double x;
      
      void ProducerThread() 
      {
          {
              boost::scoped_lock lock(mutex);
              x = rand();
              counter++;
          }
          notify(); // wake up consumer thread
      }   
      
      void ConsumerThread()
      {
          counter = 0; // reset counter, only process the latest value
      
      ... do something which takes 5 milli-seconds ...
      
          if (counter > 0) 
          {
      ... execute this function again, not too sure how to implement this ...
          } 
          else 
          {
      ... what happen if producer generates a new value here??? ...
              sleep();
          }
      }
      

      感谢。

3 个答案:

答案 0 :(得分:2)

如果我理解您的问题,对于您的特定应用,消费者只需要处理生产者提供的最新可用值。换句话说,价值被放弃是可以接受的,因为消费者无法跟上生产者的步伐。

如果是这种情况,那么我同意你可以在没有队列的情况下离开并使用计数器。但是,共享计数器和值变量需要 atomically

您可以使用boost::condition_variable向消费者发出通知,表明新值已准备就绪。这是一个完整的例子;我会让评论做解释。

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/locks.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>

boost::mutex mutex;
boost::condition_variable condvar;
typedef boost::unique_lock<boost::mutex> LockType;

// Variables that are shared between producer and consumer.
double value = 0;
int count = 0;

void producer()
{
    while (true)
    {
        {
            // value and counter must both be updated atomically
            // using a mutex lock
            LockType lock(mutex);
            value = std::rand();
            ++count;

            // Notify the consumer that a new value is ready.
            condvar.notify_one();
        }

        // Simulate exaggerated 2ms delay
        boost::this_thread::sleep(boost::posix_time::milliseconds(200));
    }
}

void consumer()
{
    // Local copies of 'count' and 'value' variables. We want to do the
    // work using local copies so that they don't get clobbered by
    // the producer when it updates.
    int currentCount = 0;
    double currentValue = 0;

    while (true)
    {
        {
            // Acquire the mutex before accessing 'count' and 'value' variables.
            LockType lock(mutex); // mutex is locked while in this scope
            while (count == currentCount)
            {
                // Wait for producer to signal that there is a new value.
                // While we are waiting, Boost releases the mutex so that
                // other threads may acquire it.
                condvar.wait(lock);
            }

            // `lock` is automatically re-acquired when we come out of
            // condvar.wait(lock). So it's safe to access the 'value'
            // variable at this point.
            currentValue = value; // Grab a copy of the latest value
                                  // while we hold the lock.
        }

        // Now that we are out of the mutex lock scope, we work with our
        // local copy of `value`. The producer can keep on clobbering the
        // 'value' variable all it wants, but it won't affect us here
        // because we are now using `currentValue`.
        std::cout << "value = " << currentValue << "\n";

        // Simulate exaggerated 5ms delay
        boost::this_thread::sleep(boost::posix_time::milliseconds(500));
    }
}

int main()
{
    boost::thread c(&consumer);
    boost::thread p(&producer);
    c.join();
    p.join();
}

附录

我最近在考虑这个问题,并意识到这个解决方案虽然可行,但并不是最优的。你的制作人正在使用所有的CPU来丢弃一半的计算值。

我建议您重新考虑您的设计,并在生产者和消费者之间使用有界阻塞队列。这样的队列应该具有以下特征:

  • 线程安全
  • 队列具有固定大小(有界)
  • 如果消费者想要弹出下一个项目,但队列为空,则操作将被阻止,直到生产者通知项目可用。
  • 制作人可以检查是否有空间推送另一个项目并阻止该空间可用。

使用这种类型的队列,您可以有效地限制生产者,使其不超过消费者。它还确保生产者不会浪费CPU资源计算将被丢弃的值。

TBBPPL等库提供了并发队列的实现。如果您想尝试使用std::queue(或boost::circular_buffer)和boost::condition_variable推送自己的广告,请查看此博客的example

答案 1 :(得分:1)

简短的回答是,你几乎肯定是错的。

对于生产者/消费者,你几乎需要两个线程之间的队列。基本上有两种选择:要么你的代码不会简单地丢失任务(这通常等于完全没有工作),否则你的生产者线程需要阻止消费者线程在它产生一个项目之前是空闲的 - 有效地转换为单线程。

目前,我将假设您从rand返回的值应该代表要执行的任务(即,生产者生成并由消费者使用的值) 。在这种情况下,我会编写如下代码:

void producer() { 
    for (int i=0; i<100; i++)
        queue.insert(random());    // queue.insert blocks if queue is full
    queue.insert(-1.0);            // Tell consumer to exit
}

void consumer() { 
    double value;
    while ((value = queue.get()) != -1) // queue.get blocks if queue is empty
        process(value);
}

这样,几乎将所有联锁降级为队列。两个线程的其余代码几乎完全忽略了线程问题。

答案 2 :(得分:0)

如果您正在进行管理,实施管道实际上非常棘手。例如,您必须使用条件变量来避免您在问题中描述的那种竞争条件,避免在实现“唤醒”消费者等机制时忙于等待...即使使用“队列”只是1元素不会使你免于一些复杂性。

通常使用专门为此目的开发和经过广泛测试的专业库会更好。如果您可以使用Visual C ++特定的解决方案,请查看Parallel Patterns LibraryPipelines的概念。