C++11新特性——互斥锁、条件变量、原子类型

1、互斥锁

C++11提供了四种互斥锁:

  1. mutex:互斥锁。
  2. timed_mutex:带超时机制的互斥锁。
  3. recursive_mutex:递归互斥锁。
  4. recursive_timed_mutex:带超时机制的递归互斥锁。

包含头文件:#include <mutex>

一、mutex类

1)加锁lock()

互斥锁有锁定和未锁定两种状态。

如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。

如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。

2)解锁unlock()

只有持有锁的线程才能解锁。

3)尝试加锁try_lock()

如果互斥锁是未锁定状态,则加锁成功,函数返回true

如果互斥锁是锁定状态,则加锁失败,函数立即返回false。(线程不会阻塞等待)

示例:

#include <iostream>
#include <thread>                // 线程类头文件。
#include <mutex>                // 互斥锁类的头文件。
using namespace std;

mutex mtx;        // 创建互斥锁,保护共享资源cout对象。

// 普通函数。
void func(int bh, const string& str) {
      for (int ii = 1; ii <= 10; ii++)
      {
            mtx.lock();      // 申请加锁。
            cout << bh << str << endl;
            mtx.unlock();  // 解锁。
            this_thread::sleep_for(chrono::seconds(1));     // 休眠1秒。
      }
}

int main()
{
      // 用普通函数创建线程。
      thread t1(func, 1, "我是一只傻傻鸟。");
      thread t2(func, 2, "我是一只傻傻鸟。");
      thread t3(func, 3, "我是一只傻傻鸟。");
      thread t4(func, 4, "我是一只傻傻鸟。");
      thread t5(func, 5, "我是一只傻傻鸟。");

      t1.join();         // 回收线程t1的资源。
      t2.join();         // 回收线程t2的资源。
      t3.join();         // 回收线程t3的资源。
      t4.join();         // 回收线程t4的资源。
      t5.join();         // 回收线程t5的资源。
      return 0;
}

二、timed_mutex类

增加了两个成员函数:

bool try_lock_for(时间长度);

bool try_lock_until(时间点);

三、recursive_mutex类

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

示例:

#include <iostream>
#include <mutex>        // 互斥锁类的头文件。
using namespace std;

class AA
{
      recursive_mutex m_mutex;
public:
      void func1() {
            m_mutex.lock();
            cout << "调用了func1()\n";
            m_mutex.unlock();
      }

      void func2() {
            m_mutex.lock();
            cout << "调用了func2()\n";
            func1();
            m_mutex.unlock();
      }
};

int main()
{
      AA aa;
      //aa.func1();
      aa.func2();
      return 0;
}

四、lock_guard类

lock_guard是模板类,可以简化互斥锁的使用,也更安全。

lock_guard的定义如下:

template<class Mutex>

class lock_guard

{

    explicit lock_guard(Mutex& mtx);

}

lock_guard在构造函数中加锁,在析构函数中解锁。

lock_guard采用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放)。

2、条件变量-生产消费者模型

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

C++11的条件变量提供了两个类:

condition_variable:只支持与普通mutex搭配,效率更高。

condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)。

包含头文件:<condition_variable>

一、condition_variable类

主要成员函数:

1condition_variable() 默认构造函数。

2condition_variable(const condition_variable &)=delete 禁止拷贝。

3condition_variable& condition_variable::operator=(const condition_variable &)=delete 禁止赋值。

4notify_one() 通知一个等待的线程。

5notify_all() 通知全部等待的线程。

6wait(unique_lock<mutex> lock) 阻塞当前线程,直到通知到达。

7wait(unique_lock<mutex> lock,Pred pred) 循环的阻塞当前线程,直到通知到达且谓词满足。

8wait_for(unique_lock<mutex> lock,时间长度)

9wait_for(unique_lock<mutex> lock,时间长度,Pred pred)

10wait_until(unique_lock<mutex> lock,时间点)

11wait_until(unique_lock<mutex> lock,时间点,Pred pred)

二、unique_lock类

template <class Mutex> class unique_lock是模板类,模板参数为互斥锁类型。

unique_locklock_guard都是管理锁的辅助类,都是RAII风格(在构造时获得锁,在析构时释放锁)。它们的区别在于:为了配合condition_variableunique_lock还有lock()unlock()成员函数。

示例1:

#include <iostream>
#include <string>
#include <thread>                      // 线程类头文件。
#include <mutex>                      // 互斥锁类的头文件。
#include <deque>                      // deque容器的头文件。
#include <queue>                      // queue容器的头文件。
#include <condition_variable>  // 条件变量的头文件。
using namespace std;

class AA
{
    mutex m_mutex;                                    // 互斥锁。
    condition_variable m_cond;                  // 条件变量。
    queue<string, deque<string>> m_q;   // 缓存队列,底层容器用deque。
public:
    void incache(int num)     // 生产数据,num指定数据的个数。
    {
        lock_guard<mutex> lock(m_mutex);   // 申请加锁。
        for (int ii=0 ; ii<num ; ii++)
        {
            static int bh = 1;           // 编号。
            string message = to_string(bh++) + "号";    // 拼接出一个数据。
            m_q.push(message);     // 把生产出来的数据入队。
        }
        m_cond.notify_one();     // 唤醒一个被当前条件变量阻塞的线程。
    }

    void outcache()       // 消费者线程任务函数。
    {
        while (true)
        {
            string message;
            {
                // 把互斥锁转换成unique_lock<mutex>,并申请加锁。
                unique_lock<mutex> lock(m_mutex);

                while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
                    m_cond.wait(lock);  // 等待生产者的唤醒信号。

                // 数据元素出队。
                message = m_q.front();  m_q.pop();
            }//出了该代码块后自动解锁
            // 处理出队的数据(把数据消费掉)。
            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。
            cout << "线程:" << this_thread::get_id() << "," << message << endl;
        }
    }
};

