c++多线程
-
-
- lambda的简单说明
-
- [demo1 函数指针](#demo1 函数指针)
- [demo2 仿函数 (函数对象)](#demo2 仿函数 (函数对象))
- [demo3 lambda表达式](#demo3 lambda表达式)
- std::mutex
- std::atomic
- thread_local
- async
- 创建
- 执行结果
- 注意事项
-
lambda的简单说明
lambda的格式:
[捕获列表]<模板声明>(参数列表)mutable 异常说明->类型{函数体}
auto b = [](int a, int b) { return a + b; };
cpp
[]<class T>(T v) noexcept -> void{
cout << v << endl;
}("blog.csdn.net");
//捕获列表 函数体必写 其他可省略 // 最短的lambda函数
auto b = [] { return 1; };
1.主线程在 t.join()
处阻塞,等待t线程执行完毕
2.std::thread::joinable()
用于检查 std::thread
是否可联结(joinable)joinable()
返回 false
:线程对象未关联线程(未初始化),或线程已被 join()
或 detach()
。 joinable()返回
true:线程对象关联了一个有效的线程,且尚未
join()或
detach()。
demo1 函数指针
thread t = thread(printMessage, 5);这行代码会立即启动一个新线程,执行传入的方法。这里和java不一样
c++
#include<iostream>
#include<thread>
using namespace std;
void printMessage(int count) {
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (function pointer)!\n";
}
}
int main()
{
thread t = thread(printMessage, 5);
t.join();// 等待线程完成
return 0;
}
demo2 仿函数 (函数对象)
使用对象的operator方法
c++
#include <iostream>
#include <thread>
class PrintTask {
public:
void operator()(int count) const {
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (function object)!\n";
}
}
};
int main() {
std::thread t2(PrintTask(), 5); // 创建线程,传递函数对象和参数
t2.join(); // 等待线程完成
return 0;
}
demo3 lambda表达式
使用lambda
c++
#include<iostream>
#include<thread>
using namespace std;
int main()
{
thread t(
[](int count){
for (int i = 0; i < count; ++i) {
std::cout << "Hello from thread (lambda)!\n";
}
},
5
);
t.join();
return 0;
}
std::mutex
std::mutex
是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。
c++
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int n = 0;
mutex mtx;// 全局互斥量
void count10000() {
for (int i = 1; i <= 10000; i++) {
mtx.lock(); // 请求锁定互斥量
n++;
mtx.unlock();// 释放互斥量
}
}
int main() {
thread th[10];
for (thread &x : th)
x = thread(count10000);
for (thread &x : th)
x.join();
cout << n << endl;
return 0;
}
还有一个bool try_lock()。尝试将mutex上锁。如果mutex未被上锁,则将其上锁并返回true;如果mutex已被锁则返回false。
std::atomic
原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。
c++
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
std::atomic<int> n = 0;
void count10000() {
for (int i = 1; i <= 10000; i++) {
n++;
}
}
int main() {
thread th[100];
for (thread &x : th)
x = thread(count10000);
for (thread &x : th)
x.join();
cout << n << endl;
return 0;
}
thread_local
线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。
c++
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
using namespace std;
thread_local int n = 0;
mutex mtx; // // 加锁只是保护输出
int main(int argc, char const *argv[])
{
thread t1 =thread(
[]() {
n= 1;
mtx.lock();
std::cout<<"thread1" << n << std::endl;
mtx.unlock();
}
);
thread t2 =thread(
[]() {
n= 2;
mtx.lock();
std::cout <<"thread2" << n << std::endl;
mtx.unlock();
}
);
thread t3 =thread(
[]() {
mtx.lock();
std::cout <<"thread3" << n << std::endl;
mtx.unlock();
}
);
t1.join();
t2.join();
t3.join();
return 0;
}
async
创建
template<class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);
可以创建有返回结果的线程
thread 也是可以同步或异步的,异步detach() :将线程与主线程分离,使其独立执行。同步join():让主线程等待该线程完成。
c++
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
// #include <thread> // 这里我们用async创建线程
#include <future> // std::async std::future
using namespace std;
template<class ... Args> decltype(auto) sum(Args&&... args) {
// C++17折叠表达式
// "0 +"避免空参数包错误
return (0 + ... + args);
}
int main() {
// 注:这里不能只写函数名sum,必须带模板参数
future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
// future::get() 阻塞等待线程结束并获得返回值
cout << val.get() << endl;
return 0;
}
std::launch有2个枚举值和1个特殊值:
枚举值:launch::async | 异步启动 | |
---|---|---|
枚举值:launch::deferred | 在调用future::get、future::wait时同步启动(std::future见后文) | |
特殊值:launch::async | launch::defereed | 同步或异步,根据操作系统而定 |
c++
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
using namespace std::chrono;
std::string fetchDataFromDB(std::string recvData) {
//确保函数要5秒才能执行完成
std::this_thread::sleep_for(seconds(5));
//处理创建数据库连接、获取数据等事情
return "DB_" + recvData;
}
std::string fetchDataFromFile(std::string recvData) {
//确保函数要5秒才能执行完成
std::this_thread::sleep_for(seconds(5));
//处理获取文件数据
return "File_" + recvData;
}
int main() {
//获取开始时间
system_clock::time_point start = system_clock::now();
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
//从文件获取数据
std::string fileData = fetchDataFromFile("Data");
//从DB获取数据
//数据在future<std::string>对象中可获取之前,将一直阻塞
std::string dbData = resultFromDB.get();
//获取结束时间
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
//组装数据
std::string data = dbData + " :: " + fileData;
//输出组装的数据
std::cout << "Data = " << data << std::endl;
return 0;
}
只使用了5秒,而不是10秒
执行结果
对于执行结果:
我们可以使用get、wait、wait_for、wait_until等待执行结束
get:
get可以获得执行的结果。如果选择异步执行策略,调用get时,如果异步执行没有结束,get会阻塞当前调用线程,直到异步执行结束并获得结果,如果异步执行已经结束,不等待获取执行结果;
如果选择同步执行策略,只有当调用get函数时,同步调用才真正执行,这也被称为函数调用被延迟。
任务的结果只能被获取一次。再次调用会导致异常 std::future_error
。
wait:
功能:等待异步任务完成。阻塞当前线程,直到任务完成。不会返回任务结果。结果可以通过 get()
获取。
wait_for()
功能:等待指定的时间,检查任务是否完成。
wait_until()
功能:等待直到指定的时间点,检查任务是否完成。
等待行为:阻塞到指定的时间点,如果任务在这段时间内完成则返回;否则返回超时状态。
返回值:返回 std::future_status
枚举值,和 wait_for()
相同。
c++
//查询future的状态
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred" << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "timeout" << std::endl;
} else if (status == std::future_status::ready) {
std::cout << "ready!" << std::endl;
}
} while (status != std::future_status::ready);
std::future
获取结果的方式有三种:
- get:等待异步操作结束并返回结果
- wait:等待异步操作结束,但没有返回值
- waite_for:超时等待返回结果,还没执行完。上面示例中就是对超时等待的使用展示
注意事项
错误示范:
c++
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
#include <iomanip>
#include <vector>
void task(int i) {
std::cout << "Task"<<i<< "started" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Task"<<i<< "finished" << std::endl;
auto end_time = std::chrono::system_clock::now();
std::time_t end_time_t = std::chrono::system_clock::to_time_t(end_time);
std::cout << "Task"<<i<< "finished time: " << std::put_time(std::localtime(&end_time_t), "%F %T") << std::endl;
}
int main() {
// 记时
auto start_time = std::chrono::system_clock::now();
std::time_t start_time_t = std::chrono::system_clock::to_time_t(start_time);
std::cout << "main Start time: " << std::put_time(std::localtime(&start_time_t), "%F %T") << std::endl;
// std::vector<std::future<void>> mfus;
for (size_t i = 0; i < 10; i++)
{
//mfus.push_back( std::async(std::launch::async, task,i));
std::async(std::launch::async, task,i);
}
auto end_time = std::chrono::system_clock::now();
std::time_t end_time_t = std::chrono::system_clock::to_time_t(end_time);
std::cout << " main End time: " << std::put_time(std::localtime(&end_time_t), "%F %T") << std::endl;
return 0;
}
这10个task 会按照顺序同步执行
为什么呢?
调用了 std::async
但是没有保存返回的 std::future
对象。当一个 std::future
对象在其生命周期结束时(例如,当它离开作用域或被显式销毁时),如果它关联的任务还没有完成,那么这个 std::future
对象的析构函数会等待任务完成。
std::future
它们会在循环体的末尾被销毁,从而导致每个任务实际上是依次等待完成的。
修改就是把上面的注释打开,使用vector不让future对象去销毁
这样就可以异步了,最好的话也可以加上下面的 ,虽然是void
c++
// Wait for all tasks to complete
for (auto& fut : futures) {
fut.get(); // This will block until the task is done
}