C++线程库

C++11 引入了原生的多线程支持,不再需要依赖平台特定的 API(如 POSIX pthreads 或 Windows threads)。这是一个重大的改进,让 C++ 多线程编程变得标准化和便携。

一、thread

1.1 构造函数

构造函数类型 语法格式 描述
默认构造函数 thread() noexcept; 构造一个不代表任何执行线程的线程对象
初始化构造函数 template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
构造一个代表新的可连接执行线程的线程对象。新执行线程调用fn,并传递args作为参数(使用其左值或右值引用的decay副本)。此构造的完成与该fn副本的调用开始同步
拷贝构造函数 thread (const thread&) = delete; 已删除的构造函数形式(线程对象不能拷贝)
移动构造函数 thread (thread&& x) noexcept; 构造一个获取由x表示的执行线程的线程对象(如果有)。此操作不影响被移动线程的执行,只是转移其句柄。x对象不再代表任何执行线程

重要注意事项:

  1. 可连接的线程对象在销毁前必须被 join()detach()
  2. 参数说明:
    • fn: 可调用对象(函数指针、函数对象、lambda表达式等)
    • args…: 传递给fn的参数,使用decay副本
  3. 构造时机:初始化构造函数完成后,新线程立即开始执行
  4. 移动语义:移动构造后,源对象变为空线程对象

1.2 get_id

用于返回 std::thread::id 类型的值,表示线程的唯一标识符。需要通过线程对象调用

函数原型

std::thread::id get_id() const noexcept;

std::thread::idstd::thread 中的类型,本质是一个类。

为什么要单独设计一个类?

  • 类型安全:防止原始类型可以随意赋值(无意义)

  • 封装不同平台的线程id类型的差异性

重要特性:

  • 支持线程ID的比较

  • 支持默认构造

  • 哈希支持

  • 流输出支持

1.3 joinable

joinable() 检查的不是线程是否执行完毕,而是线程对象是否代表一个有效的、尚未被 join/detach 的执行线程。

函数原型

bool joinable() const noexcept;

返回值

  • true: 该线程对象是一个有效的执行线程。

  • false: 该线程对象是一个非有效的执行线程。

重要特性:

  • joinable() 返回 true 表示:

    1. 线程对象关联着一个有效的执行线程

    2. 该线程尚未被 join()detach()

    3. 可以合法的调用 join()detach()

  • joinable() 返回 false 表示:

    1. 线程对象没有关联任何线程(默认构造)

    2. 线程已经被移动

    3. 线程被 detach()join()

1.4 join

等待一个线程结束。

函数原型

void join();

1.5 detach

分离线程,使线程无需join,会被自动回收资源。

函数原型

void detach();

1.6 赋值

创建一个空的线程对象,可以通过移动赋值让线程执行起来。

thread& operator=(thread&& rhs) noexcept;
thread& operator=(const thread&) = delete;

二、this_thread

std::this_thread 是一个命名空间,存放一些函数专门用于操作当前正在执行的线程。

2.1 get_id

获取当前线程的ID。可以不使用对象,直接通过命名空间访问该函数。

函数原型

thread::id get_id() noexcept;

2.2 yield

让出当前线程的时间片。

函数原型

void yield() noexcept;

2.3 sleep_for

指定当前线程的相对睡眠时长。

函数原型

template <class Rep, class Period>
void sleep_for(const chrono::duration<Rep, Period>& rel_time);

示例

std::this_thread::sleep_for(std::chrono::seconds(1));  // 睡眠1秒

2.4 sleep_until

设置当前线程睡眠到指定的时间点。

函数原型

template <class Clock, class Duration>
void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);

示例

std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::seconds(3));

sleep_forsleep_until 需要与 C++11 时间相关的类配合使用(std::chrono)

