C++11多线程学习:条件变量

互斥量和条件变量

例子

 首先,举个例子:在应用程序中有4个进程thread1,thread2,thread3和thread4,有一个int类型的全局变量iCount。iCount初始化为0,thread1和thread2的功能是对iCount的加1,thread3的功能是对iCount的值减1,而thread4的功能是当iCount的值大于等于100时,打印提示信息并重置iCount=0。
 互斥量代码:

thread1/2while (1)
       {
             pthread_mutex_lock(&mutex);
             iCount++;
             pthread_mutex_unlock(&mutex);
       }
thread4:
       while(1)
       {
             pthead_mutex_lock(&mutex);
             if (100 <= iCount)
             {
                   printf("iCount >= 100\r\n");
                   iCount = 0;
                   pthread_mutex_unlock(&mutex);
             }
             else
             {
                   pthread_mutex_unlock(&mutex);
             }
       }

 在上面代码中由于thread4并不知道什么时候iCount会大于等于100,所以就会一直在循环判断,但是每次判断都要加锁、解锁(即使本次并没有修改iCount)。这就带来了问题一:CPU浪费严重。所以在代码中添加了sleep(),这样让每次判断都休眠一定时间。但这又带来的第二个问题:如果sleep()的时间比较长,导致thread4处理不够及时,等iCount到了很大的值时才重置。对于上面的两个问题,可以使用条件变量来解决。
 条件变量代码:

thread1/2:
       while(1)
       {
               pthread_mutex_lock(&mutex);
               iCount++;
               pthread_mutex_unlock(&mutex);
               if (iCount >= 100)
               {
                      pthread_cond_signal(&cond);
               }
       }         
thread4:
       while (1)
       {
              pthread_mutex_lock(&mutex);
              while(iCount < 100)
              {
                     pthread_cond_wait(&cond, &mutex);
              }
              printf("iCount >= 100\r\n");
              iCount = 0;
              pthread_mutex_unlock(&mutex);
       }       

几点总结

  1. thread4在条件false的情况下调用pthread_cond_wait,这个函数会首先释放锁,然后将该线程阻塞在这里(线程挂起于wait队列中),这就解决了互斥量代码中thread4轮询损耗问题
  2. 在其他线程处会通过pthread_cond_signal方法重新唤醒阻塞的所有线程,唤醒的线程在wait函数中竞争lock,成功获取锁的线程会return,其他线程继续阻塞。
  3. 在thread4中使用的while (iCount < 100),而不是if (iCount < 100)。这是因为在pthread_cond_singal()和pthread_cond_wait()的lock之间有时间差,假如在时间差内,thread3又将iCount减到了100以下了,那么thread4在pthread_cond_wait()返回之后,显然应该再检查一遍iCount的大小,这就是while的用意。

pthread_cond_signal的位置

 pthread_cond_signal放在lock,unlock之间和之后都可以:
1. 在之间的情况下,可能会把等待线程饿死,原因是signal信号发出时未unlock,所有等待线程继续阻塞在wait
2. 在之后的情况下,可能会在signal信号发出后,有其他线程修改了条件变量,情况同上一节的第三点问题

C++11中的std::condition_variable

 c++11的条件变量类的定义:

class condition_variable {  
public:  
    typedef _Cnd_t native_handle_type;  

    condition_variable() {              // 构造函数,初始化条件变量,所有的条件变量必须初始化后才能使用。  
        _Cnd_initX(&_Cnd);  
    }  

    ~condition_variable() _NOEXCEPT {   // 析构函数  
        _Cnd_destroy(&_Cnd);  
    }  

    condition_variable(const condition_variable&) = delete;  
    condition_variable& operator=(const condition_variable&) = delete;  

    void notify_one() _NOEXCEPT {       // 唤醒一个在等待线程  
        _Cnd_signalX(&_Cnd);  
    }  

    void notify_all() _NOEXCEPT {       // 唤醒所有在等待的线程  
        _Cnd_broadcastX(&_Cnd);  
    }  

    void wait(unique_lock<mutex>& _Lck) {                 // 等待  
        _Cnd_waitX(&_Cnd, &_Lck.mutex()->_Mtx);  
    }  

    template<class _Predicate>  
    void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) {   // 等待,带有描述式  
        while (!_Pred())  
            wait(_Lck);  
    }  

    template<class _Rep, class _Period>  
    _Cv_status wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time) {  
        stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);  
        return (wait_until(_Lck, &_Tgt));  
    }  

    template<class _Rep, class _Period, class _Predicate>  
    bool wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) {  
        stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);  
        return (wait_until(_Lck, &_Tgt, _Pred));  
    }  

    template<class _Clock, class _Duration>  
    _Cv_status wait_until( unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time) {  
        typename chrono::time_point<_Clock, _Duration>::duration  
            _Rel_time = _Abs_time - _Clock::now();  
        return (wait_for(_Lck, _Rel_time));  
    }  

    template<class _Clock, class _Duration, class _Predicate>  
    bool wait_until(unique_lock<mutex>& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) {  
        typename chrono::time_point<_Clock, _Duration>::duration  
            _Rel_time = _Abs_time - _Clock::now();  
        return (wait_for(_Lck, _Rel_time, _Pred));  
    }  

    _Cv_status wait_until( unique_lock<mutex>& _Lck, const xtime *_Abs_time) {  
        if (!_Mtx_current_owns(&_Lck.mutex()->_Mtx))  
            _Throw_Cpp_error(_OPERATION_NOT_PERMITTED);  
        int _Res = _Cnd_timedwaitX(&_Cnd, &_Lck.mutex()->_Mtx, _Abs_time);  
        return (_Res == _Thrd_timedout ? cv_status::timeout : cv_status::no_timeout);  
    }  

    template<class _Predicate>  
    bool wait_until(unique_lock<mutex>& _Lck, const xtime *_Abs_time, _Predicate _Pred) {  
        bool _Res = true;  
        while (_Res && !_Pred())  
            _Res = wait_until(_Lck, _Abs_time)  
                != cv_status::timeout;  
        return (_Pred());  
    }  

    native_handle_type native_handle() {                    // 返回条件变量的句柄  
        return (_Cnd);  
    }  

    void _Register(unique_lock<mutex>& _Lck, int *_Ready) {  
        _Cnd_register_at_thread_exit(&_Cnd, &_Lck.release()->_Mtx, _Ready);  
    }  

    void _Unregister(mutex& _Mtx) {  
        _Cnd_unregister_at_thread_exit(&_Mtx._Mtx);  
    }  

private:  
    _Cnd_t _Cnd;  
};  

该类禁止了拷贝和赋值,将linux中的pthread进行了封装,具有以下几个类似的方法:
condition_variable::notify_one():唤醒一个处于等待状态的线程;

condition_variable::notify_all():唤醒所有处于等待状态的线程;

condition_variable::wait():将线程置于等待状态,直到被notify_xxx()唤醒;

condition_variable::wait_for():将线程置于等待状态,直到一段时间结束后自动醒来或被notify_xxx()唤醒;

condition_variable::wait_until():将线程置于等待状态,直到指定的时间点到来自动唤醒或被notify_xxx()唤醒;

例子

std::cout
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable

std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.

void do_print_id(int id)
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果标志位不为 true, 则等待...
        cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}

void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 设置全局标志位为 true.
    cv.notify_all(); // 唤醒所有线程.
}

int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);

    std::cout << "10 threads ready to race...\n";
    go(); // go!

  for (auto & th:threads)
        th.join();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42013750/article/details/80405997
今日推荐