为什么引入睡眠导致生产者消费者等待?

时间:2014-06-25 21:07:18

标签: c++ multithreading

我刚学会使用C ++线程库。 如果有人好奇 - 我的代码只是教程https://www.youtube.com/watch?v=13dFggo4t_I&t=6m45s

中的修改版本

我写了一个简单的生产者/消费者代码。我试图引入一个睡眠来使生产者和消费者以锁步的方式生产,所以物品一旦生产就会被消耗掉。但让生产者等待导致一些僵局。我无法弄清楚为什么。 你能帮我指出我在代码中缺少什么吗?

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#define BUFFER_SIZE 10

std::mutex mu;
std::condition_variable full,empty;
std::vector<int> vec;
int i=0;
std::chrono::milliseconds slp(10);

void produce()
{
  while (i < 2*BUFFER_SIZE) {
    std::unique_lock<std::mutex> locker(mu);
    full.wait(locker, [] {return vec.size() != BUFFER_SIZE;});
    vec.push_back(i++);
    empty.notify_one();
    //std::this_thread::sleep_for(slp);   <--- introducing this causes program to hang.
  }
}

void consume()
{
  while(!vec.empty()) {
    std::unique_lock<std::mutex> locker(mu);
    empty.wait(locker, [] {return !vec.empty();});
    std::cout << "Consumed:" << vec.back() <<"\n";
    vec.pop_back();
    full.notify_one();
  }
}

int main() {
  vec.reserve(BUFFER_SIZE*2);
  std::thread producer(produce), consumer(consume);
  producer.join();
  consumer.join();
  return 0;
}

编辑:

void produce()
{
  while (i < 2*BUFFER_SIZE) {
    std::unique_lock<std::mutex> locker(mu);
    full.wait(locker, [] {return vec.size() != BUFFER_SIZE;});
    vec.push_back(i++);
    locker.unlock();
    empty.notify_one();
    std::this_thread::sleep_for(slp);
  }
}

void consume()
{
  while(!vec.empty()) {
    std::unique_lock<std::mutex> locker(mu);
    empty.wait(locker, [] {return !vec.empty();});
    std::cout << "Consumed:" << vec.back() <<"\n";
    vec.pop_back();
    locker.unlock();
    full.notify_one();
  }
}

1 个答案:

答案 0 :(得分:1)

你有几个问题:

  1. 消费者在没有持有互斥锁的情况下访问vec

    while(!vec.empty()) {
    

    通过确保vec的所有加入都是&#34;内部&#34;互斥体。

  2. 如果消费者超越生产者并设法清空vec,它将提前退出。您可以通过使用其他一些机制来指明完成情况来解决此问题。

  3. 您正在使用互斥锁执行某些处理/ io / sleep,从而减少可能的并发性。理想情况下,您只应在访问共享状态时保留互斥锁。

  4. const unsigned BUFFER_SIZE = 10;
    
    std::mutex mu;
    std::condition_variable full,empty;
    std::vector<int> vec;
    const std::chrono::milliseconds slp(10);
    
    auto done = false;
    
    void produce()
    {
      for (auto i = 0u; i < 2 * BUFFER_SIZE; ++i) {
        std::unique_lock<std::mutex> locker(mu);
        full.wait(locker, [] {return vec.size() < BUFFER_SIZE;});
        auto was_empty = vec.empty();
        vec.push_back(i);
        locker.unlock();
    
        // Only notify if the buffer was empty before the push_back
        if (was_empty) {
          empty.notify_all();
        }
    
        std::this_thread::sleep_for(slp);
      }
    }
    
    void consume()
    {
      for (;;) {
        std::unique_lock<std::mutex> locker(mu);
        while (vec.empty()) {
          if (done) {
            return;
          }
          empty.wait(locker);
        }
    
        auto was_full = vec.size() >= BUFFER_SIZE;
        auto value = vec.back();
        vec.pop_back();
        locker.unlock();
    
        if (was_full) {
          full.notify_all();
        }
        std::cout << "Consumed: " << value << '\n';
      }
    }
    
    int main() {
      vec.reserve(BUFFER_SIZE*2);
      std::thread producer(produce), consumer(consume);
      producer.join();
    
      // Produce some more
      producer = std::thread(produce);
      producer.join();
    
      // Produce A LOT more
      std::vector<std::thread> many_producers(8);
      for (auto&& t : many_producers) {
          t = std::thread(produce);
      }
      for (auto && t : many_producers) {
          t.join();
      }
    
      // Tell consumer we are done producing
      {
        std::lock_guard<std::mutex> lock(mu);
        done = true;
      }
      empty.notify_one();
      consumer.join();
    }
    

    See it live at Coliru.