三、mutex

  • mutex: 普通互斥锁。

  • timed_mutex: 带超时的互斥锁。新增了两个接口 try_lock_fortry_lock_until

    • try_lock_for 尝试获取锁,最多等待指定的时间长度(相对时间)。

    • try_lock_until 尝试获取所,一直等到指定的具体时间点(绝对时间)。

    • true 表示等到了锁,false 表示等待了这么久还没有获取到锁。

    • mutex::try_lock()timed_mutex::try_lock_for() 的核心区别在于等待策略:前者是非阻塞检查,看一眼锁的状态,有空就拿没空就走,立即返回结果;后者是限时等待,愿意等待指定的时间,在此期间持续尝试获取锁,超时后才放弃。timed_mutex::try_lock_until() 原理相同,只是用绝对时间点替代相对时长来定义超时边界。

  • recursive_mutex: 允许一个线程可以被锁定多次,一般在递归中使用。

    • 可重入:同一个线程被多次锁定。

    • 需要匹配解锁:锁定几次就要解锁几次。

3.1 构造函数

构造函数 语法 说明
默认构造函数 constexpr mutex() noexcept; 创建未锁定的互斥锁,不能拷贝
拷贝构造函数 mutex(const mutex&) = delete; 已删除,禁止拷贝构造

3.2 lock

锁定互斥锁。如果已被锁定,则阻塞当前线程直到获得锁。

函数原型

void lock();

3.3 try_lock

尝试锁定互斥锁,立即返回不阻塞。

函数原型

bool try_lock();

返回值

  • true: 成功获得锁。

  • false: 未能获得锁(已被其他线程锁定)。

3.4 unlock

解锁互斥锁。

函数原型

void unlock();

3.5 ref

以引用的方式为线程执行函数传递变量和锁的情况;线程构造函数的参数完美转发和存储机制中的引用保留。

因为变量和锁本质并没有直接传递给线程的执行函数,而是先传递给 thread 的构造函数。但构造函数通过完美转发接收参数,保留了原本的语义,但是线程启动时又做了一次拷贝。因此,使用 std::ref() 是为了在这个机制中保留引用语义。

#include <iostream>
#include <thread>
#include <mutex>
#include <functional>  // 需要包含这个头文件才能用std::ref

using namespace std;

void Print(int n, int& rx, mutex& rmtx)
{
    rmtx.lock();
    for (int i = 0; i < n; i++)
    {
        ++rx;  // 修改共享变量
    }
    rmtx.unlock();
}

int main()
{
    int x = 0;
    mutex mtx;

    // 创建两个线程,分别递增x
    thread t1(Print, 1000000, ref(x), ref(mtx));  // 使用ref传递引用
    thread t2(Print, 2000000, ref(x), ref(mtx));  // 使用ref传递引用

    // 等待两个线程完成
    t1.join();
    t2.join();

    // 输出最终结果
    cout << "Final value of x: " << x << endl;
    
    return 0;
}

也可以传递lambda表达式捕获引用,从而避免 std::ref()。

3.6 lock_guard

lock_guard 是基于 RAII 风格简单包装的互斥锁,构造时立即锁定,析构时解锁。

类原型

template <class Mutex> class lock_guard;

构造函数

explicit lock_guard (Mutex& m);
lock_guard (const lock_guard&) = delete;

3.7 unique_lock

unique_lock也是基于 RAII 的锁风格,功能比 lock_guard 的更丰富。在构造函数当中的第二个参数是标志位,用来控制锁的行为。

// 构造函数原型
explicit unique_lock(mutex_type& m);           // 1. 默认:立即锁定
unique_lock(mutex_type& m, defer_lock_t) noexcept;  // 2. 延迟锁定
unique_lock(mutex_type& m, try_to_lock_t);     // 3. 尝试锁定
unique_lock(mutex_type& m, adopt_lock_t);      // 4. 接管已持有的锁
操作 mutex unique_lock
lock() 直接锁定,重复锁定会死锁 先检查,已经锁定就抛异常
unlock() 直接解锁,重复解锁会崩溃 先检查,没锁就抛异常
状态记忆 不记得自己锁没锁 有 owns_lock 标志
适合条件变量的 wait 不知道何时解锁/加锁 知道状态,安全操作

为什么锁两次会死锁?

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void test_mutex() {
    std::cout << "线程开始...\n";
    
    mtx.lock();        // 第一次锁定 ✅ 成功
    std::cout << "第一次锁定成功\n";
    
    mtx.lock();        // 第二次锁定 ❌ 死锁在这里!
    std::cout << "你看不到这句话,因为已经死锁了\n";
    
    // 而重复解锁会破坏互斥锁的内部状态,导致不可预测的行为
    mtx.unlock();
    mtx.unlock();
}

