C ++:条件变量等待

时间:2015-12-22 05:47:18

标签: c++ multithreading producer-consumer condition-variable

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>

std::mutex globalMutex;
std::condition_variable globalCondition;
int global = 0;
int activity = 0;
int CountOfThread = 1; // or more than 1

// just for console display, not effect the problem
std::mutex consoleMutex;

void producer() {
    while (true) {
        {
            std::unique_lock<std::mutex> lock(globalMutex);
            while (activity == 0) {
                lock.unlock();
                std::this_thread::yield();
                lock.lock();
            }
            global++;
            globalCondition.notify_one();
        }
        std::this_thread::yield();
    }
}


void customer() {
    while (true) {
        int x;
        {
            std::unique_lock<std::mutex> lock(globalMutex);
            activity++;
            globalCondition.wait(lock); // <- problem
            activity--;
            x = global;
        }
        {
            std::lock_guard<std::mutex> lock(consoleMutex);
            std::cout << x << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}


int _tmain(int argc, _TCHAR* argv[])
{
    for (int i = 0; i < CountOfThread; ++i) {
        std::thread(customer).detach();
    }
    std::thread(producer).detach();
    getchar();
    return 0;
}

我想要的是确保每次有一个客户线程来增加全局,期望显示如:1,2,3 ......,但我看到的是全球价值将在等待和活动 - 因此,实际显示是:1,23,56,78 ...... ....

我发现问题出现在wait()中,在wait(),'解锁,等待,锁定',发信号(等待返回)和mutex.lock之间有3个步骤,它不是原子操作,生产者线程可能会在wait()锁定互斥锁之前锁定互斥锁,并且活动仍然不为零,所以全局会意外地增加

有没有办法确定我的期望?

2 个答案:

答案 0 :(得分:1)

问题在于activity是&gt; 0,producer可循环抓取锁,递增全局,并通知条件变量。 (通知不必有相应的服务员)。

你反复拨打thread.yield是一个红旗 - 他们的意思是你在投票,而不是等待。我认为解决方案是您需要两个条件变量。生产者等待一个直到被消费者通知,消费者等待另一个直到生产者通知。我不太确定你如何与多个消费者合作。

答案 1 :(得分:0)

我发现在线程的上下文中它有助于我。例如,如果您是客户,等待什么?通过你,我的意思是在线程的上下文。当您这样想时,使用monitors进行编码非常简单。

现在,正如马丁所说,我相信重复调用thread.yield有点可怕。这可能导致代码的可怕交错。

为了展示代码无法工作的示例,让我们快速进行交错:

  1. 创建了几个客户,抓住锁定的客户增加了activity。然后,由于调用wait,该线程进入休眠状态。
  2. 在初始客户线程调用wait之后,另一个线程被唤醒。这是因为wait解锁了传递给它的互斥锁。
  3. 该主题获取globalMutex并增加activity。然后它等了。
  4. 重复CountOfThread个线程数,因为多线程代码完全可以实现。
  5. 最后,生产者线程运行,看到activity == 0并解锁。但是,它没有notify_one(即信号)。然后它产生。这可能会导致代码不明确和混乱。
  6. 我的建议:

    1. 在等待条件时,切勿致电yield。这可能导致复杂且难以阅读的无效监视器代码。
    2. 考虑每个线程等待的条件。如果代码的不同部分在不同条件下等待,请创建不同的锁。只对一段共享代码使用一个锁。
    3. 在某些情况下为不同条件使用不同的条件变量。如果条件依赖于不同的数据,请明确使用不同的条件变量。
    4. 在您的情况下,解决方案并不像您想象的那么复杂。使用我的原始建议:在线程的上下文中思考。例如,当生产者线程正在运行时,它希望在客户没有注意到global已被更改时等待。当客户线程正在运行时,只要生产者未更改global ,它就会等待。

      以下是您想要的行为示例:

      mutex m;
      
      condition_variable cv;
      int global = 0, prev_global = 0;
      
      void producer()
      {
          while (true)
          {
              unique_lock<mutex> lock(m);
      
              while (prev_global != global)
              {
                  cv.wait(lock);
              }
              prev_global = global++;
              cv.notify_one();
              lock.unlock();
          }
      }
      
      void customer()
      {
          while (true)
          {
              unique_lock<mutex> lock(m);
      
              while (prev_global == global)
              {
                  cv.wait(lock);
              }
      
              prev_global = global;
              cv.notify_one();
              lock.unlock();
          }
      }
      
      int main()
      {
          vector<thread> pool;
          for (int i = 0; i < 5; ++i)
          {   
      
              pool.push_back(thread (customer));
          }
      
          pool.push_back(thread (producer));
      
          for (auto it = pool.begin(); it != pool.end(); ++it)
          {
              it->join();
          }
          return 0;
      }