C++11线程库指南:线程、锁、原子操作与并发编程实战

文章目录

一、线程创建

c++线程库屏蔽了linux和Windows等系统在线程上的底层差异,具有跨平台性一致性。

构造函数(需要包含头文件include):

  1. 默认构造空线程对象
  2. 初始化构造函数,传入可变参数,第一个参数为可执行函数,后面的参数为该函数的实参。
  3. 禁用拷贝构造函数,防止多个对象管理同一个线程。
  4. 移动构造,参数为右值或使用move修饰左值。

示例:

cpp 复制代码
#include <thread>
#include <iostream>
void Print(int n)
{
    for(int i = 0;i<n;i++)
        std::cout<<i<<" ";
    std::cout<<std::endl;
}
int main()
{
    std::thread t1(Print,10);
    std::thread t2(Print,10);
    t1.join();
    t2.join();
    return 0;
}

注意:需要使用join接口等待线程结束,以防主线程提前结束子线程被迫终止。

执行结果:

注意:std::thread 构造时会将传入的参数拷贝到线程内部的独立存储中,因此如果可执行函数的参数是引用类型(如int&),直接传左值会编译失败(无法将拷贝的临时值绑定到非 const 引用)。此时需要用std::ref 包装左值,让线程拷贝的是 "引用包装器" 而非原值,从而实现对原变量的引用传递。示例:

cpp 复制代码
void Print(int& n)
{
	//.....
}
int main()
{
    int n = 10
    std::thread t1(Print,ref(n));
    //......
}

可直接构造Lambda 表达式:

cpp 复制代码
std::thread t1([&](int n = 10){
    for(int i = 0;i<n;i++)
        std::cout<<i<<" ";
    std::cout<<std::endl;
});

在线程中常用的全局接口

这是一个用于多线程的接口在命名空间std::this_thread中,其中:

【1】get_id():用于获取当前线程的id。在主线程中可调用线程管理对象的成员函数get_id()获取子线程id。

cpp 复制代码
#include <iostream>
#include <thread>
void fun()
{
    std::cout<<"执行fun的线程id: "<<std::this_thread::get_id()<<std::endl;
}
int main()
{
    std::thread t1(fun);
    std::cout<<"main线程id: "<<std::this_thread::get_id()<<std::endl;
    std::cout<<"t1线程id: "<<t1.get_id()<<std::endl;
    t1.join();
    return 0;
}

注意:因为没有加锁所以输出结果是乱的。

【2】yield:让出执行权,让出 CPU,由操作系统重新调度,示例:

cpp 复制代码
// 线程A执行:
while (!data_ready) {
    std::this_thread::yield();  // 让出CPU,但仍在就绪队列中
}
// data_ready为true后继续执行

【3】sleep_until:线程休眠函数,当前线程休眠到指定的绝对时间点后再继续执行。

cpp 复制代码
    //需要添加头文件<chrono>,chrono是一个时间处理库
    using namespace std::chrono;
    // 获取当前时间点
    auto now = system_clock::now();
    // 设置3秒后的时间点
    auto wakeup_time = now + seconds(3);
    
    // 休眠直到指定时间点
    std::this_thread::sleep_until(wakeup_time);

【4】sleep_for:线程休眠函数,当前线程休眠指定的相对时长后再继续执行。

例如:

cpp 复制代码
//休眠1秒
std::this_thread::sleep_for(std::chrono::seconds(1));

二、锁

2.1 mutex

这样一段程序实现使用两个线程对一个变量进行改变:

cpp 复制代码
#include <iostream>
#include <thread>
int x = 0;
void func(int n)
{
    while(n--){
        x++;
    }
}
int main()
{
    std::thread t1(func, 100000);
    std::thread t2(func, 200000);
    t1.join();
    t2.join();
    std::cout<<x<<std::endl;
    return 0;
}

这个结果往往是不能预测的,x++操作并不是原子(即在底层并不是单个指令完成)。事实上它做了这些操作:

复制代码
mov eax, dword ptr [x]    ; 1. 从内存读取x的值到寄存器eax
add eax, 1                ; 2. 寄存器eax加1
mov dword ptr [x], eax    ; 3. 将eax写回内存中的x

非原子操作存在两个问题:

  1. 在指令执行到一半CPU时间片就用完了被切出去,而在期间x值被其他线程改了放在内存中,但该线程再次拿到CPU执行该操作时是使用原来保存的上下文基础上进行操作并写回内存导致数据计算错误。
  2. 在第一个线程执行到一半其他线程就并发的对x操作,同样导致数据计算错误。