int main() {
    test_mutex();  // 程序会卡住,不会结束!
    return 0;
}
3.7.1 defer_lock

延迟锁定。

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

// 需要时再手动锁定
lock.lock();  

与 lock 配合使用。

// 需要同时锁定多个mutex,避免死锁
std::mutex mtx1, mtx2;
std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);

// 同时锁定两个,避免死锁
std::lock(lock1, lock2);  // 原子操作

// 现在两个锁都有了
// 自动管理解锁
3.7.2 try_to_lock

尝试锁定。

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
// ↑ 尝试锁定,不阻塞

if (lock.owns_lock()) {  // 检查是否成功
    // 成功获得锁
    std::cout << "Got the lock!" << std::endl;
} else {
    // 没拿到锁
    std::cout << "Lock is busy" << std::endl;
    // lock 析构时不会解锁(因为没有锁)
}
3.7.3 adopt_lock

接管已持有的锁。

// 配合 std::lock 使用
std::mutex mtx1, mtx2;

// 先手动锁定(用 std::lock 避免死锁)
std::lock(mtx1, mtx2);

// 然后让 unique_lock 接管
std::unique_lock<std::mutex> lock1(mtx1, std::adopt_lock);
std::unique_lock<std::mutex> lock2(mtx2, std::adopt_lock);

// 现在自动管理解锁,安全!
// 即使抛异常也会正确解锁

相比 lock_guard 只有 adopt_lock 接管已持有的锁。

3.8 lock

lock是⼀个函数模板,可以⽀持对多个锁对象同时锁定,如果其中⼀个锁对象没有锁住,lock函数会把已经锁定的对象解锁⽽进⼊阻塞,直到锁定所有的所有的对象。

函数原型

template<class Lockable1, class Lockable2, class... LockableN>
void lock(Lockable1& l1, Lockable2& l2, LockableN&... ln);

工作原理

  • 使用死锁避免算法。

  • 保证要么全部锁定,要么一个都不锁。

  • 不会因为锁定顺序导致死锁。

3.9 try_lock

try_lock也是⼀个函数模板,尝试对多个锁对象进⾏同时尝试锁定,如果全部锁对象都锁定了,返回-1,如果某⼀个锁对象尝试锁定失败,把已经锁定成功的锁对象解锁,并则返回这个对象的下标,不会进行阻塞(下标从0开始算)。

函数原型

template<class Lockable1, class Lockable2, class... LockableN>
int try_lock(Lockable1& l1, Lockable2& l2, LockableN&... ln);

返回值

  • 成功:返回-1

  • 失败:返回第一个未能锁定的锁的索引(从0开始)。

四、atomic

什么是atomic?

Atomic(原子操作) 是在多线程编程中,不需要加锁就能保证线程安全的操作。这些操作在执行过程中不会被其他线程中断。

template <class T> struct atomic;

atomic对T类型的要求模板可⽤任何满⾜可复制构造 (CopyConstructible) 及可复制赋值
(CopyAssignable) 的可平凡复制 (TriviallyCopyable) 类型 T 实例化,T类型⽤以下⼏个函数判断时,如果⼀个返回false,则⽤于atomic不是原⼦操作。

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
  • atomic对于整形和指针支持基本加减运算和位运算。

std::atomic 成员函数原型表格

