c++ 学习笔记之多线程:线程锁,条件变量,唤醒指定线程

基于CAS线程加锁方式

CAS(Compare-And-Swap)和 mutex 都是用于实现线程安全的技术,但它们适用于不同的场景,具有不同的性能和复杂性。下面是对两者的区别和使用场景的详细解释:

CAS(Compare-And-Swap)

工作原理

CAS 是一种无锁(lock-free)的同步机制,通过原子操作来比较和交换变量的值。它的核心思想是:只有当变量的当前值等于预期值时,才将其更新为新值,否则重新尝试。这个操作通常在硬件级别支持,以确保其原子性。

优点
  1. 高性能:由于没有锁的开销,CAS 通常比互斥锁更快,尤其是在争用较少的情况下。
  2. 无锁编程:CAS 允许多线程环境下的无锁编程,避免了死锁问题。
  3. 可扩展性:在高并发场景下,CAS 能提供更好的可扩展性,因为它减少了线程间的竞争。
缺点
  1. 复杂性:编写无锁代码更复杂,容易出错,特别是在处理复杂数据结构时。
  2. ABA问题:CAS 操作可能会受到 ABA 问题的影响,即一个值被修改成另一个值后又变回原值,导致 CAS 操作误认为值没有变化。
  3. 不适合长时间操作:如果需要进行长时间操作,CAS 可能不适用,因为在高争用情况下,CAS 可能会反复失败,导致性能下降。
使用场景
  1. 短小且频繁的操作:适用于操作简短且需要高频率执行的场景,如计数器、指针交换等。
  2. 高并发环境:适用于高并发但低争用的场景,如无锁队列、栈等数据结构。

mutex(互斥锁)

工作原理

mutex 是一种基于锁的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。线程在进入临界区前必须获取锁,离开时释放锁。

优点
  1. 易于理解和使用mutex 的使用方式相对简单,易于理解和实现。
  2. 适用广泛:适用于需要保护临界区的各种场景,包括长时间的复杂操作。
  3. 解决复杂同步问题:对于复杂的共享资源访问,mutex 提供了可靠的解决方案。
缺点
  1. 性能开销:获取和释放锁的操作有一定的性能开销,尤其是在高并发环境下。
  2. 死锁风险:如果使用不当,可能会导致死锁,即两个或多个线程互相等待对方释放锁。
  3. 上下文切换开销:在高争用情况下,线程可能频繁阻塞和唤醒,导致上下文切换开销。
使用场景
  1. 复杂操作和长时间操作:适用于需要长时间保护的临界区操作,特别是复杂的共享数据结构修改。
  2. 低到中等并发环境:适用于并发度不高的环境,或者即使在高并发环境中,也能有效管理临界区的访问。
  3. 简单同步需求:对于简单的同步需求,如单个资源的访问控制,mutex 是一个可靠的选择。

总结

  • CAS:适用于短小、频繁且简单的操作,特别是在高并发但低争用的场景中,能提供更好的性能和可扩展性,但编写无锁代码更复杂。
  • mutex:适用于长时间、复杂的操作,以及需要可靠保护的临界区,易于理解和使用,但在高并发环境下性能可能不如无锁方案。

示例:

#include <iostream>
#include <thread>
#include <atomic>std::atomic<int> num(0); // 使用std::atomic定义一个原子整数void increment() {for (int i = 0; i < 1000; ++i) {int expected = num.load();while (!num.compare_exchange_weak(expected, expected + 1)) {// 如果CAS失败,expected会被更新为当前的值,继续尝试}}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value of num: " << num << std::endl;return 0;
}

条件变量:std::condition_variable

条件变量的工作原理

  1. 线程等待 (wait)

    • 当线程调用 wait 方法时,它会自动释放与条件变量相关联的互斥锁,并进入等待状态。
    • 线程在等待期间会阻塞,直到被其他线程通知。
    • 重要的是,wait 方法会确保线程在被唤醒时会重新获得互斥锁,这样它可以安全地检查和更新条件。
  2. 线程通知 (notify_allnotify_one)

    • 当某个线程改变了共享状态(即更新了条件)后,它会调用 notify_allnotify_one 方法来通知其他线程。
    • notify_all 会唤醒所有在条件变量上等待的线程,而 notify_one 只会唤醒一个线程(如果有多个线程等待)。

