等待线程完成
当一个线程创建完成,我们可能需要等待他完成,以便线程的执行结果完成或执行清理操作,我们可以使用t.join()方法来 等待线程完成。
#include<iostream>
#include<thread>
#include<string.h>
using namespace std;
void printHelloworld(string msg)
{
cout << msg << endl;
}
int main()
{
thread thread1(printHelloworld,"Hello");
thread1.join();
return 0;
}
在这段代码中,如果我们不使用join函数等待线程完成,那么可能在线程未执行完成时,主函数就已经结束了,会出现以下的报错。
如果正确使用了join操作,则会等待线程完成,输出正确的结果。
detach()守护进程
- 当你希望线程在完成其任务后自动结束,而不需要主线程等待时,可以使用
detach()
。 - 调用
detach()
后,线程将不再与创建它的线程关联。这意味着线程将独立运行,直到它完成执行。
此时终端不会出现任何结果,说明这个线程在后台运行了,不需要再等待主进程的执行。
Joinable()检查线程是否还在运行
Joinable()的返回值是一个Bool类型,在我们使用时要注意返回值。
- 在调用
join()
之前,使用joinable()
可以避免对已经结束的线程调用join()
,这可能会导致异常。 - 在决定是否要
detach()
一个线程之前,可以使用joinable()
来检查线程是否还在运行。 -
异常安全:如果尝试对一个不可join的线程调用
join()
,将抛出std::system_error
异常。使用joinable()
可以避免这种情况。
ref 对引用的操作
使用 std::ref
可以自动推导被引用对象的类型,而不需要显式指定类型。
作为函数参数:当需要将引用传递给函数时,可以使用 std::ref
创建一个引用包装器,然后在函数中使用 std::cref
(用于常量引用)来传递常量引用。
避免拷贝:在某些情况下,直接传递对象可能导致不必要的拷贝。使用 std::ref
可以避免这种情况,因为它只是包装了一个引用。
mutex互斥量
在C++中,互斥量(mutex)是一种同步原语,用于防止多个线程同时访问共享资源,从而避免数据竞争和一致性问题。C++11标准在 <mutex>
头文件中引入了互斥量的实现。
include<iostream>
#include<thread>
#include<mutex>
using namespace std;
int a = 0;
mutex mtx;
void func()
{
for (int i = 0;i < 10000;i++)
{
//mtx.lock();
a += 1;
//mtx.unlock();
}
}
int main()
{
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << a << endl;
return 0;
假设我们创建两个线程,同时对a进行+1的操作,如果不加mutex给变量上锁,线程就会对同一个变量不停的自增,导致得不到相应的输出结果。如果我们进行上锁操作,则可以正确的得到我们想要的结果20000。
lock_graud()锁定互斥量
lock_guard
的语法非常简洁,主要用于自动管理互斥量的加锁和解锁操作。
std::mutex mtx; // 声明一个互斥量对象
std::lock_guard<std::mutex> lk(mtx); // 锁定 mtx // lk 被销毁,mtx 自动解锁
其中lk是一个新创建的实例对象,mtx为互斥量对象。
-
特点:
std::lock_guard
不拥有互斥量,它只是管理互斥量的锁定状态。- 它不允许转移所有权,即不能复制或移动
std::lock_guard
对象。 - 它通常用于保护临界区代码,确保线程安全。
int a = 0; mutex mtx; void func() { for (int i = 0;i < 10000;i++) { lock_guard<mutex> lg(mtx); a++; } } int main() { thread t1(func); thread t2(func); t1.join(); t2.join(); cout << a << endl; return 0; }
unique_lock()
std::unique_lock 是 C++11 中的一个互斥量锁定器,它提供了灵活的锁定和解锁方式,支持超时等待和可中断等特性。与 std::lock_guard 不同,std::unique_lock 可以在构造函数中选择是否锁定,以及在析构函数中选择是否解锁,从而提供了更多的控制和灵活性。
unique_lock控制作用域内的互斥体所有权。互斥体的所有权可以延迟到构造之后,并且可以通过移动构造或移动赋值(move construction or move assignment)将其转移到另一个unique_lock。如果析构函数运行时拥有互斥锁,则所有权将被释放。
Call_once函数
std::call_once
是 C++11 标准库中的一个函数,用于确保在多线程环境中某个函数或操作只被执行一次。它与 std::once_flag
结合使用,后者是一个特殊的标志,用来指示某个操作是否已经被执行过。
std::once_flag
是一个标记,用来与 std::call_once
一起工作,确保某个操作只执行一次。
语法:
std::once_flag flag;
std::call_once(flag, []{ // 只执行一次的代码 });
#include <iostream>
#include <mutex>
#include <thread>
std::once_flag flag;
int global_resource = 0;
void initialize_resource() {
global_resource = 42; // 模拟资源初始化
}
void thread_function() {
std::call_once(flag, initialize_resource);
std::cout << "Resource value: " << global_resource << std::endl;
}
int main() {
std::thread t1(thread_function);
std::thread t2(thread_function);
t1.join();
t2.join();
return 0;
}
在这个示例中,initialize_resource
函数只会被执行一次,即使 thread_function
被多个线程调用。std::call_once
确保了 global_resource
被初始化为 42,并且所有线程都可以看到这个初始化值。
Condition Variable机制
条件变量(condition variable)是一种同步机制,用于在多线程编程中等待某个条件的发生。它允许一个或多个线程在某些条件不满足时挂起(阻塞),直到另一个线程使条件得到满足并通知条件变量。条件变量通常与互斥量(mutex)一起使用,以确保线程安全。
线程可以使用 wait
函数在互斥量锁定的情况下挂起,直到另一个线程通知条件变量。
std::unique_lock<std::mutex> lk(mtx);
// 等待条件满足
cv.wait(lk, []{ return condition; });
通知条件变量:
- 当条件满足时,另一个线程可以使用
notify_one
唤醒等待条件变量的一个线程,或者使用notify_all
唤醒所有等待的线程。{ std::lock_guard<std::mutex> lk(mtx); // 改变条件 condition = true; cv.notify_one(); // 或 cv.notify_all(); }
-
使用
std::unique_lock
:- 与
std::lock_guard
不同,std::unique_lock
允许在等待条件变量时释放互斥量的锁,并在唤醒时重新获得锁。 - 如果在等待期间发生异常,
std::unique_lock
将自动释放互斥量。
- 与
异步编程和并发执行
std::async
std::async
是一个函数模板,用于启动一个新线程来异步执行一个给定的任务(函数或函数对象),并立即返回一个 std::future
对象,该对象可以用于访问任务的结果。
- 用法:
std::async
可以接受一个函数和任意数量的参数来执行该函数。 - 返回:返回一个
std::future
对象,该对象持有异步操作的结果。 - 执行策略:默认情况下,
std::async
可能会启动一个新的线程来执行任务,但具体行为取决于特定的执行策略和传递给std::async
的参数。
std::future
std::future
是一个模板类,用于表示异步操作的结果。它可以被用作访问由 std::async
、std::packaged_task
或其他异步操作产生结果的代理。
- 获取结果:
std::future
提供了get
方法来获取异步操作的结果。如果结果尚未准备好,get
方法将阻塞调用线程直到结果可用。 - 一次性:一旦结果被
get
方法检索,std::future
对象就不能再被用来获取结果了。 - 共享结果:
std::future
可以被移动,但不能被复制,这确保了结果只被检索一次
在这段代码中,使用#include<future> int func() { int i = 0; for (i = 0;i < 1000;i++) { i++; } return i; } int main() { future<int> future_result = async(launch::async, func); cout << func() << endl; cout << future_result.get() << endl; return 0; }
std::async
时,std::launch
是一个枚举类型,用作std::async
的策略参数,指示异步操作的启动方式。std::launch::async
是std::launch
枚举的一个成员,它指示std::async
应该在新线程上启动异步任务,最后再接受一个函数的地址作为参数。
而future<int> 则是一个模板类,用于接受async的返回值。
在这段代码中
future<int> future_result = async(launch::async, func);
运行这一行的时候,相应的值就已经存储到future_result里面了,也就是说我们只需要调用future_result.get()函数,就可以去查看相应的值。
运行结果应为两个1000。
packaged_task封装
std::packaged_task
是 C++ 标准库中的一个模板类,它封装了一个可调用对象,比如函数、lambda 表达式或函数对象,并且能够使这个封装的可调用对象像一个线程那样被 std::thread
所执行。
调用packaged_task<int()> task(func)
生成一个task的封装对象,封装内容为func函数。
使用task.get_future()获取封装的内容,最后再直接调用thread线程(由于task是一个可移动对象,所以要使用move使其转换为右值)。
最后就可以正常输出func函数的内容。
Promise获取另一个线程值
std::promise
是 C++ 标准库中的一个模板类,用于在线程间传递值。当你创建一个 std::promise
对象时,你可以在其中一个线程中设置一个值,然后在另一个线程中使用 std::future
对象来获取这个值。
-
创建
std::promise
对象:创建时指定你想要传递的值的类型。 -
设置值:使用
promise
对象的set_value
方法来设置一个值。 -
获取值:使用
std::promise
返回的std::future
对象来获取设置的值。#include<future> void func(promise<int> f) { f.set_value(1000); } int main() { promise<int> f; auto future_result = f.get_future(); thread t1(func, move(f)); t1.join(); cout << future_result.get() << endl; }
在这里,我们设置了一个promise<int> f 作为func函数的参数,并设置值为1000(生成子线程);同时在主线程中,使用future_result=f.get_future()来获取其生成值,最后直接调用thread来生成相应的线程输出,最后正确在主线程中得到的子线程设置的值1000。
std::promise
是实现线程间值传递的一种有效机制,特别是当需要从子线程向主线程返回结果时。通过 std::promise
,你可以确保值的传递是安全的,并且可以在适当的时机进行。