类别 函数名 函数原型 说明
构造与赋值 默认构造函数 constexpr atomic() noexcept; 默认构造(值未初始化)
初始化构造函数 constexpr atomic(T desired) noexcept; 用给定值初始化
赋值运算符 T operator=(T desired) volatile noexcept;
T operator=(T desired) noexcept;
原子赋值
通用操作 is_lock_free bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;
检查是否无锁实现
store void store(T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子存储值
load T load(std::memory_order order = std::memory_order_seq_cst) const volatile noexcept;
T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
原子读取值
operator T operator T() const volatile noexcept;
operator T() const noexcept;
隐式转换(相当于load)
exchange T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
交换值并返回旧值
compare_exchange_weak bool compare_exchange_weak(T& expected, T desired, std::memory_order success, std::memory_order failure) volatile noexcept;
bool compare_exchange_weak(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
弱比较并交换
compare_exchange_strong bool compare_exchange_strong(T& expected, T desired, std::memory_order success, std::memory_order failure) volatile noexcept;
bool compare_exchange_strong(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
强比较并交换
整数/指针特化操作 fetch_add T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子加,返回旧值
fetch_sub T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子减,返回旧值
fetch_and T fetch_and(T arg, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T fetch_and(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子按位与,返回旧值
fetch_or T fetch_or(T arg, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T fetch_or(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子按位或,返回旧值
fetch_xor T fetch_xor(T arg, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
T fetch_xor(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept;
原子按位异或,返回旧值
operator++ (前缀) T operator++() volatile noexcept;
T operator++() noexcept;
原子递增(前缀)
operator++ (后缀) T operator++(int) volatile noexcept;
T operator++(int) noexcept;
原子递增(后缀)
operator-- (前缀) T operator--() volatile noexcept;
T operator--() noexcept;
原子递减(前缀)
operator-- (后缀) T operator--(int) volatile noexcept;
T operator--(int) noexcept;
原子递减(后缀)
operator+= T operator+=(T arg) volatile noexcept;
T operator+=(T arg) noexcept;
原子复合加赋值
operator-= T operator-=(T arg) volatile noexcept;
T operator-=(T arg) noexcept;
原子复合减赋值
operator&= T operator&=(T arg) volatile noexcept;
T operator&=(T arg) noexcept;
原子复合与赋值
operator|= T operator|=(T arg) volatile noexcept;
T operator|=(T arg) noexcept;
原子复合或赋值
operator^= T operator^=(T arg) volatile noexcept;
T operator^=(T arg) noexcept;
原子复合异或赋值

注意:

  1. 表中每个函数都有 volatile 和非 volatile 两个版本
  2. 整数/指针特化操作仅适用于 std::atomic<integral_type>std::atomic<pointer_type>
  3. std::memory_order 参数默认为 std::memory_order_seq_cst

atomic 的底层依赖于:

  • 硬件指令支持:CPU 提供的原子指令。

  • 内存屏障/内存顺序:控制指令重排序和内存可见性。

  • 编译器支持:阻止编译器对 atomic 操作进行不安全的优化。

atomic的原理主要是硬件层⾯的⽀持,现代处理器提供了原⼦指令来⽀持原⼦操作。例如,在 x86 架构中有CMPXCHG(⽐较并交换)指令。这些原⼦指令能够在⼀个不可分割的操作中完成对内存的读取、⽐较和写⼊操作,简称CAS,Compare And Set,或是 Compare And Swap。另外为了处理多个处理器缓存之间的数据⼀致性问题,硬件采⽤了缓存⼀致性协议,当⼀个atomic操作修改了⼀个变量的值,缓存⼀致性协议会确保其他处理器缓存中的相同变量副本被正确地更新或标记为⽆效。

// GCC 支持的 CAS 接口
bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval);
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval);

// Windows 支持的 CAS 接口
InterlockedCompareExchange (__inout LONG volatile *Target,
                            __in LONG Exchange,
                            __in LONG Comperand);

// 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_weak

  • 成功:比较 obj 当前值是否等于 *expected,若两者相等,将 obj 的值原子性的更新为 desired,返回 true

  • 失败:分为两种,真实失败和虚假失败。

    • 真实失败:obj 的当前值不等于 *expected,将 obj 的实际值读如 *expected,返回 false

    • 虚假失败:obj 的当前值等于 *expected(本应成功),但由于某些原因操作失败(缓存与内存不一致问题)。注意:atomic_compare_exchange_strong 不会虚假的失败,但是有性能的代价。

通过 CAS 操作模拟 atomic 的 opertaor++ 的原子操作。

void add(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));
}

示例

// atomic::compare_exchange_weak example:
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);

void append (int val) {     // append an element to the list
    Node* oldHead = list_head;
    Node* newNode = new Node {val,oldHead};

    // what follows is equivalent to: list_head = newNode, but in a thread-safe way:
    while (!list_head.compare_exchange_weak(oldHead,newNode))
        newNode->next = oldHead;
}

int main ()
{
    // spawn 10 threads to fill the linked list:
    std::vector<std::thread> threads;
    for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
    for (auto& th : threads) th.join();

    // print contents:
    for (Node* it = list_head; it!=nullptr; it=it->next)
    std::cout << ' ' << it->value;
    std::cout << '\n';

    // cleanup:
    Node* it; while (it=list_head) {list_head=it->next; delete it;}

    return 0;
}

atomic 是一个完整的原子操作库,CAS 是这个库里最核心、最强大的功能,可以用来构建其他所有的原子操作。

五、condition_variable

std::condition_variable 是 C++11 引入的条件变量,用于线程间的同步。它允许线程等待某个条件成立,而不是忙等待。

5.1 构造函数与赋值

condition_variable();  // (1) 默认构造
condition_variable(const condition_variable&) = delete;  // (2) 拷贝构造(禁用)
condition_variable& operator=(const condition_variable&) = delete;  // (3) 赋值(禁用)

5.2 wait

wait() 函数让当前线程主动睡眠等待,直到被其他线程唤醒,期间会自动释放锁以避免死锁。

函数原型

void wait(std::unique_lock<std::mutex>& lock); // 如果使用不带条件的版本就需要自己考虑判断条件以及伪唤醒

template <class Predicate>  void wait (unique_lock<mutex>& lck, Predicate pred); // 函数内部已经考虑了伪唤醒的情况,直接调用即可,无需要在使用时考虑

参数

  • Predicate: 是一个可调用对象,满足:

    • 不接受任何参数。

    • 返回 bool 类型。返回 true 立即返回,不等待;返回 false 解锁,进入等待状态,被唤醒后重新加锁。

注意

  • 如果使用不带条件的版本就需要自己考虑判断条件以及伪唤醒

  • 带条件的 wait 内部已经考虑了伪唤醒的情况,直接调用即可。

  • 等价 while (!pred()) wait(lck);

5.3 notify_one 与 notify_all

唤醒一个等待线程和唤醒所有等待线程。如果没有等待的线程,则什么都不做。唤醒哪个线程由系统决定,不是 FIFO。注意:存在伪唤醒。

函数原型

void notify_one() noexcept;    // 唤醒一个等待线程
void notify_all() noexcept;    // 唤醒所有等待线程

5.4 wait_for 与 wait_until

他们与 wait 的行为完全一致,只是阻塞等待的时间长短不同。

  • wait: 无限阻塞,直到被 notify_onenotify_all

  • wait_for: 等一段时间,超时后,强制返回。

  • wait_until: 超过时间点后,强制返回。

函数原型

谓词:谓词就是一个返回 bool 的函数(或类似函数的东西)。

// 版本1:不带谓词,返回超时状态
template<class Rep, class Period>
cv_status wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time
);

std::cv_status::no_timeout // 表示没有超时被唤醒了
std::cv_status::timeout // 表示超时了,条件还没有满足

// 版本2:带谓词,返回布尔值  
template<class Rep, class Period, class Predicate>
bool wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time,
    Predicate pred
);

true // 表示没有超时被唤醒了
false // 表示超时了,条件还没有满足

// 版本1:不带谓词,返回超时状态
template<class Clock, class Duration>
cv_status wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& timeout_time
);

// 版本2:带谓词,返回布尔值
template<class Clock, class Duration, class Predicate>
bool wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& timeout_time,
    Predicate pred
);