来控制多个线程的执行顺序。以下是一个示例代码,实现了四个线程按照指定顺序(2 -> 1 -> 4 -> 3)循环执行:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <algorithm>std::mutex mtx;
std::condition_variable cv;
int current_thread = 2; // 初始线程编号
const std::vector<int> order = {2, 1, 4, 3}; // 执行顺序
const int num_iterations = 10; // 每个线程执行的循环次数void thread_function(int thread_id) {for (int i = 0; i < num_iterations; ++i) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [thread_id]() { return current_thread == thread_id; });// 执行线程的任务std::cout << "Thread " << thread_id << " is executing\n";// 更新当前线程编号auto it = std::find(order.begin(), order.end(), thread_id);if (it != order.end() && ++it != order.end()) {current_thread = *it;} else {current_thread = order[0];}// 唤醒所有等待线程cv.notify_all();}
}int main() {std::vector<std::thread> threads;// 创建四个线程for (int i = 1; i <= 4; ++i) {threads.emplace_back(thread_function, i);}// 等待所有线程完成for (auto& t : threads) {t.join();}return 0;
}

notify_allnotify_one 的区别

  1. notify_all:

    • 功能:唤醒所有在条件变量上等待的线程。
    • 使用场景:当条件变化时,你希望所有等待的线程都能被唤醒并检查条件。例如,当一个共享资源的状态发生变化,并且所有等待的线程都需要重新检查状态时,使用 notify_all 是合适的。
    • 缺点:如果有大量线程等待,并且每次通知都会唤醒所有线程,可能会导致性能问题,因为所有线程都会被唤醒并重新竞争锁。
  2. notify_one:

    • 功能:唤醒一个在条件变量上等待的线程。如果有多个线程等待,哪个线程被唤醒是不确定的。
    • 使用场景:当条件变化时,只需要唤醒一个线程进行处理,其他线程可以继续等待。例如,当有线程处理某个任务时,你可能只需要唤醒一个线程来处理它,并且其他线程可以继续等待。
    • 优点:比 notify_all 更高效,特别是在只需唤醒一个线程的情况下,减少了不必要的线程唤醒和锁竞争。

多线程编程中,怎么唤醒指定的线程

在多线程编程中,标准的 C++ 条件变量 (std::condition_variable) 并不提供直接指定唤醒哪个特定线程的功能。std::condition_variable 提供的 notify_one()notify_all() 方法仅仅是唤醒一个或所有等待线程,并不支持精确指定具体的线程。要实现更细粒度的线程控制,你需要采用其他方法。以下是一些常见的方法来间接实现唤醒特定线程的需求:

1. 线程标识符与条件变量

你可以使用标识符或某种标志来控制哪个线程应该继续执行。这种方法不直接唤醒特定线程,但通过条件变量和额外的状态管理来实现间接的控制。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>std::mutex mtx;
std::condition_variable cv;
bool ready[4] = {false, false, false, false}; // 每个线程的准备状态void worker(int id) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [id]{ return ready[id]; }); // 等待特定条件std::cout << "Worker " << id << " is processing\n";
}void set_ready(int id) {std::lock_guard<std::mutex> lock(mtx);ready[id] = true;cv.notify_all(); // 或 notify_one(),具体取决于你的需求
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 4; ++i) {threads.emplace_back(worker, i);}std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间// 唤醒特定的线程set_ready(2); // 只唤醒 id 为 2 的线程for (auto& t : threads) {t.join();}return 0;
}

2. 使用 std::promisestd::future

std::promisestd::future 提供了另一种方式来管理线程间的同步,并可以用于将结果传递给特定线程。

