C++11多线程---条件变量(三)

本文详细介绍了C++中条件变量在多线程同步中的应用,包括条件变量的使用过程、成员函数如wait、wait_for、wait_until、notify_one和notify_all的用法,以及通过条件变量实现同步队列的示例。
摘要由CSDN通过智能技术生成

系列文章目录

C++11多线程---线程(一)

C++11多线程---互斥锁(二)

C++11多线程---条件变量(三)

C++11多线程---原子操作变量(四)

C++11多线程---异步操作(五)


前言

互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11 对这种行为也提供了有力的支持,这就是 条件变量 。条件变量位于头文件 condition_variable 下。

一、多线程的条件变量

1、条件变量使用过程:

  • 拥有条件变量的线程获取互斥量;
  • 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
  • 某个线程满足条件执行完之后调用notify_onenotify_all唤醒一个或者所有等待线程。

2、成员函数

条件变量提供了两类操作:waitnotify。这两类操作构成了多线程同步的基础。

2.1 wait函数

函数原型
void wait ( unique_lock < mutex >& lck );
template < class Predicate >
void wait ( unique_lock < mutex >& lck , Predicate pred );

包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用 unique_lock,因为wait函数的工作原理:

  • 当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者 notify_all 唤醒当前线程;一旦当前线程获得通知(notify)wait()函数也是自动调用lock(),同理不能使用lock_guard对象
  • 如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_onenotify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
  • 如果wait包含第二个参数,如果第二个参数不满足,那么wait将解锁互斥量并堵塞到本行,直到某一个线程调用notify_onenotify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为falsewait对互斥量解锁,然后休眠,如果为true,则进行后面的操作。

2.2 wait_for函数

函数原型:
template < class Rep , class Period >
        cv_status wait_for ( unique_lock < mutex >& lck ,
                                        const chrono::duration < Rep , Period >& rel_time );
template < class Rep , class Period , class Predicate >
        bool wait_for ( unique_lock < mutex >& lck ,
                                const chrono::duration < Rep , Period >& rel_time , Predicate pred );
wait 不同的是, wait_for 可以执行一个时间段 ,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for 返回,剩下操作和 wait 类似。

2.3 wait_until函数  

 函数原型:

template < class Clock , class Duration >
        cv_status wait_until ( unique_lock < mutex >& lck ,
                                        const chrono::time_point < Clock , Duration >& abs_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 );

 与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until返回,剩下操作和wait类似。

2.4 notify_one函数  

函数原型:
void notify_one () noexcept ;
解锁正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待的线程多余一个,则唤醒的线程是不确定的。

2.5 notify_all函数

函数原型:
void notify_all () noexcept ;

 解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

3 代码范例

使用条件变量实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取。

3.1 代码范例:同步队列的实现 condition-sync-queue

sync_queue.h
#define SYNC_QUEUE_H 
#include<list> #include<mutex> 
#include<thread> #include<condition_variable> 
#include <iostream>

template<typename T> 
class SyncQueue 
{
private: 
    bool IsFull() const 
    { 
        return _queue.size() == _maxSize; 
    }
    bool IsEmpty() const 
    { 
        return _queue.empty(); 
    } 
public: 
    SyncQueue(int maxSize) : _maxSize(maxSize) 
    {}

    void Put(const T& x) 
    { 
        std::lock_guard<std::mutex> locker(_mutex); 
        
        #if 0
        while (IsFull()) 
        { 
            std::cout << "full wait..." << std::endl;
            _notFull.wait(_mutex); }_queue.push_back(x);
            notFull.notify_one(); 
        }

        #endif
        /* 下面一行代码与上 if 0代码段效果相同,但是后者更简洁,条件变量会先检查判断式是否满足            
         条件,如果满足条件则重新获取mutex,然后结束wait继续往下执行;如果不满足条件则释放        
         mutex,然后将线程置为waiting状态继续等待。
        */
        _notFull.wait(_mutex, [this] {return !IsFull();});

        void Take(T& x) 
        { 
            std::lock_guard<std::mutex> locker(_mutex); 
            while (IsEmpty()) 
            { 
                std::cout << "empty wait..." << std::endl; 
                notEmpty.wait(_mutex); 
            }
                
            x = _queue.front(); 
            queue.pop_front();
            notFull.notify_one(); 
        }

        bool Empty() 
        { 
            std::lock_guard<std::mutex> locker(_mutex); 
            return _queue.empty(); 
        }
             
        bool Full() 
        { 
            std::lock_guard<std::mutex> locker(_mutex);
            return _queue.size() == _maxSize;
        }

        size_t Size() 
        { 
            std::lock_guard<std::mutex> locker(_mutex); 
            return _queue.size(); 
        }

        int Count() 
        { 
            return _queue.size(); 
        } 
private: 
    std::list<T> _queue; //缓冲区 
    std::mutex _mutex; //互斥量和条件变量结合起来使用 
    std::condition_variable_any _notEmpty;//不为空的条件变量 
    std::condition_variable_any _notFull; //没有满的条件变量 
    int _maxSize; //同步队列最大的size };#endif // SYNC_QUEUE_H

main.cpp

#include <iostream> 
#include "sync_queue.h" 
#include <thread> 
#include <iostream> 
#include <mutex> 
using namespace std; 
SyncQueue<int> syncQueue(5); 
void PutDatas() 
{ 
    for (int i = 0; i < 20; ++i) 
    { 
        syncQueue.Put(888); 
    }
    std::cout << "PutDatas finish\n"; 
}

void TakeDatas() 
{ 
    int x = 0; 
    for (int i = 0; i < 20; ++i) 
    { 
        syncQueue.Take(x); 
        std::cout << x << std::endl; 
    }
    std::cout << "TakeDatas finish\n"; 
}

int main(void) 
{ 
    std::thread t1(PutDatas); 
    std::thread t2(TakeDatas); 
    t1.join(); 
    t2.join(); 
    std::cout << "main finish\n"; 
    return 0; 
}

总结

以上就是线程互斥锁配套条件变量的使用方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值