关于std :: condition_variables的两个问题

问题描述 投票:0回答:1

[我一直在试图找出std::condition_variables,我对wait()以及使用notify_all还是notify_one感到特别困惑。

首先,我编写了一些代码并将其附加在下面。这是一个简短的解释:Collection是一个类,可容纳一堆Counter对象。这些Counter对象具有Counter::increment()方法,需要一遍又一遍地在所有对象上调用该方法。为了加速所有工作,Collection还维护了一个线程池来分配工作,并使用其Collection::increment_all()方法将所有工作发送出去。

这些线程不需要相互通信,通常Counter对象比线程更多。只要一个线程比其他线程处理更多的Counter就好,只要完成所有工作即可。将工作添加到队列很容易,只需要在“主”线程中完成即可。据我所知,唯一可能发生的坏事是在工作进行期间是否允许在计数器上调用其他方法(例如Collection::printCounts)。

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


class Counter{
private:
    int m_count;
public:
    Counter() : m_count(0) {}
    void increment() { 
        m_count ++; 
    }
    int getCount() const { return m_count; }
};


class Collection{
public:
    Collection(unsigned num_threads, unsigned num_counters) 
    : m_shutdown(false)
    {
        // start workers
        for(size_t i = 0; i < num_threads; ++i){
            m_threads.push_back(std::thread(&Collection::work, this)); 
        }

        // intsntiate counters
        for(size_t j = 0; j < num_counters; ++j){
            m_counters.emplace_back();
        }
    }

    ~Collection() 
    { 
        m_shutdown = true;
        for(auto& t : m_threads){
            if(t.joinable()){
                t.join();
            }
        }
    }

    void printCounts() {

        // wait for work to be done
        std::unique_lock<std::mutex> lk(m_mtx);
        m_work_complete.wait(lk); // q2: do I need a while lop?  

        // print all current counters
        for(const auto& cntr : m_counters){
            std::cout << cntr.getCount() << ", ";
        }
        std::cout << "\n";
    }

    void increment_all() 
    {
        std::unique_lock<std::mutex> lock(m_mtx);
        m_work_complete.wait(lock);
        for(size_t i = 0; i < m_counters.size(); ++i){
            m_which_counters_have_work.push(i);
        }

    }


private:    
    void work()
    {
        while(!m_shutdown){

            bool action = false;
            unsigned which_counter;
            {
                std::unique_lock<std::mutex> lock(m_mtx);
                if(m_which_counters_have_work.size()){
                    which_counter = m_which_counters_have_work.front();
                    m_which_counters_have_work.pop();
                    action = true;
                }else{
                    m_work_complete.notify_one(); // q1: notify_all
                }
            }

            if(action){
                m_counters[which_counter].increment();
            }
        }   
    }



    std::vector<Counter> m_counters;
    std::vector<std::thread> m_threads;
    std::condition_variable m_work_complete;
    std::mutex m_mtx;
    std::queue<unsigned> m_which_counters_have_work;
    bool m_shutdown;

};

int main() {

    int num_threads = std::thread::hardware_concurrency()-1;
    int num_counters = 10;
    Collection myCollection(num_threads, num_counters);

    myCollection.printCounts();
    myCollection.increment_all();
    myCollection.printCounts();

    myCollection.increment_all();
    myCollection.printCounts();

    return 0;
}

我在Ubuntu 18.04上使用g++ -std=c++17 -pthread thread_pool.cpp -o tp && ./tp编译了此代码,我认为代码可以实现所有这些目标,但是仍然存在一些问题:

  1. 我正在使用m_work_complete.wait(lk)确保在开始打印所有新计数之前工作已完成。为什么有时我会看到这写在while循环中,或者带有第二个参数作为lambda谓词函数? These docs提及虚假唤醒。如果发生虚假唤醒,这是否意味着printCounts可能会过早打印?如果是这样,我不要。我只想确保工作队列为空,然后再开始使用应该存在的数字。

  2. 我正在使用m_work_complete.notify_all而不是m_work_complete.notify_one。我读过this thread,但我认为这并不重要-只有主线程会因此而被阻塞。使用notify_one是否更快,以便其他线程不必担心它?

c++ multithreading mutex race-condition stdthread
1个回答
0
投票

非常简单:何时使用notify()

  1. 没有理由不止一个线程需要了解该事件。 (例如,使用notify()声明工作线程将“消费”的项目的可用性,从而使该项目对其他工作人员不可用)。[[* AND *
  2. 没有
  3. 错误线程可以唤醒。 (例如,如果所有线程都在同一精确函数的同一行中是wait(),则可能很安全。)
在所有其他情况下都使用notify_all()。>>
© www.soinside.com 2019 - 2024. All rights reserved.