C++11多线程
多线程共享全局变量
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。
<atomic>:
该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。<thread>:
该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。<mutex>:
该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。<condition_variable>:
该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。<future>:
该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
无参函数:
#include <iostream> #include <thread> //多线程头文件 #include <chrono> void hello() { for (int i = 0; i < 30; i++) { std::cout << "子线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); //本线程休眠2秒 } } int main() { std::thread t(hello);//创建线程并启动 //t 线程名 //参数:线程要执行的函数名--无参函数 t.join(); //t线程结束,再往下执行 for (int i = 0; i < 30; i++) { std::cout << "主线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); } return 0; }
有参函数 匿名函数 类成员函数:
#include <iostream> #include <thread> #include <chrono> void func1() { for (int i = 0; i != 10; ++i) { std::cout << "thread 1 print " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } void func2(int n) { for (int i = 0; i != 10; ++i) { std::cout << "thread 2 print " << n << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } class A { public: void print() { for (int i = 0; i != 10; ++i) std::cout << "类成员函数:" <<i<< std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } }; int main() { std::thread t1(func1); std::thread t2(func2, 111); //有参函数 //参数2:函数的参数 std::thread t3([]()->void{std::cout << "匿名函数" << std::endl; }); //使用匿名函数 A a; std::thread t4(&A::print, &a); //使用类的普通成员函数 //A 类名 //print 函数名 // a 类A的对象 t1.join(); t2.join(); for (int i = 0; i < 30; i++) { std::cout << "主线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } return 0; }
形参是引用的函数:
#include <iostream> #include <thread> //多线程头文件 #include <chrono> void fl(int& x) { for (int i = x - 1; i > 0; i--) { std::cout << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { int a = 20; //std::thread t(fl, a); 错 //提示错误:未能使函数模板“unknown-type std::invoke(_Callable &&,_Ty1 &&,_Types2 &&...) noexcept(<expr>)” //错误原因:即使默认参数是引用形式,也会拷贝到线程独立内存中 //解决办法:需要std::ref将参数转换为引用的形式----不同线程之间传递引用 std::thread t(fl, std::ref(a)); //对 形参是引用的函数 for (int i = 0; i < 30; i++) { std::cout << "主线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } return 0; }
joinable()
#include <iostream> #include <thread> #include <chrono> void hello() { for (int i = 0; i < 30; i++) { std::cout << "子线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t(hello); bool b = t.joinable(); //返回能否调用join()函数 //每个线程join()函数只能调用一次 if (b) { std::cout << "b=" << b << std::endl; t.join(); } for (int i = 0; i < 30; i++) { std::cout << "主线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); b = t.joinable(); std::cout << "b=" << b << std::endl; } return 0; }
转移线程所有权【控制权】move
#include <iostream> #include <thread> #include <chrono> void hello() { for (int i = 0; i < 30; i++) { std::cout << "子线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t(hello); std::thread t1; t1= move(t); //转移线程所有权【控制权】 //hello函数到t1线程执行,t线程变成空线程 for (int i = 0; i < 30; i++) { std::cout << "主线程:" << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); } return 0; }
thread::hardware_concurrency 获取硬件支持并发线程数
unsigned int in = std::thread::hardware_concurrency();//返回支持的并发线程数,若值非良定义或不可计算,则返回 0 std::cout << in << std::endl;
用户创建的线程数最好是:线程数目 = cpu核数+1
获取线程id
#include <iostream> #include <thread> #include <chrono> void hello() { for (int i = 0; i < 30; i++) { std::thread::id d1 = std::this_thread::get_id(); std::cout << "子线程id=" << d1 << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread::id d= std::this_thread::get_id(); //获取当前线程id std::cout <<"主线程id=" <<d << std::endl; std::thread t(hello); std::thread::id d2=t.get_id(); //获取指定线程id std::cout << "主线程获取子线程id=" << d2 << std::endl; t.join(); return 0; }
detach线程分离
#include <thread>
#include <iostream>
#include <chrono>
using namespace std;
void func()
{
cout << "子线程func开始执行!" << endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
cout << "子线程func执行结束!" << endl;
}
int main()
{
cout << "主线程main开始执行!" << endl;
thread t(func);
t.detach(); //指定线程与主线程分离
/*
detach()的作用是将子线程和主线程的关联分离,也就是说detach()后
子线程在后台独立继续运行,主线程无法再取得子线程的控制权,
主线程结束,子线程也结束
*/
cout << "主线程main执行结束!" << endl;
return 0;
}
pthread_exit(NULL) ; //退出主线程
#include <thread>
#include <iostream>
#include <chrono>
using namespace std;
void func()
{
cout << "子线程func开始执行!" << endl;
for(int i=0;i<30;i++){
cout<<"i="<<i<<endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
cout << "子线程func执行结束!" << endl;
}
int main()
{
cout << "主线程main开始执行!" << endl;
thread t(func);
cout << "主线程main执行结束!\n" ;
pthread_exit(NULL) ; //退出主线程
//这条语句的作用:主线程结束后,子线程继续执行
//仅仅是主线程结束,进程不会结束,进程内的其他线程也不会结束,直到所有线程结束,进程才会终止
return 0;
}
互斥锁mutex
需要:#
方法一:lock unlock方法--手动锁
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; //创建互斥锁对象 void func(const std::string& str) { for (int i = 0; i < 30; i++) { mu.lock(); //上锁,防止别的线程进入 //这种方法的缺陷:如果这句话抛出异常,mu永远会被锁住,所以不推荐 std::cout << "子线程:" << str<<"--->"<<i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); mu.unlock(); //解锁,别的线程可以进入了 } } int main() { std::thread t(func,"t线程"); std::thread t1(func, "t1线程"); std::thread t2(func, "t2线程"); std::thread t3(func, "t3线程"); std::thread t4(func, "t4线程"); t.join(); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
方法二:lock_guard方法--作用域锁
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; void func(const std::string& str) { for (int i = 0; i < 30; i++) { std::lock_guard<std::mutex> guard(mu); //上锁,防止别的线程进入 //当此句异常,mu对象自动被解锁 //其原理是:声明一个局部的lock_guard对象,在定义该局部对象的时候加锁,出了该对象作用域的时候解锁 //在需要被加锁的作用域内 将mutex传入到创建的std::lock_guard局部对象中 std::cout << "子线程:" << str<<"--->"<<i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t(func,"t线程"); std::thread t1(func, "t1线程"); std::thread t2(func, "t2线程"); std::thread t3(func, "t3线程"); std::thread t4(func, "t4线程"); t.join(); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
方法三:unique_lock
这种方法的第二参数有三个选项
第二参数:defer_lock
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; void func(const std::string& str) { std::unique_lock<std::mutex> locker(mu, std::defer_lock);//创建unique_lock锁对象 /* locker: 是unique_lock的锁对象名 参数1:互斥锁对象名 参数2:3个选其一 1.std::defer_lock:互斥锁还没有上锁,如果已经上锁报错 有两个成员函数来进行加锁或解锁:lock(),加锁 unlock(),解锁 可以在不同的地方多次加锁和解锁--比较灵活 */ for (int i = 0; i < 30; i++) { locker.lock();//上锁 for (int j = 0; j < 5; j++) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "子线程--上锁:" << str << "--->" << j << std::endl; } locker.unlock(); //解锁 for (int j = 0; j < 10; j++) { std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "子线程--不上锁:" << str << "--->" << j << std::endl; } } } int main() { std::thread t(func,"t线程"); std::thread t1(func, "t1线程"); std::thread t2(func, "t2线程"); std::thread t3(func, "t3线程"); std::thread t4(func, "t4线程"); t.join(); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
第二参数:adopt_lock
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; void func(const std::string& str) { mu.lock(); std::unique_lock<std::mutex> lckkkk(mu, std::adopt_lock); //格式一 //std::lock_guard<std::mutex> lckkkk(mu, std::adopt_lock);//把lock()转为lock_guard【作用域锁】--格式二 //后面的意思与lock_guard【作用域锁】完全相同;注意:要提前用lock()锁定 //lckkkk 【随便起名】 for (int j = 0; j < 15; j++) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "子线程:" << str << "--->" << j << std::endl; } } int main() { std::thread t(func,"t线程"); std::thread t1(func, "t1线程"); std::thread t2(func, "t2线程"); std::thread t3(func, "t3线程"); std::thread t4(func, "t4线程"); t.join(); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
第二参数:try_to_lock
方式一
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; void func(const std::string& str) { std::unique_lock<std::mutex> lckkkk(mu, std::try_to_lock); //尝试用lock去锁定 //会尝试用lock()去锁定,但如果没有锁定成功,也会立即返回,并不会阻塞在那里 //用这个try_to_lock的前提是你自己不能先lock //lckkkk 【随便起名】a if (lckkkk.owns_lock()) { //如果锁定了 for (int j = 0; j < 15; j++) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "子线程:" << str << "--->" << j << std::endl; } } else { std::cout << str << "没锁定" << std::endl; } } int main() { std::thread t(func,"t线程"); t.join(); return 0; }
方式二
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <string> std::mutex mu; void func(const std::string& str) { std::unique_lock<std::mutex> lckkkk(mu, std::defer_lock); if (lckkkk.try_lock() == true) { //尝试用lock去锁,返回true表示拿到锁了 for (int j = 0; j < 15; j++) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "子线程:" << str << "--->" << j << std::endl; } } else { std::cout << str << "没锁定" << std::endl; } } int main() { std::thread t(func,"t线程"); t.join(); return 0; }
线程间数据共享future
线程间数据共享一般可以使用全局变量,但是从安全角度看,有些不妥;为此C++11提供了std::future类模板
async()、get()
#include <future> //这个类已经封装了多线程 #include <iostream> #include <chrono> int func1(int x) //线程函数 { std::this_thread::sleep_for(std::chrono::seconds(4)); std::cout << "func1" << std::endl; return x + 100; //线程函数的返回值会保存到future对象 } int main() { std::future<int> fut; //创建future对象 /* <int> 是线程函数返回值的类型,就是要获取的数据类型 */ fut = std::async(func1, 86); //创建线程 //任务创建之后,std::async立即返回一个std::future对象 //参数1:线程要执行的函数名 //参数2:传递给线程函数的实参 int ret = fut.get(); //获取线程函数的返回值 //如果线程函数还在任务中,会处于阻塞状态,等待线程函数的返回值 std::cout << "ret="<<ret << std::endl; return 0; }
wait_for()
#include <future> #include <iostream> #include <chrono> int func1(int x) { std::cout << "func1" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(30)); std::cout << "func1" << std::endl; return x + 100; } int main() { std::future<int> fut; fut = std::async(func1, 86); std::chrono::seconds ms(1); //std::future_status st = fut.wait_for(ms); //等待线程函数的返回值 /* 参数:最多等待时间,超过这个时间继续往下执行 在指定时间内一旦线程函数返回值了,立即往下执行 注意:参数是std::chrono对象 返回值:在指定时间内线程函数返回值了,则返回std::future_status::ready状态 在指定时间内线程函数没有返回值,则返回std::future_status::timeout状态
也可以用wait()函数,但是不能设置等待时间
也可以wait_until(等待直到某特定时间点到达) */ while (fut.wait_for(ms)== std::future_status::timeout) { std::cout << "."; } std::cout << std::endl; std::cout<<"线程函数完成任务了"<< std::endl; return 0; }
promise
#include <future> #include <iostream> #include <chrono> #include <thread> void func(std::future<int>& fut) { int x = fut.get(); // 等待直到获取共享状态的值 std::cout << "x=" << x << std::endl; } int main() { /* std::promise的作用就是提供一个不同线程之间的数据同步机制, 它可以存储一个某种类型的值,并将其传递给对应的future对象, 即使这个future对象不在同一个线程中也可以安全的访问到这个值 */ std::promise<int> prom; //创建promise对象 //<int> 是共享数据的类型 std::future<int> fut = prom.get_future(); //和future 关联 std::thread t(func, std::ref(fut)); // 将 future 交给另外一个线程t std::this_thread::sleep_for(std::chrono::seconds(10)); prom.set_value(10); // 设置共享状态的值, 这个值也自动传递给捆绑的future对象 t.join(); return 0; }
std::packaged_task
主要是调用包装函数的
#include <iostream> #include <future> #include <chrono> #include <functional> int Test_Fun(int a, int b, int &c) { std::this_thread::sleep_for(std::chrono::seconds(5)); c = a + b + 230; return c; } int main() { std::packaged_task<int(int, int, int&)> pt1(Test_Fun); //声明一个packaged_task对象pt1,并指向包装函数Test_Fun //<int(int, int, int&) 包装函数的返回值类型和形参类型 std::future<int> fu1 = pt1.get_future(); //future对象fu1,并与pt1关联 //共享数据类型就是包装函数返回值类型 int c = 0; std::thread t1(std::move(pt1), 1, 2, std::ref(c)); //创建一个线程t1,执行pt1函数 int iResult = fu1.get(); //等待线程函数返回共享数据 std::cout << "执行结果:" << iResult << std::endl; std::cout << "c:" << c << std::endl; return 0; }
调用包装得到匿名函数
#include <iostream> #include <future> #include <thread> int main () { std::packaged_task<int(int)> bar([](int x){return x*2;}); //创建packaged_task对象,并指向匿名函数 std::future<int> ret = bar.get_future(); std::thread(std::move(bar), 10).detach(); // 产生线程,调用被包装的任务. int value = ret.get(); // 等待任务完成并获取结果. std::cout << "The double of 10 is " << value << ".\n"; return 0; }
shared_future
#include <iostream> #include <future> int do_get_value() { return 10; } int main () { std::future<int> fut = std::async(do_get_value); std::shared_future<int> shared_fut = fut.share(); //创建shared_future对象,并从future对象获取数据 // 共享的shared_future对象可以被多次访问. //future对象只能访问一次,在那之后future对象就处于无效状态 //获取数据后, fut变为无效 bool b=fut.valid(); if (b){ std::cout << "有效的" << '\n'; } else{ std::cout << "无效的" << '\n'; } std::cout << "value: " << shared_fut.get() << '\n'; //第一次访问 std::cout << "its double: " << shared_fut.get()*2 << '\n'; //第二次访问 return 0; }
valid()返回future对象是否有效
#include <iostream> // std::cout #include <future> // std::async, std::future int do_get_value() { return 11; } int main () { std::future<int> foo,bar; //初始化之前是无效的 bool b=foo.valid(); //返回future对象是否有效 if (b){ std::cout << "foo是:初始化之前有效的" << '\n'; } else{ std::cout << "foo是:初始化之前无效的" << '\n'; } foo = std::async(do_get_value); //初始化后变为有效 b=foo.valid(); if (b){ std::cout << "foo是:初始化之后有效的" << '\n'; } else{ std::cout << "foo是:初始化之后无效的" << '\n'; } bar=std::move(foo); //转移所有权 //foo 变为无效 bar变为有效 b=foo.valid(); if (b){ std::cout << "foo是:所有权转移之后有效的" << '\n'; } else{ std::cout << "foo是:所有权转移之后无效的" << '\n'; } b=bar.valid(); if (b){ std::cout << "bar是:所有权转移之后有效的" << '\n'; } else{ std::cout << "bar是:所有权转移之后无效的" << '\n'; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)