C++学习笔记(21)

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

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

程才会被唤醒。

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

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

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

类型)。

包含头文件:<condition_variable> 一、condition_variable 类

主要成员函数:

1)condition_variable() 默认构造函数。

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

3)condition_variable& condition_variable::operator=(const condition_variable &)=delete

禁止赋值。

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

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

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

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

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

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

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

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

二、unique_lock 类

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

unique_lock 和 lock_guard 都是管理锁的辅助类,都是 RAII 风格(在构造时获得锁,在析构时释放

锁)。它们的区别在于:为了配合 condition_variable,unique_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();

}

示例 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();

}

244、原子类型 atomic

C++11 提供了 atomic<T>模板类(结构体),用于支持原子类型,模板参数可以是 bool、char、i

nt、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)。

原子类型的别名:

注意:

 atomic<T>模板类重载了整数操作的各种运算符。

 atomic<T>模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。

 原子整型可以用作计数器,布尔型可以用作开关。

 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;

}

相关推荐
Green1Leaves27 分钟前
pytorch学习-9.多分类问题
人工智能·pytorch·学习
慕y2741 小时前
Java学习第十五部分——MyBatis
java·学习·mybatis
碣石潇湘无限路2 小时前
【AI篇】当Transformer模型开始学习《孙子兵法》
人工智能·学习
kikikidult3 小时前
(2025.07)解决——ubuntu20.04系统开机黑屏,左上角光标闪烁
笔记·ubuntu
future14123 小时前
C#每日学习日记
java·学习·c#
近津薪荼3 小时前
初学者关于数据在内存中的储存的笔记
笔记
liulilittle4 小时前
深度剖析:OPENPPP2 libtcpip 实现原理与架构设计
开发语言·网络·c++·tcp/ip·智能路由器·tcp·通信
十年编程老舅5 小时前
跨越十年的C++演进:C++20新特性全解析
c++·c++11·c++20·c++14·c++23·c++17·c++新特性
碎叶城李白5 小时前
若依学习笔记1-validated
java·笔记·学习·validated
im_AMBER5 小时前
学习日志05 python
python·学习