#include <iostream>
#include <thread>
#include <future>
#include <vector>std::vector<std::promise<void>> promises(4);
std::vector<std::future<void>> futures;void worker(int id) {futures[id].wait(); // 等待对应的 promise 被设置std::cout << "Worker " << id << " is processing\n";
}int main() {for (int i = 0; i < 4; ++i) {futures.push_back(promises[i].get_future());std::thread(worker, i).detach();}std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间// 唤醒特定的线程promises[2].set_value(); // 唤醒 id 为 2 的线程std::this_thread::sleep_for(std::chrono::seconds(1)); // 给线程时间完成任务return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/49969.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Git多人协作开发】知识点总结

目录 知识点总结 1.创建dev分支开发 1.1在本地创建 1.1在远程创建&#xff08;推荐&#xff09; 2.远程分支和本地分支建立连接☞pull和push操作 2.1情况1 2.2情况2 2.3情况3 3.本地仓库对远程仓库的拉取pull操作 3.1情况1 3.2情况2 4.将开发分支的内容合并到远程m…

【Week-G5】适用于图像翻译的pix2pix模型-Pytorch版本

文章目录 1、基础知识1.1 图像翻译1.2 CGAN1.3 U-Net1.4 Pix2Pix 2、运行代码 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 本次主要学习Pix2Pix网络&#xff0c;常用于图像翻译&#…

CVE-2020-7248 OpenWRT libubox标记二进制数据序列化漏洞(更新中)

提要 该文档会一直处于更新当中&#xff0c;当状态为完毕后&#xff0c;才是更新完成。由于网络上关于该漏洞原理的分析文档和资源实在是太少&#xff0c;而本人关于该方向也才是刚入门&#xff0c;能力有限&#xff0c;所以复现需要的时间较长&#xff0c;需要补充和学习的东西…

用Manim实现【多边形】类的实现——[上]

用Manim实现【多边形】类的实现——[上] Polygram内容是关于不同几何图形的分类&#xff0c;特别是涉及多边形&#xff08;Polygon&#xff09;及其扩展形式&#xff0c;比如多图形&#xff08;Polygram&#xff09;。在manim中有10中特征&#xff0c;接下来5种类及其特征的解…

错误代码-130 proxy_connection_failed的解法

fiddler连接手机抓包时&#xff0c;下载证书遇到的报错 解决方法&#xff1a;关闭手机的代理服务器就可以了

Python while编程题|Python一对一辅导练习题目

逐步逼近游戏&#xff1a; 写一个程序模拟“热和冷”游戏&#xff0c;其中计算机随机选择一个1到100之间的数字&#xff0c;用户必须猜这个数字。程序应该用 while 循环持续接收用户输入&#xff0c;直到猜中为止。对每个猜测&#xff0c;程序应告诉用户是太高、太低还是正确。…

Pytorch 7多维

多维读取 numpy xy np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32)print("input data.shape", x_data.shape) x_data torch.from_numpy(xy[:, :-1])# 选择所有行不包括最后一列 y_data torch.from_numpy(xy[:, [-1]])# 选择所有行&#xff0c;只有最后一…

搜维尔科技:Cyber​​glove通过其前所未有的柔性传感器技术,带来了多年的经验、专业知识和可靠性

Cyberglove 概述 新一代数据手套技术 MoCap 手套采用了原始 CyberGlove 产品 20 年经验所建立的技术&#xff0c;产生了改进的和新的特性、能力和设计&#xff0c;非常适合动作捕捉环境。 旧与新相遇&#xff0c; Cyberglove 通过其前所未有的柔性传感器技术&#xff0c;带来…

linux自动化构建工具--make/makefile

目录 1.make/makefile介绍 1.1基本认识 1.2依赖关系、依赖方法 1.3具体操作步骤 1.4进一步理解 1.5默认设置 1.6make二次使用的解释 1.7两个文件的时间问题 1.8总是被执行 1.9特殊符号介绍 1.make/makefile介绍 1.1基本认识 make是一个指令&#xff0c;makefile是一…

mysql面试(六)

前言 本章节详细讲解了一下mysql执行计划相关的属性释义&#xff0c;以及不同sql所出现的不同效果 执行计划 一条查询语句经过mysql查询优化器的各种基于成本和各种规则优化之后&#xff0c;会生成一个所谓的 执行计划&#xff0c;这个执行计划展示了这条查询语句具体查询方…