所以想这样的非原子操作的共享资源(临界资源)需要使用锁来保护,以得到原子的效果。

cpp 复制代码
#include <mutex>
int x = 0;
std::mutex mtx;
void func(int n)
{
    mtx.lock();
    while(n--){
        x++;
    }
    mtx.unlock();
}

这样一来并行执行就退化成了串行执行,效率变低,但正确性安全性优先。

当然也可以把加解锁放在while内,但这样效率会很低,在锁被占用的情况会进行阻塞首先需要保存上下文,在此过程会有很多消耗,而且中间就只有一条语句,加解锁频繁,效率低。

  • 注:锁不能进行拷贝构造,因为同一把锁是不能被两个不同对象管理的。
  • 注:除lock之外还有try_lock()非阻塞的获取锁,如果抢到锁返回true,没抢到返回false。

2.2 timed_mutex

timed_mutex也是锁管理类,但它比mutex多了两个接口,功能更为丰富。

成员函数如下:

try_lock_for:尝试获取锁多长时间?这是一个相对的时间。
try_lock_until:尝试获取锁到什么时间以后结束?是一个绝对的时间点。

返回值:true表示成功,false表示超时。

2.3 recursive_mutex

这是一个递归锁,在一些递归场景,使用普通锁通常会出现死锁问题,如下场景:

复制代码
void func()
{
    mtx.lock();
    //......
    func();
    mtx.unlock();
}

recursive_mutexmutex完全类似,recursive_mutex提供排他性递归所有权语义:

  • (1)调用方线程在从它成功调用 locktry_lock 开始的时期里占有 recursive_mutex。此时期之内,线程可以进行对 locktry_lock 的附加调用。所有权的时期在线程进行匹配次数的 unlock 调用时结束。
  • (2)线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调用 lock)或收到 false 返回值(对于调用 try_lock)也一样。

三、RAII自动化锁管理

RAII(资源获取即初始化),在new开辟空间可以使用智能指针来管理,以防出现抛出异常后忘记释放内存等问题。类似的对于锁也一样,以防出现抛异常忘记释放锁等情况,使用RAII自动化管理。

3.1 lock_guard

该类的原理很简单:在构造时加锁,析构时自动释放锁,就完成了自动化管理,是随生命周期的。模拟实现:

cpp 复制代码
template<class Mutex>
class my_lock_guard
{
public:
    my_lock_guard(Mutex& mtx)
        :_mtx(mtx)
    {
        _mtx.lock();
    }
    my_lock_guard(const my_lock_guard&) = delete;
    my_lock_guard& operator=(const my_lock_guard&) = delete;
    ~my_lock_guard()
    {
        _mtx.unlock();
    }
private:
    Mutex& _mtx;    
}

使用示例:

cpp 复制代码
void func1(int n)
{
    std::lock_guard<std::mutex> lock(mtx);
    while(n--){
        x++;
    }
    //......
}

lock_guard的生命周期结束才解锁,所以要控制指定的临界区只需添加{}lock_guard连同临界区扩起来即可。如下:

cpp 复制代码
void func1(int n)
{
	{
	    std::lock_guard<std::mutex> lock(mtx);
	    while(n--){
	        x++;
	    }
    }
    //......
}

lock_guard仅有构造函数无其他成员函数,而常用的仅有lock_guard(mutex_type& m)

此外还重载了一个特殊的参数接口adopt_lock_t tag,它提供的实参仅有adopt_lock_t管理已经上锁的锁

拷贝构造函数被禁用拷贝构造函数以防多个对象管理一个锁。

3.2 unique_lock

lock_guard类似是对锁的自动化管理,不过unique_lock功能更丰富:

它还提供了这些接口:

  • try_lock:尝试锁定互斥量
  • try_lock_for:在指定时间内尝试锁定
  • try_lock_until:尝试锁定直到指定时间点

它们的返回值:成功返回true,失败返回false

它的构造函数更为丰富,如下:

  • try-locking(3)try_to_lock_t参数表示尝试上锁。需要注意的是构造函数没有返回值,那么如何知道它是否上锁成功呢。
    • 方法一:使用成员函数owns_lock(),上锁成功返回true,失败返回false。
    • 方法二:直接使用if对对象进行条件判断,因为该类重定义了operator bool函数,上锁成功返回true,失败返回false。
  • deferred(4):defer_lock_t参数表示延迟上锁,先对锁对象进行管理,后面再进行自行上锁。
  • adopting(5):adopt_lock_t参数表示对已经上锁的锁进行管理。
  • locking for(6):在一个相对时间段内尝试上锁,超时则放弃。
  • locking until(7):在达到一个绝对时间点前尝试上锁,超时则放弃。

