C++11 thread
文章目录
- C++11 线程库
- 线程对象的构造方式
- 无参的构造函数
- 调用带参的构造函数
- 调用移动构造函数
- thread常用成员函数
- this_thread命名空间
- join && detach
- mutex
C++11 线程库
线程对象的构造方式
无参的构造函数
1、调用无参的构造函数,调用无参的构造函数创建出来的线程对象没有关联任何线程函数,即没有启动任何线程
thread t1;
2、thread提供了移动赋值函数,当后续需要让该线程对象与线程函数关联时,可以以带参的方式创建一个匿名对象,然后调用移动赋值将该匿名对象关联线程的状态转移给该线程对象
void Print(size_t n , const std::string& s)
{for (size_t i = 0; i < n; i++){std::cout << std::this_thread::get_id() << ":" << i << std::endl;}
}
int main()
{int n = 10;// 创建n个线程执行Printstd::vector<std::thread> vthd(n);size_t j = 0;for (auto& thd : vthd){// 移动赋值thd = std::thread(Print, 10, "线程" + std::to_string(j++));}for (auto& thd : vthd){thd.join();}return 0;}
场景:
实现线程池的时候, 需要先创建一批线程,但,一开始这些线程什么也不做,当有任务到来时再让这些线程来处理这些任务。
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>class ThreadPool {
public:ThreadPool(size_t threads = 4); // 构造函数,可以指定线程池的大小~ThreadPool(); // 析构解函数void enqueue(std::function<void()> task); // 添加任务到线程池private:std::vector<std::thread> workers; // 线程向量std::queue<std::function<void()>> tasks; // 任务队列队std::mutex queue<std::function<void()>> completedTasks;std::condition_variable cond; // 条件变量bool stop; // 停止标志void work(); // 线程工作函数
};// 构造函数初始化线程池
ThreadPool::ThreadPool(size_t threads) : stop(false) {for(size_t i = 0; i < threads; ++i)workers.emplace_back(std::thread(&ThreadPool::work, this));
}// 析构解函数等待所有线程完成
ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(this->mtx);this->stop = true;}cond.notify_all();for(std::thread &worker : workers) {if(worker.joinable()) {worker.join(); // 等待线程结束}}
}// 添加任务到线程池
void ThreadPool::enqueue(std::function<void()> task) {{std::unique_lock<std::mutex> lock(mtx);if(stop)throw std::runtime_error("enqueue on stopped ThreadPool");tasks.emplace(task);}cond.notify_one();
}void ThreadPool::work() {while(true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->mtx);while(tasks.empty()) {cond.wait(lock); // 如果没有任务,等待}task = std::move(tasks.front());tasks.pop();}();task(); // 执行任务}
}
在这个示例中,ThreadPool
类管理一组工作线程。构造函数创建指定数量的线程,每个线程都调用work
方法等待并执行任务。enqueue
方法用于添加新任务到队列,如果线程池已经停止,则抛出异常。每个工作线程在接收到任务后会出队列并执行它
调用带参的构造函数
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
fn
:可调用对象,比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。args...
:调用可调用对象fn时所需要的若干参数
调用移动构造函数
用一个右值线程对象来构造一个线程对象
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t3 = thread(func, 10);t3.join();return 0;
}
线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
如果创建线程对象时没有提供线程函数,那么该线程对象实际没有对应任何线程。
如果创建线程对象时提供了线程函数,那么就会启动一个线程来执行这个线程函数,该线程与主线程一起运行。
thread类是防拷贝的,不允许拷贝构造和拷贝赋值,但是可以移动构造和移动赋值,可以将一个线程对象关联线程的状态转移给其他线程对象,并且转移期间不影响线程的执行
thread常用成员函数
join ,对该线程进行等待,在等待的线程返回之前,调用join函数的线程将会被阻塞
joinable , 判断该线程是否已经执行完毕,如果是则返回true,否则返回false
detach ,将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用join函数对其进行等待
get_id , 获取该线程的id
swap , 将两个线程对象关联线程的状态进行交换
joinable函数还可以用于判定线程是否是有效的,
如果是以下任意情况,则线程无效:
采用无参构造函数构造的线程对象。(该线程对象没有关联任何线程)
线程对象的状态已经转移给其他线程对象。(已经将线程交给其他线程对象管理)
线程已经调用join或detach结束。(线程已经结束)
thread的成员函数get_id
可以获取线程的id,但该方法必须通过线程对象来调用get_id
函数
如果要在线程对象关联的线程函数中获取线程id,调用this_thread
命名空间下的get_id
函数
void func()
{cout << this_thread::get_id() << endl; //获取线程id
}
int main()
{thread t(func);t.join();return 0;
}
this_thread命名空间
yield | 当前线程“放弃”执行,让操作系统调度另一线程继续执行 |
---|---|
sleep_until | 让当前线程休眠到一个具体时间点 |
sleep_for | 让当前线程休眠一个时间段 |
线程传参
1、使用lambda
#include<thread>
#include<iostream>
#include<vector>
#include<string>int main()
{size_t n1 = 5;size_t n2 = 5;/*std::cin >> n1 >> n2;*/std::thread t1( [n1](){for (size_t i = 0; i < n1; i++){//拿到该线程的线程idstd::cout << std::this_thread::get_id() << ":" << i << " " << std::endl;}} );std::thread t2([n2](){for (size_t i = 0; i < n2; i++){//拿到该线程的线程idstd::cout << std::this_thread::get_id() << ":" << i << " " << std::endl;}} );t2.join();t1.join();return 0;
}
join && detach
启动一个线程后,当这个线程退出时,需要对该线程所使用的资源进行回收,否则可能会导致内存泄露等问题。thread库提供了两种回收线程资源的方式:
1、join
主线程创建新线程后,调用join函数等待新线程终止,当新线程终止时join
函数就会自动清理线程相关的资源
join
函数清理线程的相关资源后,thread对象与已销毁的线程就没有关系了,因此一个线程对象一般只会使用一次join
,如果一个线程对象使用多次join , 程序会崩溃
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t(func, 20);t.join();t.join(); //程序崩溃return 0;
}
2、detach
主线程创建新线程后,也可以调用detach函数将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给C++运行库,此时C++运行库会保证当线程退出时,其相关资源能够被正确回收。
使用detach的方式回收线程的资源,一般在线程对象创建好之后就立即调用detach函数。
否则线程对象可能会因为某些原因,在后续调用detach函数分离线程之前被销毁掉,这时就会导致程序崩溃。
因为当线程对象被销毁时会调用thread的析构函数,而在thread的析构函数中会通过joinable判断这个线程是否需要被join,如果需要那么就会调用terminate终止当前程序(程序崩溃)
#include <iostream>
#include <thread>
#include <vector>void worker() {std::cout << "Working..." << std::endl;// 模拟耗时操作std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Finished" << std::endl;
}int main() {std::vector<std::thread> threads;// 创建并分离多个线程for (int i = 0; i < 5; ++i) {threads.emplace_back(std::thread(worker));threads.back().detach(); // 分离线程}std::cout << "Main thread continues to run..." << std::endl;// 主线程可以继续执行其他任务,而不需要等待分离的线程// 等待所有线程完成(可选)for (auto& th : threads) {if (th.joinable()) {th.join();}}std::cout << "All threads finished" << std::endl;return 0;
}
过度使用分离线程可能会导致资源泄露,因为分离的线程将继续运行,即使主线程已经结束。因此,通常建议在可能的情况下使用join
来管理线程的生命周期。
mutex
C++11中,mutex中总共包了四种互斥量
1、std::mute
mutex锁是C++11提供的最基本的互斥量,mutex对象之间不能进行拷贝,也不能进行移动
mutex常用的成员函数:
lock | 对互斥量进行加锁 |
---|---|
try_lock | 尝试对互斥量进行加锁 |
unlock | 对互斥量进行解锁,释放互斥量的所有权 |
线程函数调用lock时,可能会发生以下三种情况:
1、如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 全局互斥锁void printBlock(int n) {mtx.lock(); // 获取锁,如果锁已经被其他线程占用,则等待for (int i = 0; i < n; ++i) {std::cout << "Thread " << std::this_thread::get_id() << " says " << i << std::endl;}mtx.unlock(); // 释放锁,其他线程可以获取该锁
}int main() {std::thread t1(printBlock, 1);std::thread t2(printBlock, 2);t1.join();t2.join();return 0;
}
mtx
是一个全局互斥锁,所以一次只能有一个线程执行printBlock
函数。这意味着即使两个线程几乎同时运行,输出也将是交错的,而不是同时打印,因为一个线程在打印时会锁定互斥锁,另一个线程必须等待
2、如果该互斥量已经被其他线程锁住,则当前的调用线程会被阻塞。
互斥锁(mutex)在多线程环境中的基本行为。互斥锁是一种同步机制,用于防止多个线程同时访问共享资源,从而避免数据竞争条件和不一致的状态。当一个线程尝试获取已经被其他线程持有的互斥锁时,该线程将被阻塞,直到互斥锁被释放
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex mtx; // 全局互斥锁void worker(int id) {// 尝试获取锁mtx.lock();std::cout << "Thread " << id << " acquired the lock" << std::endl;// 模拟耗时的工作std::this_thread::sleep_for(std::chrono::milliseconds(500));std::cout << "Thread " << id << " released the lock" << std::endl;// 释放锁mtx.unlock();
}int main() {std::thread t1(worker, 1);std::thread t2(worker, 2);// 主线程稍作等待,以便 t1 有机会获取锁std::this_thread::sleep_for(std::chrono::milliseconds(100));// 尝试在 t1 持有锁时获取锁mtx.lock();std::cout << "Main thread acquired the lock" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟主线程持有锁mtx.unlock();t1.join();t2.join();return 0;
}
3、如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
如何使用局部变量加锁?
例1:用lambda
int main()
{size_t n1 = 10000;size_t n2 = 10000;/*std::cin >> n1 >> n2;*/int x = 0;std::mutex mtx;std::thread t1([&n1, &x ,&mtx]() {for (size_t i = 0; i < n1; i++){mtx.lock();x++;//拿到该线程的线程id//std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl;mtx.unlock();}});std::thread t2([&n2, &x,&mtx]() {for (size_t i = 0; i < n2; i++){mtx.lock();//拿到该线程的线程idx++;//std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl;mtx.unlock();}});t2.join();t1.join();std::cout << x;return 0;
}
例2:
void Print1(size_t n,size_t j , const std::string& s ,std::mutex & mtx) //锁必须传引用 ,锁不支持拷贝
{for (size_t i = 0; i < j+n; i++){mtx.lock();std::cout << std::this_thread::get_id() << ":" << i << std::endl;mtx.unlock();}
}
int main()
{int n = 10;// 创建n个线程执行Printstd::vector<std::thread> vthd(n);std::mutex mtx;std::thread t1(Print1, 100, 1, "hello", ref(mtx )); //必须加ref函数 ,否则锁不能以传引用的方式传参传过去std::thread t2(Print1, 100, 100000, "world", ref(mtx));t1.join();t2.join();return 0;}
例3
void Print1(size_t n, const std::string& s, std::mutex& mtx ,int & rx) //锁必须传引用 ,锁不支持拷贝
{for (size_t i = 0; i < n; i++){mtx.lock();std::cout << std::this_thread::get_id() << ":" << i << std::endl;++rx;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(1000));}
}int main()
{std::mutex mtx;int x = 10;std::thread t1(Print1, 5, "hello", std::ref(mtx), std::ref(x));std::thread t2(Print1,5, "world", std::ref(mtx), std::ref(x));std::cout << "线程1: " << t1.get_id() << std::endl;std::cout << "线程2: " << t2.get_id() << std::endl;t1.join();t2.join();std::cout << x << std::endl;return 0;
}
线程调用try_lock时,类似也可能会发生以下三种情况:
1、如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。
2、如果该互斥量已经被其他线程锁住,则try_lock调用返回false,当前的调用线程不会被阻塞。
3、如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)
相关文章:
C++11 thread
文章目录 C11 线程库线程对象的构造方式无参的构造函数调用带参的构造函数调用移动构造函数thread常用成员函数 this_thread命名空间join && detachmutex C11 线程库 线程对象的构造方式 无参的构造函数 1、调用无参的构造函数,调用无参的构造函数创建出来的线程对象…...

rabbitmq五种模式的总结——附java-se实现(详细)
rabbitmq五种模式的总结 完整项目地址:https://github.com/9lucifer/rabbitmq4j-learning 一、简单模式 (一)简单模式概述 RabbitMQ 的简单模式是最基础的消息队列模式,包含以下两个角色: 生产者:负责发…...

Qt中基于开源库QRencode生成二维码(附工程源码链接)
目录 1.QRencode简介 2.编译qrencode 3.在Qt中直接使用QRencode源码 3.1.添加源码 3.2.用字符串生成二维码 3.3.用二进制数据生成二维码 3.4.界面设计 3.5.效果展示 4.注意事项 5.源码下载 1.QRencode简介 QRencode是一个开源的库,专门用于生成二维码&…...

Java数据结构---链表
目录 一、链表的概念和结构 1、概念 2、结构 二、链表的分类 三、链表的实现 1、创建节点类 2、定义表头 3、创建链表 4、打印链表 5、链表长度 6、看链表中是否包含key 7、在index位置插入val(0下标为第一个位置) 8、删除第一个关键字key …...
mongodb是怎么分库分表的
在构建高性能的数据库架构时,MongoDB的分库分表策略扮演着至关重要的角色,它通过一系列精细的步骤确保了数据的高效分布与访问。以下是对这一过程的详尽阐述,旨在提供一个清晰且优化过的理解框架。 确定分片键(Shard Key…...
C++自研游戏引擎-碰撞检测组件-八叉树AABB检测算法实现
八叉树碰撞检测是一种在三维空间中高效处理物体碰撞检测的算法,其原理可以类比为一个管理三维空间物体的智能系统。这个示例包含两个部分:八叉树部分用于宏观检测,AABB用于微观检测。AABB可以更换为均值或节点检测来提高检测精度。 八叉树的…...

spring boot对接clerk 实现用户信息获取
在现代Web应用中,用户身份验证和管理是一个关键的功能。Clerk是一个提供身份验证和用户管理的服务,可以帮助开发者快速集成这些功能。在本文中,我们将介绍如何使用Spring Boot对接Clerk,以实现用户信息的获取。 1.介绍 Clerk提供…...
一种动态地址的查询
背景 当我们注入一个进程,通过函数地址进行call时经常会遇到这样的一个问题。对方程序每周四会自动更新。更新后之前的函数地址就变化了,然后需要重新找地址。所以,我就使用了一个动态查询的方式。 第一步:先为需要call的函数生…...

周雨彤:用角色与生活,诠释审美的艺术
提到内娱审美优秀且持续在线的女演员,周雨彤绝对是其中最有代表性的一个。 独树一帜的表演美学 作为新生代演员中的实力派代表,周雨彤凭借细腻的表演和对角色的深度共情,在荧幕上留下了多个令人难忘的“出圈”形象。在《我在他乡挺好的》中…...

使用jks给空apk包签名
1、在平台官方下载空的apk包(上传应用时有提醒下载) 2、找到jdk目录,比如C:\Program Files\Java\jdk1.8\bin,并把下载的空包apk和jks文件放到bin下 3、以管理员身份运行cmd,如果不是管理员会签名失败 4、用cd定位到…...

500. 键盘行 771. 宝石与石头 简单 find接口的使用
500. 键盘行1 给你一个字符串数组 words ,只返回可以使用在 美式键盘 同一行的字母打印出来的单词。键盘如下图所示。 请注意,字符串 不区分大小写,相同字母的大小写形式都被视为在同一行。 美式键盘 中: 第一行由字符 "qwer…...

仙剑世界手游新手攻略 仙剑世界能用云手机玩吗
欢迎来到《仙剑世界》手游的仙侠世界!作为新手玩家,以下是一些详细的攻略和建议,帮助你快速上手并享受游戏的乐趣。 一、新手职业推荐 1.轩辕:这是一个偏辅助的职业,可以给队友提供输出加成等增益效果,不过…...
[题解]2024CCPC重庆站-小 C 的神秘图形
Sources:K - 小 C 的神秘图形Abstract:给定正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n\le 10^5) n(1≤n≤105),三进制字符串 n 1 , n 2 ( ∣ n 1 ∣ ∣ n 2 ∣ n ) n_1,n_2(|n_1||n_2|n) n1,n2(∣n1∣∣n2∣n),按如下方法…...

NPS内网穿透SSH使用手册
1、说明 nps-一款轻量级、高性能、功能强大的内网穿透代理服务器 github地址:https://github.com/ehang-io/nps 官网文档地址:https://ehang-io.github.io/nps/#/?idnps 2、服务端 下载地址:https://github.com/ehang-io/nps/releases 下…...

大幂计算和大阶乘计算【C语言】
大幂计算: #include<stdio.h> long long int c[1000000]{0}; int main() {long long a,b,x1;c[0]1;printf("请输入底数:");scanf("%lld",&a);printf("请输入指数:");scanf("%lld",&b…...

【Linux】详谈 进程控制
目录 一、进程是什么 二、task_struct 三、查看进程 四、创建进程 4.1 fork函数的认识 4.2 2. fork函数的返回值 五、进程终止 5.1. 进程退出的场景 5.2. 进程常见的退出方法 5.2.1 从main返回 5.2.1.1 错误码 5.2.2 exit函数 5.2.3 _exit函数 5.2.4 缓冲区问题补…...
Linux top 命令
作用 top 是一个实时系统监控工具,用于查看系统的资源使用情况和进程状态。 示例 以下是一些常用的 top 命令示例: top :动态显示结果,每 3 秒刷新一次。 top -d 2:动态显示结果,每 2 秒刷新一次。 top …...

Leetcode 424-替换后的最长重复字符
给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。 在执行上述操作后,返回 包含相同字母的最长子字符串的长度。 题解 可以先做LCR 167/Leetcode 03再做本题 滑动窗口&…...

《StyleDiffusion:通过扩散模型实现可控的解耦风格迁移》学习笔记
paper:2308.07863 目录 摘要 1、介绍 2、相关工作 2.1 神经风格迁移(NST) 2.2 解耦表示学习(DRL) 2.3 扩散模型(Diffusion Models) 3、方法 3.1 风格移除模块 3.2 风格转移模块 3.3 …...

Django 创建表时 “__str__ ”方法的使用
在 Django 模型中,__str__ 方法是一个 Python 特殊方法(也称为“魔术方法”),用于定义对象的字符串表示形式。它的作用是控制当对象被转换为字符串时,应该返回什么样的内容。 示例: 我在初学ModelForm时尝…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...