5.5 两线程交替打印奇数和偶数(经典面试题)

// 下面演示一个经典问题,两个线程交替打印奇数和偶数
#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;
    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();
        }
    });
    
    // this_thread::sleep_for(std::chrono::milliseconds(3000));
    
    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;
}

5.6 condition_variable_any 与 condition_variable 的区别

condition_variable 只能用 unique_lock<mutex>condition_variable_any 可以用任何满足 Lock 要求的锁。any 是范型版本。

六、future/promise

C++11 引入的 future/promise 模式是一种异步编程模型,它允许我们:

  • 异步执行任务

  • 在需要时获取结果

  • 处理异步操作的状态

6.1 future 与 async

std::async 异步执行一个函数,返回 std::future

  • std::launch::async: 立即在新线程执行。

  • std::launch::deferred: 延迟执行(调用 futureget() 时执行)。

  • 如果不显示传参,由编译器决定具体策略(std::launch::async | std::launch::deferred)。

std::future 是一个模板类,用于表示异步操作的结果。它提供了获取异步操作结果的方法。

int main() {
    auto task = []() {
        std::cout << "任务执行,线程ID: " 
                  << std::this_thread::get_id() << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待1s
        return 100;
    };
    
    // 方式1:立即在新线程执行
    std::cout << "主线程ID: " << std::this_thread::get_id() << std::endl;
    
    std::future<int> fut1 = std::async(std::launch::async, task);
    std::cout << "fut1: " << fut1.get() << std::endl;
    
    // 方式2:延迟执行(在get()时才执行)
    std::future<int> fut2 = std::async(std::launch::deferred, task);
    std::cout << "fut2将在get()时执行..." << std::endl;
    std::cout << "fut2: " << fut2.get() << std::endl;  // 这里才执行
    
    return 0;
}
6.1.1 valid

