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;

}

相关推荐
Everglowwwwww36 分钟前
【bug】通过lora方式微调sdxl inpainting踩坑
学习·计算机视觉·ai作画·stable diffusion·bug
源代码•宸37 分钟前
Leetcode—322. 零钱兑换【中等】(memset(dp,0x3f, sizeof(dp))
c++·算法·leetcode·职场和发展·dp
初级代码游戏42 分钟前
国密起步6:GmSSL3使用SM4自定义格式加解密C++版
c++·国密·sm4
许野平44 分钟前
Rust 编译器使用的 C++ 编译器吗?
c++·rust
B.-1 小时前
Remix 学习 - @remix-run/react 中主要的 hooks
前端·javascript·学习·react.js·web
tuantuan_tech1 小时前
开放式耳机哪个好用?开放式耳机好还是入耳式耳机好?
大数据·学习·生活·旅游·智能硬件
街 三 仔1 小时前
【LabVIEW学习篇 - 25】:JKI状态机
学习·labview
六点半8881 小时前
【C/C++】涉及string类的经典OJ编程题
c语言·开发语言·c++·算法
DKPT2 小时前
数据结构之排序的基本概念
java·数据结构·笔记·学习·算法
厚学2 小时前
JetLinks物联网学习(前后端项目启动)
学习