注意1:最后两个带超时的锁管理必须传timed_mutex,其他构造函数可以传任意锁。

注意2:直接调用lock()/unlock() 存在异常安全风险 ------ 若临界区代码抛出异常,unlock() 不会执行,导致锁永远无法释放。因此实际开发中必须使用 RAII 锁(lock_guard/unique_lock) 替代手动加解锁。

四、std::lock


std::lock是一个可变参数函数模板。同时锁定多个对象带来死锁问题。死锁场景:

cpp 复制代码
std::mutex foo, bar;
void task_a() {
	std::this_thread::sleep_for(std::chrono::seconds(1));
	foo.lock();
	bar.lock(); 
	std::cout << "task a\n";
	foo.unlock();
	bar.unlock();
}
void task_b() {
	std::this_thread::sleep_for(std::chrono::seconds(1));
	bar.lock(); 
	foo.lock();
	std::cout << "task b\n";
	bar.unlock();
	foo.unlock();
}
int main()
{
	std::thread th1(task_a);
	std::thread th2(task_b);
	th1.join();
	th2.join();
	return 0;
}

如果在同一时间task_a锁住了bar,task_b锁住了foo,它们要抢下一个锁时,因为锁都在对方手中,所以两个线程永远阻塞。

std::lock工作原理:如果同时锁定多个锁继续往下执行,如果没有同时锁定成功,会把已经锁住的解锁,然后进入没有锁住的阻塞,等它解锁后再次尝试同时锁,依此循环。

结合unqiue_lock使用示例:

cpp 复制代码
void task_c() {
    std::lock(foo,bar);
    std::unique_lock<std::mutex> lf(foo,std::adopt_lock);//adopt_lock_t对已经锁住的锁对象管理
    std::unique_lock<std::mutex> lb(bar,stf::adopt_lock);
	std::cout << "task c\n";
}
void task_d() {
    std::unique_lock<std::mutex> lf(foo,std::defer_lock);//defer_lock_t延迟上锁
    std::unique_lock<std::mutex> lb(bar,std::defer_lock);
    std::lock(foo,bar);
	std::cout << "task d\n";
}

类似的有全局可变参数函数模板std::try_lock,尝试对多个锁进行锁定,但不会阻塞而是返回没有锁住的锁的编号(锁编号从函数实参的左到右,0开始依次编号,int类型),都锁住返回-1。

五、atomic

5.1 原子操作

如果临界区代码非常短小,阻塞、休眠、上下文切换的开销会非常大,而atomic作用变成原子操作。

atomic对模板参数T的要求很苛刻,T类型需要满足以下几个类型判断条件,如果任意⼀个返回false,则⽤于atomic不是原⼦操作。

cpp 复制代码
std::is_trivially_copyable<T>::value
std::is_copy_constructible<T>::value
std::is_move_constructible<T>::value
std::is_copy_assignable<T>::value
std::is_move_assignable<T>::value
std::is_same<T, typename std::remove_cv<T>::type>::value

CAS(Compare-And-Swap)是一种原子操作,它是实现无锁编程的核心原子操作机制。std::atomic是 C++ 提供的线程安全操作封装,其中许多操作(如 compare_exchange_weak/strong)底层就是通过 CAS 指令实现的。

cpp 复制代码
// C++11⽀持的CAS接⼝
template <class T>
bool atomic_compare_exchange_weak (atomic<T>* obj, T* expected, T val) noexcept;
template <class T>
bool atomic_compare_exchange_strong (atomic<T>* obj, T* expected, T val) noexcept;
// C++11中atomic类的成员函数
bool compare_exchange_weak (T& expected, T val,
memory_order sync = memory_order_seq_cst) noexcept;
bool compare_exchange_strong (T& expected, T val,
memory_order sync = memory_order_seq_cst) noexcept;

以上atomic_compare_exchange_weakcompare_exchange_weak功能和作用完全一样,只是调用方式不同,一个是全局函数,一个是atomic类的成员函数。atomic_compare_exchange_strongcompare_exchange_strong也是一样。

