C++ Thread线程库的基本使用

等待线程完成

当一个线程创建完成,我们可能需要等待他完成,以便线程的执行结果完成或执行清理操作,我们可以使用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为互斥量对象。

  1. 特点

    • 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::asyncstd::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::asyncstd::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 对象来获取这个值。

  1. 创建 std::promise 对象:创建时指定你想要传递的值的类型。

  2. 设置值:使用 promise 对象的 set_value 方法来设置一个值。

  3. 获取值:使用 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,你可以确保值的传递是安全的,并且可以在适当的时机进行。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值