检查是否关联有效的共享状态。

函数原型

bool valid() const noexcept;
6.1.2 get

获取结果,只能调用一次,会阻塞等待。或抛出异常。调用后 valid() 返回 false

函数原型

T get();	
R& future<R&>::get();       // when T is a reference type (R&)
void future<void>::get();   // when T is void
6.1.3 wait

等待结果就绪。只等待,不获取结果,可以多次调用,不改变 valid() 状态。

函数原型

void wait() const;
6.1.4 wait_for 与 wait_until

带超时的等待。

template< class Rep, class Period >
std::future_status wait_for(
    const std::chrono::duration<Rep, Period>& timeout_duration
) const;

template< class Clock, class Duration >
std::future_status wait_until(
    const std::chrono::time_point<Clock, Duration>& timeout_time
) const;

返回值

  • future_status::ready - 结果已就绪。

  • future_status::timeout - 等待超时。

  • future_status::deferred - 任务延迟执行。

注意点

  • 一个 future 只能获取一次结果。

  • get() 会传播任务中的异常。

  • future 只能移动,不能拷贝。

  • 操作前使用 valid() 检查,避免访问无效状态。

  • 如果 future 关联着 std::launch::async 启动的任务,并且没有调用过 get()wait(),那么析构函数会阻塞等待任务完成。

  • std::launch::async 策略底层会:

    • 1.创建新线程 2.新线程执行传入的函数 3.函数结果存储到future中 4.通过同步机制(互斥锁 + 条件变量)通知主线程。
6.1.5 share

share() 函数用于将 std::future 转换为 std::shared_future转移 共享状态的所有权。转移而非拷贝,原来的 future 会变为无效。

函数原型

shared_future<T> share();
6.1.6 shared_future

std::shared_futurestd::future 的共享版本,允许多个线程安全地访问同一个异步结果。shared_future 接口与 future 接口类似。

  • 可以拷贝: 与 std::future 不同,可以创建多个拷贝。

  • 共享状态: 所有拷贝共享同一个异步结果。

  • 线程安全: 多个线程可以同时调用 get()

  • 重复get: 可以多次调用 get() 获取结果。

6.2 promise

在 C++11 中,std::promise异步编程 的重要组件,通常与 std::futurestd::thread 配合使用,用于在线程之间传递数据或异常。

std::promise 允许一个线程存储一个值(或异常),然后另一个线程可以通过关联的 std::future 获取这个值。

类模版

template <class T>  promise;

基本流程

  1. 创建 promise 和 并获取 promise 中的 future

  2. 在一个线程中通过 promise 设置值。

  3. 在另一个线程中通过 future 获取值。

基本用法

#include <iostream>
#include <thread>
#include <future>

void producer(std::promise<int>&& prom) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(42);  // 设置值
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();  // 获取关联的 future
    
    std::thread t(producer, std::move(prom));
    
    // 等待并获取结果
    int result = fut.get();  // 阻塞直到有值可用
    std::cout << "Result: " << result << std::endl;
    
    t.join();
    return 0;
}

