深入应用C++11 笔记---条件变量 (八)

深入应用C++11 笔记(八)

—条件变量 +原子变量+call_once/once_flag

条件变量

​ 条件变量是C++11 提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来用。

  • condition_variable,配合std::unique_lock < std::mutex>进行wait操作。
  • condition_variable_any,和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些。

condition_variable_any比condition_variable更灵活,因为它更通用,对所有锁都适用,而condition_variable性能更好。

条件变量使用过程如下:

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

可以用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取,比如半同步半异步线程池的同步队列:

#include 
#include 
#include 

template<typename T>
class SyncQueue
{
private:
    bool IsFull() const //const函数不能修改其数据成员
    {
        return m_queue.size()==m_maxSize;
    }
    bool IsEmpty() const
    {
        return m_queue.empty();
    }
public:
    SyncQueue(int maxSize):m_maxSize(maxSize){}
    void Put(const T& x)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        while(IsFull())
        {
            std::cout<<"缓冲区满了,需要等待...."<<std::endl;
            m_notFull.wait(m_mutex);
        }
        m_queue.push_back(x);
        m_notEmpty.notify_one();
    }

    void Take(T& x)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        while(IsEmpty())
        {
            std::cout<<"缓冲区空了,需要等待...."<<std::endl;
            m_notEmpty.wait(m_mutex);
        }
        x=m_queue.front();
        m_queue.pop_front();
        m_notFull.notify_one();
    }

    bool Empty()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.empty();
    }

    bool Full()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.size()==m_maxSize;
    }

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

    int Count()
    {
        return m_queue.size();
    }

private:
    std::list m_queue;   //  缓冲区
    std::mutex m_mutex;     //互斥量和条件变量结合起来使用
    std::condition_variable_any m_notEmpty;
    std::condition_variable_any m_notFull;
    int m_maxSize;
}
  • 必须使用while循环来等待条件变为真,即醒来之后要立马再判断一次条件是否成立再决定是否需要继续等待,因为很有可能条件并不为真,但是线程却被各种奇怪的中断或者pthread_cond_broadcast这样的东西给唤醒了

    std::lock_guard::mutex> locker(m_mutex);
    while(IsFull())
    {
        m_notFull.wait(m_mutex);
    }

    也可以改成这样:

    std::unique_lock::mutex> locker(m_mutex);
    m_notFull.wait(locker,[this]{return !IsFull();})

    ​ 两种写法效果一样,条件变量会先检查判断式是否满足条件,如果满足条件,则重新获取mutex,然后结束wait,继续往下执行;如果不满足条件,则释放mutex,然后将线程置为waiting状态,继续等待。

原子变量

​ C++11 提供了一个原子类型std::atomic,可以使用任意类型作为模板参数,C++11内置了整型的原子变量,可以更方便地使用原子变量,不需要使用互斥量来保护该变量。如下是一个计数器的实现:

//使用mutex实现计数器
struct Counter
{
    int value;
    std::mutex;
    void increment()
    {
        std::lock_guard lock(mutex);
        ++value;
    }
    void decrement()
    {
        std::lock_guard lock(mutex);
        --value;
    }
    int get()
    {
        return value;
    }
}
//使用原子变量实现计数器
#include
struct AtomicCounter
{
    std::atomic<int> value;
    void increment(){++value;}
    void decrement(){--value;}
    int get()
    {
        return value.load();
    }
}

call_once/once_flag的使用

​ 为了保证多线程环境下某个函数只被调用一次,比如,需要初始化某个对象,而这个对象只能初始化一次时,就可以用std::call_once来保证函数在多线程环境中只被调用一次。使用std::call_once时,需要一个once_flag作为call_once的入参:

#include
#include
#include

std::once_flag flag;
void do_once()
{
    std::call_once(flag,[](){std::cout<<"Called once"<<std::endl;});
}
int main()
{
    std::thread t1(do_once);
    std::thread t2(do_once);
    std::thread t3(do_once);

    t1.join();
    t2.join();
    t3.join();
    return 0;
}
//输出: Called once
void oncetest()
{
    std::cout << "once...." << std::endl;
}
std::once_flag flag;
void do_once()
{
    std::call_once(flag, oncetest);
}

声明:以上主要来源于深入应用C++11 这本书,强烈推荐

你可能感兴趣的:(C++,1z)