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时尝…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