常用成员函数

  • get_future(): 获取与 promise 关联的 future。对于同一个 promiseget_future() 只能调用一次。

    • get_future() 的原型:future<T> get_future();。因为 future 没有拷贝构造只有移动构造,而函数内部返回一个临时的 future 对象,编译器默认调用移动构造,因此这里并不是简单的值拷贝,所以才能关联到 promise 中的 future
  • set_value(): 设置值(触发 future 就绪)。

  • set_exception(): 设置异常。

  • set_value_at_thread_exit(): 在线程退出时设置值。

  • set_exception_at_thread_exit(): 在线程退出时设置异常。

6.3 packaged_task

std::packaged_task 是一个可调用对象的包装器,它将函数或可调用对象与 std::future 绑定,允许异步获取函数调用的结果。

核心特性

  • 包装任何可调用对象(函数、lambda、函数对象)。

  • 自动创建 std::future 来获取结果。

  • 可以传递给线程异步执行。

  • 支持移动语义,不可拷贝。

基本用法

#include <iostream>
#include <future>
#include <thread>

int add(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return a + b;
}

int main() {
    // 1. 创建 packaged_task,包装函数
    std::packaged_task<int(int, int)> task(add);
    
    // 2. 获取与任务关联的 future
    std::future<int> result = task.get_future();
    
    // 3. 在线程中执行任务
    std::thread t(std::move(task), 10, 20);  // 注意:必须使用 move!
    
    // 4. 获取结果
    std::cout << "Result: " << result.get() << std::endl;
    
    t.join();

    return 0;
}

常见的成员函数

  • valid(): 检查是否有有效任务。存在可调用对象返回 true,若默认构造则返回 false

  • get_future(): 获取关联的 future

  • reset(): 重制任务状态,可以获取新的 future

  • operator(): 是执行包装的任务函数,并将结果存储到关联的 future 中。

    • 只能调用一次(除非使用 reset()

    • 可以直接调用或在其他线程中调用

简单的任务队列案例

#include <iostream>
#include <future>
#include <queue>
#include <thread>
#include <mutex>

std::mutex cout_mtx;  // 用于保护std::cout的输出

void taskRunner(std::queue<std::packaged_task<int()>>& taskqueue,
    std::mutex& task_mtx,
    std::condition_variable& cv) {
    while (true) {
        // 只要队列不为空,就取队头的任务进行执行
        std::packaged_task<int()> task;

        {
            // 如果队列不为空,则不wait阻塞,直接执行
            // 如果队列空,则wait阻塞
            std::unique_lock<std::mutex> lock(task_mtx);
            cv.wait(lock, [&taskqueue] { return !taskqueue.empty(); });

            task = std::move(taskqueue.front());
            taskqueue.pop();
        }

        if (!task.valid()) break;  // 空任务表示结束

        task();  // 执行任务

        {
            std::lock_guard<std::mutex> lock(cout_mtx);
            std::cout << "任务在线程 " << std::this_thread::get_id()
                << " 执行完毕" << std::endl;
        }
    }
}

int main() {
    std::queue<std::packaged_task<int()>> taskqueue;
    std::mutex task_mtx;
    std::condition_variable cv;

    // 启动工作线程
    std::thread worker(taskRunner, std::ref(taskqueue),
        std::ref(task_mtx), std::ref(cv));

    // 创建5个任务
    for (int i = 0; i < 5; ++i) {
        std::packaged_task<int()> task(
            [i]() {
                std::this_thread::sleep_for(std::chrono::seconds(1));
                return i * i;
            });

        auto fut = task.get_future();

        {
            // 执行任务push到队列
            std::lock_guard<std::mutex> lock(task_mtx);
            taskqueue.push(std::move(task));
        }

        cv.notify_one();

        // 在主线程获取结果
        std::cout << "任务 " << i << " 结果: " << fut.get() << std::endl;
    }

    // 发送结束信号
    {
        std::lock_guard<std::mutex> lock(task_mtx);
        taskqueue.emplace();  // 空任务表示结束
        //taskqueue.emplace(std::packaged_task<int()>());  // 空任务表示结束
    }
    cv.notify_one();

    worker.join();

    return 0;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