int main()
{
    AA aa;

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。
    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。
    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。

    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。
    aa.incache(3);      // 生产3个数据。

    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。
    aa.incache(5);      // 生产5个数据。

    t1.join();   // 回收子线程的资源。
    t2.join();
    t3.join();
    return 0;
}

示例2:

#include <iostream>
#include <string>
#include <thread>                      // 线程类头文件。
#include <mutex>                      // 互斥锁类的头文件。
#include <deque>                      // deque容器的头文件。
#include <queue>                      // queue容器的头文件。
#include <condition_variable>  // 条件变量的头文件。
using namespace std;

class AA
{
    mutex m_mutex;                                    // 互斥锁。
    condition_variable m_cond;                  // 条件变量。
    queue<string, deque<string>> m_q;   // 缓存队列,底层容器用deque。
public:
    void incache(int num)     // 生产数据,num指定数据的个数。
    {
        lock_guard<mutex> lock(m_mutex);   // 申请加锁。
        for (int ii=0 ; ii<num ; ii++)
        {
            static int bh = 1;           // 编号。
            string message = to_string(bh++) + "号";    // 拼接出一个数据。
            m_q.push(message);     // 把生产出来的数据入队。
        }
        //m_cond.notify_one();     // 唤醒一个被当前条件变量阻塞的线程。
        m_cond.notify_all();          // 唤醒全部被当前条件变量阻塞的线程。
    }

    void outcache()   {    // 消费者线程任务函数。
        while (true)   {
            // 把互斥锁转换成unique_lock<mutex>,并申请加锁。
            unique_lock<mutex> lock(m_mutex);

            // 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。
            //while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
            //    m_cond.wait(lock);  // 1)把互斥锁解开;2)阻塞,等待被唤醒;3)给互斥锁加锁。
            m_cond.wait(lock, [this] { return !m_q.empty(); });

            // 数据元素出队。
            string message = m_q.front();  m_q.pop();
            cout << "线程:" << this_thread::get_id() << "," << message << endl;
            lock.unlock();      // 手工解锁。

            // 处理出队的数据(把数据消费掉)。
            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。
        }
    }
};

int main()
{
    AA aa;

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。
    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。
    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。

    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。
    aa.incache(2);      // 生产2个数据。

    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。
    aa.incache(5);      // 生产5个数据。

    t1.join();   // 回收子线程的资源。
    t2.join();
    t3.join();
    return 0;
}

244、原子类型atomic

C++11提供了atomic<T>模板类(结构体),用于支持原子类型,模板参数可以是bool、char、int、long、long long、指针类型(不支持浮点类型和自定义数据类型)。

原子操作由CPU指令提供支持,它的性能比锁和消息传递更高,并且,不需要程序员处理加锁和释放锁的问题,支持修改、读取、交换、比较并交换等操作。

头文件:#include <atomic>

构造函数:

atomic() noexcept = default;  // 默认构造函数。

atomic(T val) noexcept;  // 转换函数。

atomic(const atomic&) = delete;  // 禁用拷贝构造函数。

赋值函数:

atomic& operator=(const atomic&) = delete;   // 禁用赋值函数。

常用函数:

void store(const T val) noexcept;   // 把val的值存入原子变量。

T load() noexcept;  // 读取原子变量的值。

T fetch_add(const T val) noexcept; // 把原子变量的值与val相加,返回原值。

T fetch_sub(const T val) noexcept; // 把原子变量的值减val,返回原值。

T exchange(const T val) noexcept; // 把val的值存入原子变量,返回原值。

T compare_exchange_strong(T &expect,const T val) noexcept; // 比较原子变量的值和预期值expect,如果当两个值相等,把val存储到原子变量中,函数返回true;如果当两个值不相等,用原子变量的值更新预期值,函数返回false。CAS指令。

bool is_lock_free();  // 查询某原子类型的操作是直接用CPU指令(返回true),还是编译器内部的锁(返回false)。

原子类型的别名:

 

注意:

  1. atomic<T>模板类重载了整数操作的各种运算符。
  2. atomic<T>模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。
  3. 原子整型可以用作计数器,布尔型可以用作开关。
  4. CAS指令是实现无锁队列基础。

示例:

#include <iostream>
#include <atomic>     // 原子类型的头文件。
using namespace std;

int main()
{
      atomic<int> a = 3;       // atomic(T val) noexcept;  // 转换函数。
      cout << "a=" << a.load() << endl;   // 读取原子变量a的值。输出:a=3
      a.store(8);      // 把8存储到原子变量中。
      cout << "a=" << a.load() << endl;   // 读取原子变量a的值。 输出:a=8

      int old;        // 用于存放原值。
      old = a.fetch_add(5);         // 把原子变量a的值与5相加,返回原值。
      cout << "old = " << old <<",a = " << a.load() << endl;   // 输出:old=8,a=13
      old = a.fetch_sub(2);         // 把原子变量a的值减2,返回原值。
      cout << "old = " << old << ",a = " << a.load() << endl;   // 输出:old=13,a=11

      atomic<int> ii = 3;  // 原子变量
      int expect = 4;         // 期待值
      int val = 5;               // 打算存入原子变量的值
      // 比较原子变量的值和预期值expect,
      // 如果当两个值相等,把val存储到原子变量中;
      // 如果当两个值不相等,用原子变量的值更新预期值。
      // 执行存储操作时返回true,否则返回false。
      bool bret = ii.compare_exchange_strong(expect, val);
      cout << "bret=" << bret << endl;
      cout << "ii=" << ii << endl;
      cout << "expect=" << expect << endl;
      return 0;
}

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值