线程池-入门
线程池
1. 创建线程 thread
#include <thread>
int main(){
std::thread th()
}
thread 直接创建一个线程,参数是所需执行的函数。
2. join
当开启线程后,主线程不会等待其他线程执行完后再关闭,所以需要让主线程去等待其他线程执行完之后,再关闭主线程。
可通过 joinable 判断该线程是否是可 join 的。添加判断条件
th.join();
if(th.joinable()){
th.join();
}
3. detach
主线程不等待主线程执行结束,且主线程结束之后,子线程依然可以执行。
当使用detach之后,主线程和子线程分离,子线程在后台依旧可以运行。
4. std::ref
可以将一个临时变量、局部变量转换成引用变量。
#include <iostream>
#include <thread>
#include <mutex>
#include <functional>
void modify(int& num){
num *= 2;
}
int main(){
int num = 3;
std::thread th(modify, std::ref(num));
std::cout << "num = " << num << std::endl;
system("pause");
}
5. thread 绑定成员函数
倘若现在存在一个类,其中有一个成员函数 func
class A{
public:
void func(){
std::cout << "A::func()" << std::endl;
}
}
使用thread去绑定func的时候,
std::thread th(&A::func);
//上面这句是不对的,除了绑定类::成员函数之外,因为成员函数还包含一个隐含的this指针,所以需要再后面添加一个对象的指针。
//可以创建出一个对象,可以new,也可以使用make_shared创建一个智能指针。
std::shared_ptr<A> pa = std::make_shared<A>();
std::thread th(&A::func, pa);
如果 A中的func 的访问修饰符从 public 改成 private,则 thread绑定就绑定不到该成员函数。
可以在类A中将执行thread 的函数声明成友元函数。
class A{
friend void func();
// 或者直接将thread相关的语句写在main中,声明main函数为友元
friend int main();
private:
void func(){
std::cout << "A::func()" << std::endl;
}
}
void func(){
std::shared_ptr<A> pa = std::make_shared<A>();
std::thread th(&A::func, pa);
}
int main(){
func();
}
6. 互斥锁
互斥锁本质上就是为了解决多线程访问共享变量时所发生的问题,当多个线程同时访问同一个变量的时候,对该变量进行的操作在同一时刻被视为重合的,因此会曲解部分操作内容。
比如两个线程同时对一个为 0 的变量各进行一万次的+1操作,当结果出来时,理想结果应该是20000,但最终可能是不到20000的数据,因为中间部分操作发生了冲突。也就是存在竞态条件问题。竞态条件指的是多线程并发执行操作时,程序结果依赖于线程的执行顺序,因此导致结果是不确定性的。
使用互斥锁,mutex 可以进行上锁可解锁。在一个线程对共享数据进行操作时,进行上锁。当操作结束时,进行解锁。在上锁和解锁中间的这块区域被称为临界区,临界区如果过大,则会有线程饥饿的问题。临界区如果过小,则会有频繁上锁解锁的等待问题。因此临界区的大小设计要合理。
上锁可以理解为获取一个资源的所有权。
解锁可以理解为释放一个资源的所有权。
#include <mutex>
int main(){
std::mutex _mutex;
_mutex.lock(); // 上锁
// 临界区
mutex.unlock(); // 解锁
}
既然有互斥锁,那么不可避免的就会出现死锁问题。死锁问题的产生有多种方式,比如一个不可重入锁被重复上锁;或者当前有两把锁,AB线程各锁一把,且都等待对方的锁的释放
std::mutex mutexA;
std::mutex mutexB;
void func1(){
mutexA.lock();
mutexB.lock();
// TODO
mutexB.unlock();
mutexA.unlock();
}
void func2(){
mutexB.lock();
mutexA.lock();
// TODO
mutexA.unlock();
mutexB.unlock();
}
此时两个线程同时执行,就会出现死锁问题。
7. lock_guard
lock_guard 接收一个 mutex 参数,当接收该参数后,构造函数会默认对其进行上锁,析构函数会进行解锁,且 lock_guard 无法进行复制和移动,只能定义在局部作用域下。
且lock_guard提供了其他的构造函数,可以不上锁的一个构造函数版本。
std::lock_guard lk(mutex);
当lock_guard 所在的局部作用域释放后,自动解锁。
std::mutex mutex;
int shared_data = 0;
void func(){
for(int i=0;i<10000;i++){
std::lock_guard<std::mutex> lk(mutex);
shared_data = 0;
}
}
int main(){
std::thread th1(func);
std::thread th2(func);
th1.join();
th2.join();
system("pause");
}
8. unique_lock
unique_lock 提供了比lock_guard 更加丰富的加锁解锁功能,如 try_lock 、超时等操作。
在使用unique_lock 构造锁的时候,不对锁进行任何其他操作,
std::unique_lock<std::mutex> lk(mutex, std::defer_lock);
std::defer_lock 表示不对其进行加锁,调用其中的一个构造函数。
可以使用 try_lock 或 try_lock_for进行延时加锁。普通的锁也不支持延时操作,因此在使用try_lock_for的时候,需要使用的是一个 时间锁。
std::time_mutex mutex;
std::unique_lock<std::time_mutex> lk(mutex, std::defer_lock);
lk.try_lock_for(std::chrono::seconds(5));
当线程使用延迟锁的时候,给定的时间内拿不到共享资源就会直接返回。
9. call_once
为了防止如单例模式下,一个单例在多线程中被创建了多次对象,可以使用 call_once 来确保某个函数只会执行一次。
且call_once只能在多线程中使用。
void f(){
std::once_flag once;
std::call_once(once, func);
}
// 多线程运行该函数,call_once确保只有一个线程可以执行 func
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类