编写训练脚本:根据stable diffusion的文档和示例代码,编写训练脚本,包括数据加载、模型定义、训练循环等

以下是一个示例的训练脚本&#xff0c;根据stable diffusion的文档和示例代码编写而成。该脚本包括了数据加载、模型定义和训练循环等部分。 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision impo…

django 小型超市库存与销售管理系统-计算机毕业设计源码46608

摘 要 随着信息技术的快速发展&#xff0c;超市库存与销售管理面临着前所未有的挑战与机遇。为了提升超市的运营效率&#xff0c;优化库存管理&#xff0c;并增强销售数据的分析能力&#xff0c;我们基于Django框架设计并开发了一套小型超市库存与销售管理系统。该系统充分利用…

运算符的运算顺序

【单目算术位关系&#xff0c;逻辑三目后赋值】 ![在这里插入图片描述] (https://i-blog.csdnimg.cn/direct/e4c8f4e22b5044a48154bf7378e3b3b3.png)

C++中的类、对象、函数、构造函数、析构函数

在C中&#xff0c;类&#xff08;class&#xff09;是用于定义对象的模板或蓝图。类中可以包含数据成员&#xff08;变量&#xff09;和成员函数&#xff08;方法&#xff09;。对象是类的实例&#xff0c;通过类定义创建的实体。构造函数和析构函数是特殊的成员函数&#xff0…

php 做一个mqtt按钮,发布触发信号

在之前博客php 做一个文件下载服务器&#xff0c;得避免跨路径工具&#xff0c;安全很重要 中加了一个按钮&#xff0c;触发物联网设备返回数据。基于mqtt开发&#xff0c;如果想知道mqtt如何搭建&#xff0c;可以看我的博客【MQTT&#xff08;1&#xff09;】服务端的搭建 效…

机器学习 | 回归算法原理——多重回归

Hi&#xff0c;大家好&#xff0c;我是半亩花海。接着上次的多项式回归继续更新《白话机器学习的数学》这本书的学习笔记&#xff0c;在此分享多重回归这一回归算法原理。本章的回归算法原理基于《基于广告费预测点击量》项目&#xff0c;欢迎大家交流学习&#xff01; 目录 一…

Air780EP模块 LuatOS开发-MQTT接入阿里云应用指南

简介 本文简单讲述了利用LuatOS-Air进行二次开发&#xff0c;采用一型一密、一机一密两种方式认证方式连接阿里云。整体结构如图 关联文档和使用工具&#xff1a;LuatOS库阿里云平台 准备工作 Air780EP_全IO开发板一套&#xff0c;包括天线SIM卡&#xff0c;USB线 PC电脑&…

产品经理-​统计数据是如何产生的(20)

在互联网当中,监测一个项目的实际情况,在产品当中,往往需要进行数据的监测,看用户的习惯,进而进行对产品进行优化,比如统计产品用户的一些行为,鼠标点击,鼠标hover,停留时长,进入,进出等 产品经理看到的数据统计一般是经历了下面几个阶段 数据埋点&#xff1a;这个阶段产品经理…

RK3568笔记四十三:MPU6050驱动开发(硬件I2C_3)

若该文为原创文章&#xff0c;转载请注明原文出处。 正点原子提供的I2C有测试ap3216c&#xff0c;SH3001等传感器&#xff0c;根据手册操作可以实现效果。 这里记录使用I2C3驱动MPU6050. 记录原因是前面有模拟I2C&#xff0c;但硬件如何使用&#xff0c;有点不是很清楚&#…

猫头虎分享:GPT-4o Mini VS GPT-3.5 Turbo 新旧对决,谁能拔得头筹?

GPT-4o Mini VS GPT-3.5 Turbo &#x1f31f; 新旧对决&#xff0c;谁能拔得头筹&#xff1f; 我们正在进入廉价语言模型的新时代 &#x1f680; 阅读时间&#xff1a;6分钟 摘要&#xff1a; 尽管 GPT-4o 功能强大&#xff0c;但我并不经常使用它。如果我正在寻找一个用于复…