它们的工作原理(以x++为例)
(1)从内存中取出x值放入寄存器
(2)CPU根据寄存器中的x值计算出新的x值
(3)把新的x值写入内存,如果内存中的x值与之前寄存器中的x值相同则写入成功,结束。如果不同则写入失败,并把内存中的x更新到寄存器。(原子操作)
(4)重复2到3步直到成功写入。

weak和strong的区别?

  • weak:即使期望值与内存当前值相同,操作也可能失败返回false。这种情况称为"假失败",可能由多种原因引起。
  • strong:如果返回true,则一定成功执行了比较和交换操作;如果返回false,则一定是因为期望值与内存当前值不匹配。
    strong很少使用,它通过额外的保证机制确保无假失败,但这会带来性能开销。

注:由于CPU时钟频率在GHz级别(每纳秒执行多个时钟周期),多个CPU核心几乎不可能在完全相同的时刻对同一内存位置进行修改。即使多个线程尝试同时修改,硬件级的时序差异也会使它们的操作被序列化执行。

使用示例:

cpp 复制代码
void Add1(atomic<int>& cnt)
{
   int old = cnt.load();
   // 如果cnt的值跟old相等,则将cnt的值设置为old+1,并且返回true,这组操作是原子的。
   // 那么如果在load和compare_exchange_weak操作之间cnt对象被其他线程改了
   // 则old和cnt不相等,则将old的值改为cnt的值,并且返回false。
   //while (!atomic_compare_exchange_weak(&cnt, &old, old + 1));
   while (!cnt.compare_exchange_weak(old, old + 1));
}

为了更方便的完成原子操作,c++的atomic类封装重定义了一些操作符,比如++,--等。使用示例:

cpp 复制代码
#include <atomic>
std::atomic<int> acnt;
++acnt;

常用的成员函数:

  • store:写入值,以原子方式将值写入原子对象。
  • load:读取值,以原子方式读取原子对象的当前值。

5.1 memory_order

在C++11标准库中, std::atomic提供了多种内存顺序( memory_order )选项,⽤于控

制原⼦操作的内存同步⾏为。这些内存顺序选项允许开发者在性能与正确性之间进⾏权衡,特别是

在多线程编程中。以下是std::atomic⽀持的六种内存顺序选项:

  • memory_order_relaxed最宽松的内存顺序,仅保证原⼦操作的原⼦性,不提供任何同步或顺序
    约束。使⽤场景:适⽤于不需要同步的场景,例如计数器或统计信息。
  • memory_order_consume限制较弱的内存顺序,仅保证依赖于当前加载操作的数据的可⻅性。
    通常⽤于数据依赖的场景。使⽤场景:适用于某些特定的数据依赖链场景,但实际使⽤较少。
  • memory_order_acquire保证当前操作之前的所有读写操作(在当前线程中)不会被重排序到当
    前操作之后。通常⽤于加载操作。使⽤场景:⽤于实现锁或同步机制中的"获取"操作
  • memory_order_release保证当前操作之后的所有读写操作(在当前线程中)不会被重排序到当
    前操作之前。通常⽤于存储操作。使⽤场景:⽤于实现锁或同步机制中的"释放"操作。
  • memory_order_acq_rel结合了memory_order_acquirememory_order_release的语义。
    适⽤于读-修改-写操作(如fetch_addcompare_exchange_strong)。使⽤场景:⽤于需要
    同时实现"获取"和"释放"语义的操作
  • memory_order_seq_cst最严格的内存顺序,保证所有线程看到的操作顺序是⼀致的(全局顺序
    ⼀致性)。默认的内存顺序。使⽤场景:适⽤于需要强⼀致性的场景,但性能开销较⼤。

示例:

cpp 复制代码
std::atomic<int> x(0);
x.store(42, std::memory_order_relaxed);

5.2 自旋锁

自旋锁是一种忙等待的同步机制,与互斥锁(休眠锁)在工作方式上有本质区别。

互斥锁 (Mutex)

  • 工作方式:获取锁失败时,线程立即休眠,让出CPU
  • 上下文切换:涉及内核调度,有显著的性能开销
  • 适用场景:临界区较大、竞争激烈或等待时间较长的情况

自旋锁 (Spinlock)

  • 工作方式:获取锁失败时,线程在用户态循环检查(自旋),不释放CPU
  • 上下文切换:无调度开销,但占用CPU时间
  • 适用场景:临界区极小、竞争不激烈或等待时间极短的情况

简单的自旋锁封装:

cpp 复制代码
#include <atomic>
class SpinLock {
public:
    SpinLock() : flag(false) {}
    void lock() {
        // 使用原子操作来尝试获取锁
        while (flag.exchange(true, std::memory_order_acquire)) {
            // 自旋等待,直至锁被释放
        }
    }
    void unlock() {
        // 使用原子操作来释放锁
        flag.store(false, std::memory_order_release);
    }
private:
    std::atomic<bool> flag;
};

方法二:

cpp 复制代码
class SpinLock
{
private:
    // ATOMIC_FLAG_INIT默认初始化为false
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
    void lock()
    {
        // test_and_set将内部值设置为true,并且返回之前的值
        // 第一个进来的线程将值原子的设置为true,返回false
        // 后面进来的线程将原子的值设置为true,返回true,所以卡在这里空转,
        // 直到第一个进去的线程unlock, clear, 将值设置为false
        while (flag.test_and_set(std::memory_order_acquire));
    }
    void unlock()
    {
        // clear将值原子的设置为false
        flag.clear(std::memory_order_release);
    }
};

六、条件变量

condition_variable类是条件变量,需要配合锁使用。主要提供wait和notify系统接⼝。

条件变量的目的是让线程能够高效、安全地等待某个条件成立,并在条件满足时被精确唤醒,避免忙等待和竞态条件。

常用接口

  • notify_one:唤醒任意一个
  • notify_all:唤醒所有等待在该条件变量上的线程;线程唤醒后会重新竞争锁,最终只有一个线程能拿到锁执行临界区代码,其余线程会再次阻塞在锁上。
  • wait:调用时会原子地解锁并阻塞当前线程,直到被notify_one()/notify_all() 唤醒;唤醒后会重新加锁,并检查条件。
  • wait_for:等待一个相对时间后,无论有没有被唤醒都要往下执行。
  • wait_until:等待一个绝对的时间,无论有没有被唤醒都往下执行。

使用示例,两个线程交替打印奇偶数:

cpp 复制代码
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
using namespace std;
int main()
{
    std::mutex mtx;
    std::condition_variable c;
    int n = 100;
    bool flag = true;
    // 第⼀个打印的是t1打印0
    thread t1([&]() {
        int i = 0;
        while (i < n)
        {
            unique_lock<mutex> lock(mtx);
            // flag == false t1⼀直阻塞
            // flag == true t1不会阻塞
            while (!flag)
            {
                c.wait(lock);
            } 
            cout << i << endl;
            flag = false;
            i += 2; // 偶数
            c.notify_one();
        } 
    });
    thread t2([&]() {
        int j = 1;
        while (j < n)
        {
            unique_lock<mutex> lock(mtx);
            // 只要flag == true t2⼀直阻塞
            // 只要flag == false t2不会阻塞
            while (flag)
            c.wait(lock);
            cout << j << endl;
            j += 2; // 奇数
            flag = true;
            c.notify_one();
        } 
    });
    t1.join();
    t2.join();
    return 0;
}

七、future

7.1 future和async

std::async 是一个异步执行函数,用于创建异步任务:

  • 若指定std::launch::async 策略,会保证开辟新线程执行任务;
  • 若指定std::launch::deferred 策略,任务会延迟到future.get() 时执行(无新线程,在调用 get () 的线程中执行);
  • 默认策略(不指定)由实现决定,可能创建新线程或延迟执行。

与std::thread 不同,std::async 返回std::future 对象,可直接获取任务执行结果。

future类常用的接口:

  • get:等待 + 取结果(阻塞直到任务完成,然后返回结果),只能调用一次。
  • wait:只等待(阻塞直到任务完成,但不关心结果)
  • wait_for:等待一个相对时间后结束,返回值是std::future_status枚举类型,包含三种状态:
    • std::future_status::ready:任务已完成;
    • std::future_status::timeout:等待超时(任务未完成);
    • std::future_status::deferred:任务延迟执行(仅std::launch::deferred 策略下出现)
  • wait_until:等待绝对时间后结束,返回值同wait_for

async函数的参数:

  • 第一个参数(可选):启动策略,分为以下几种:
    • std::launch::async:异步执行(默认参数)
    • std::launch::deferred:延迟执行
  • 第二个参数:可执行函数
  • 后续参数:传递给可调用对象的参数

注:如果设置了延迟执行,只有在future调用get时才执行。

示例:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

void task(const char* name) {
    std::cout << name << " 开始执行,线程ID: "
              << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << name << " 完成" << std::endl;
}

int main() {
    std::cout << "主线程ID: " << std::this_thread::get_id() << std::endl;
    // 异步执行(新线程)
    auto f1 = std::async(std::launch::async, task, "异步任务");
    // 延迟执行(调用get时执行)
    auto f2 = std::async(std::launch::deferred, task, "延迟任务");
    std::cout << "主线程工作..." << std::endl;
    f1.get(); // 等待异步任务完成
    f2.get(); // 此时才执行延迟任务
    return 0;
}

7.2 promise

std::promise是一个承诺对象,允许一个线程设置值(或异常),然后另一个线程通过 std::future 来获取这个值。

复制代码
// 生产者线程   →  promise.set_value()   →  消费者线程
//                                             ↓
//                                           future.get()

示例:

cpp 复制代码
#include <iostream>
#include <thread>
#include <future>

void producer(std::promise<int> promise_obj) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    // 生产者:设置结果值
    promise_obj.set_value(42);
    
    std::cout << "生产者:已设置值 42\n";
}

int main() {
    // 创建 promise-future 对
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    
    // 启动生产者线程,传递 promise(需要移动)
    std::thread producer_thread(producer, std::move(prom));
    
    // 消费者:等待并获取结果
    std::cout << "消费者:等待结果...\n";
    int result = fut.get();  // 这里会阻塞直到有值
    
    std::cout << "消费者:收到结果 " << result << "\n";
    
    producer_thread.join();
    return 0;
}

std::promise 非线程安全:

  • 多个线程同时调用set_value()/set_exception() 会导致未定义行为(只能由一个线程设置值 / 异常);
  • 其关联的std::future 对象的get() 方法也不能被多个线程同时调用(调用后 future 进入无效状态)。

7.3 shared_future

std::shared_future相当于future的升级版本,允许多个线程安全地等待并获取同一个异步结果。

特点:

  • 可被多个线程安全访问
  • 可多次调用get()
  • 通过future::share()或构造函数创建

示例:

cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <vector>
#include <mutex>
std::mutex mtx;
void printResult(std::shared_future<int> fut, int id) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "线程" << id << " 得到结果: " << fut.get() << std::endl;
}

int main() {
    std::promise<int> prom;
    std::shared_future<int> sharedFut = prom.get_future().share();
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(printResult, sharedFut, i);
    }
    // 模拟计算
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(100);
    for (auto& t : threads) {
        t.join();
    }
    return 0;
}

7.4 packaged_task

packaged_task是一个函数包装器,类似function,不过它是用来包装异步函数的。

特性:

  • 包装一个可调用对象(函数、lambda、函数对象等)
  • 任务执行后,结果自动存储到关联的 std::future 中
  • 适合在线程池或任务队列中使用

示例:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
int main() {
    // 1. 创建一个 packaged task,包装一个计算平方的函数
    std::packaged_task<int(int)> task([](int x) {
        std::cout << "Calculating square of " << x << " in thread "
                  << std::this_thread::get_id() << std::endl;
        return x * x; // 返回平方值
    });

    // 2. 获取与该任务关联的 future 对象
    std::future<int> result = task.get_future();

    // 3. 将任务移动到线程中异步执行(注意:packaged_task 不可复制)
    std::thread worker(std::move(task), 5); // 传入参数 5

    // 4. 主线程继续执行其他工作...
    std::cout << "Main thread working..." << std::endl;

    // 5. 获取异步任务的结果(若未完成会阻塞)
    int square = result.get();
    std::cout << "Result: " << square << std::endl;

    // 6. 等待线程结束
    worker.join();

    return 0;
}

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

相关推荐
夏幻灵2 小时前
JAVA基础-就近原则和this关键字
java·开发语言
zhaokuner2 小时前
08-仓储与映射-DDD领域驱动设计
java·开发语言·设计模式·架构
微露清风2 小时前
系统性学习C++进阶-第十五讲-map和set的使用
java·c++·学习
一起养小猫2 小时前
LeetCode100天Day9-无重复字符的最长子串与赎金信
java·开发语言·数据结构·leetcode
wjs20242 小时前
Go 语言类型转换
开发语言
菩提祖师_2 小时前
基于Java的物联网智能交通灯控制系统
java·开发语言·物联网
公众号:ITIL之家2 小时前
服务价值体系重构:在变化中寻找不变的运维本质
java·运维·开发语言·数据库·重构
zhaokuner2 小时前
01-领域与问题空间-DDD领域驱动设计
java·开发语言·设计模式·架构
fqbqrr3 小时前
2601,C++的模块1
c++