【C++】多线程

基础

1. 线程创建与管理

  1. C++11 通过 头文件提供了跨平台的线程支持
#include <iostream>
#include <thread>
#include <chrono>

void threadFunction(int id) {
    std::cout << "线程 " << id << " 开始执行" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "线程 " << id << " 结束执行" << std::endl;
}

int main() {
    std::cout << "主线程开始,创建3个子线程" << std::endl;
    
    // 创建并启动线程
    std::thread t1(threadFunction, 1);
    std::thread t2(threadFunction, 2);
    std::thread t3(threadFunction, 3);
    
    // 等待线程完成
    t1.join();
    t2.join();
    t3.join();
    
    std::cout << "所有线程执行完毕" << std::endl;
    return 0;
}

2. 多种线程创建方式

// 1. 函数指针
void normalFunction() { /* ... */ }
std::thread t1(normalFunction);

// 2. Lambda表达式(推荐,简洁)
std::thread t2([]() {
    std::cout << "Lambda线程" << std::endl;
});

// 3. 函数对象(仿函数)
struct MyFunctor {
    void operator()() { /* ... */ }
};
std::thread t3(MyFunctor{});

// 4. 成员函数
class MyClass {
public:
    void memberFunction() { /* ... */ }
};
MyClass obj;
std::thread t4(&MyClass::memberFunction, &obj);

3. 线程生命周期管理

  1. 如果不调用 join() 或 detach(),thread 对象析构时会调用 std::terminate()终止程序.
std::thread t([]{ 
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "后台线程完成" << std::endl;
});

// 必须在销毁thread对象前调用join()或detach()
if (t.joinable()) {
    t.join();   // 等待完成
    // t.detach(); // 或分离到后台
} 
  1. 线程移动语义:std::thread是不可拷贝的,但支持移动语义。这允许线程所有权的转移。
#include <iostream>
#include <thread>
#include <vector>

void worker(int id) {
    std::cout << "Worker " << id << " running on thread: " 
              << std::this_thread::get_id() << std::endl;
}

int main() {
    // 1. 从临时对象构造(隐式移动)
    std::thread t1([]{
        std::cout << "Thread from lambda\n";
    });
    
    // 2. 移动构造:转移所有权
    std::thread t2 = std::move(t1);
    // 此时 t1 不再拥有线程(joinable() == false)
    
    std::cout << "t1 joinable: " << t1.joinable() << std::endl;  // 0
    std::cout << "t2 joinable: " << t2.joinable() << std::endl;  // 1
    
    // 3. 移动赋值
    std::thread t3;
    t3 = std::move(t2);  // 转移所有权到 t3
    // 注意:如果 t3 已有线程,会先调用 std::terminate()
    
    t3.join();
    
    // 4. 在容器中存储线程
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; ++i) {
        // emplace_back 直接构造
        threads.emplace_back(worker, i);
        
        // 或者 push_back 移动
        // std::thread t(worker, i);
        // threads.push_back(std::move(t));
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}
  1. 线程局部存储:每个线程都有自己的一个局部变量
  • 延迟初始化:每个线程第一次访问 thread_local变量时才初始化
  • 线程结束时销毁:变量在线程退出时按创建顺序的逆序销毁
  • 每个线程独立:每个线程都有自己独立的实例
// 普通全局变量 - 所有线程共享同一个
int global_counter = 0;

// thread_local 变量 - 每个线程有独立的副本
thread_local int thread_counter = 0;

void increment() {
    for (int i = 0; i < 5; ++i) {
        global_counter++;      // 修改共享的全局变量
        thread_counter++;      // 修改本线程的独立副本
        
        std::cout << "Thread " << std::this_thread::get_id() 
                  << ": global = " << global_counter
                  << ", thread_local = " << thread_counter << std::endl;
        
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
  1. 线程异常处理:线程中的异常不会自动传播到创建线程的上下文中,需要在线程内部处理。

4. 参数传递

  1. 值传递:参数被拷贝到新线程的上下文。确保线程拥有独立的数据,避免悬挂引用。
  2. 引用传递:必须使用 std::ref或 std::cref进行包装,否则会尝试拷贝引用本身(非法)。
void modify(int& val) { val = 100; }
int main() {
    int x = 10;
    // std::thread t(modify, x); // 错误!试图拷贝int&
    std::thread t(modify, std::ref(x)); // 正确:传递引用包装器
    t.join();
    std::cout << x << std::endl; // 输出 100
}
  1. 指针传递:传递指针(地址)本身是值传递(拷贝了指针值)。需确保指针所指向对象的生命周期覆盖线程的执行,否则会访问已释放内存。
  2. 移动语义:对于不可拷贝但可移动的对象(如 std::unique_ptr, std::future),使用 std::move。
void sink(std::unique_ptr<int> up) {}
int main() {
    auto up = std::make_unique<int>(99);
    std::thread t(sink, std::move(up)); // 所有权转移到线程内部
    t.join();
    // 此时 up 为空
}

同步原语

互斥锁

原理:给共享数据加一把锁,同一时间只有一个线程能访问,其他线程排队等着。

  1. std::mutex
  • 最基础、最常用的独占互斥锁。
  • 同一时间只能一个线程持有
  • 拿不到就阻塞休眠(不占 CPU)
  • 简单、安全、通用
  1. std::recursive_mutex 递归锁
  • 允许同一个线程多次加锁。
  • 适用场景:
  • 函数 A 加锁 → 调用函数 B,B 也加同一个锁。
  • 普通 mutex 会死锁,递归锁不会。
  1. std::timed_mutex / std::recursive_timed_mutex
  • 带超时的锁。
  • 拿不到锁不会死等,可以等一段时间就放弃。
  • 用来破坏死锁的 “不可抢占” 条件。
std::timed_mutex mtx;

// 等 100ms,拿不到就拉倒
if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
    // 拿到了
} else {
    // 没拿到
}
  1. std::shared_mutex 读写锁(C++17)
  • 读锁(shared lock):可以多人同时读
  • 写锁(unique lock):只能一个人写,且写的时候不能读
    适用场景:读多写少,比如配置、缓存、日志。
std::shared_mutex mtx;

// 读:加共享锁
mtx.lock_shared();

// 写:加独占锁
mtx.lock();
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <vector>
#include <chrono>

// 全局数据,读多写少
int g_data = 0;
// 读写锁
std::shared_mutex g_rw_mutex;

// 读者:多个可以同时读
void reader(int id) {
    for (int i = 0; i < 3; ++i) {
        // 🔥 读锁:shared_lock
        std::shared_lock<std::shared_mutex> lock(g_rw_mutex);

        std::cout << "读者 " << id << " 读到: " << g_data << '\n';

        // 模拟读耗时,多个读者可以并行
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

// 写者:同时只能有一个在写
void writer(int id) {
    for (int i = 0; i < 2; ++i) {
        // 🔥 写锁:unique_lock / lock_guard
        std::unique_lock<std::shared_mutex> lock(g_rw_mutex);

        g_data++;
        std::cout << "====== 写者 " << id << " 修改为: " << g_data << " ======\n";

        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}

int main() {
    // 2 个写者
    std::thread w1(writer, 1);
    std::thread w2(writer, 2);

    // 5 个读者
    std::thread r1(reader, 1);
    std::thread r2(reader, 2);
    std::thread r3(reader, 3);
    std::thread r4(reader, 4);
    std::thread r5(reader, 5);

    w1.join(); w2.join();
    r1.join(); r2.join(); r3.join(); r4.join(); r5.join();

    return 0;
}
  1. 锁的关键特性(必须懂)
  • 原子性:加锁、解锁本身是原子操作,CPU 指令保证不会被打断。
  • 可见性:解锁线程的所有修改,对随后加锁的线程全部可见。(相当于内存屏障)。unlock 之前,线程对内存的所有修改,必须刷回内存;lock 之后,线程必须从内存重新读,不能用旧缓存。
  • 有序性“”加锁 / 解锁会禁止编译器与 CPU 乱序重排。在 lock 之后的代码,不能重排到 lock 之前;
    在 unlock 之前的代码,不能重排到 unlock 之后。
  • 等待队列:锁内部维护一个等待队列,线程拿不到锁就进入队列休眠,不浪费 CPU。每个 mutex 内部都有一个等待队列(wait queue),而不是在拿不到锁的情况下一直循环等待,浪费cpu
    • 线程 A 拿到锁
    • 线程 B 来 lock,发现锁被占
    • 操作系统把线程 B 从运行队列移到锁的等待队列
    • 线程 B 进入休眠状态,完全不占 CPU
    • 线程 A unlock
    • 操作系统从等待队列唤醒一个线程,让它去拿锁

锁管理器

手动 lock/unlock 会因为异常导致死锁,C++ 必须用 RAII

std::lock_guard

它是 C++ 为 std::mutex 做的「自动锁开关」,利用 RAII 机制:构造时自动加锁,离开作用域时自动解锁。
你完全不用手动写 .lock() 和 .unlock(),从根源避免忘记解锁导致死锁。

#include <mutex>
std::mutex mtx;
int shared = 0;

void good_func() {
    // 🔥 构造时:自动调用 mtx.lock()
    std::lock_guard<std::mutex> lock(mtx);

    shared++;
    // 中间随便抛异常、return、break 都无所谓
} 
// 🔥 离开大括号作用域:lock_guard 析构,自动调用 mtx.unlock()

很少用的参数:std::adopt_lock:你已经手动锁了 mutex,想让 lock_guard 接管解锁,不再重复加锁。

mtx.lock();                  // 手动先锁
std::lock_guard lock(mtx, std::adopt_lock); // 只接管,不再加锁

std::unique_lock

  • std::unique_lock = 加强版的 std::lock_guard
  • 保留核心优点:RAII 自动解锁(离开作用域必解锁,绝不死锁)
  • 新增核心能力:手动控制锁(可以手动加锁、解锁、尝试加锁、超时加锁)
  1. 默认构造:创建时自动加锁,离开作用域自动解锁。
#include <mutex>
std::mutex mtx;

void func() {
    // 1. 自动调用 mtx.lock()
    std::unique_lock<std::mutex> lock(mtx);

    // 临界区:操作共享数据
    shared_data++;

} // 2. 自动调用 mtx.unlock(),绝对安全
  1. std::defer_lock → 只绑定,不加锁(最常用)
// 不自动加锁!只是把 mtx 和 lock 绑定
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

// 想加锁时手动加
lock.lock();
// 想解锁时手动解
lock.unlock();
  1. std::try_to_lock → 尝试加锁,不阻塞。拿不到锁直接返回,不卡死线程。
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
    // 成功拿到锁
} else {
    // 没拿到,去做别的事
}
  1. std::adopt_lock → 接管已加锁的 mutex。你已经手动加锁了,让 unique_lock 负责自动解锁:
mtx.lock(); // 手动先加锁
// 告诉 unique_lock:锁已经加好了,你帮我解锁就行
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
  1. try_lock():非阻塞尝试加锁
  2. try_lock_for(超时时间):超时尝试加锁
  3. owns_lock():判断是否持有锁

std::scoped_lock(C++17)

std::scoped_lock是 C++17 引入的 RAII 锁封装器,用于管理多个互斥量的锁定和解锁。它是对 std::lock_guard的增强,支持同时锁定多个互斥量而不会死锁。

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

std::mutex m1, m2, m3;

void using_lock_guard() {
    // 旧方式:可能死锁!
    // std::lock_guard<std::mutex> lk1(m1);
    // std::lock_guard<std::mutex> lk2(m2);  // 另一个线程可能以相反顺序锁定
    
    // 正确方式(C++11/C++14):
    std::lock(m1, m2, m3);  // 使用 std::lock 避免死锁
    std::lock_guard<std::mutex> lk1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> lk2(m2, std::adopt_lock);
    std::lock_guard<std::mutex> lk3(m3, std::adopt_lock);
    
    std::cout << "lock_guard: locked 3 mutexes\n";
    // 离开作用域自动解锁
}

void using_scoped_lock() {
    // C++17 新方式:一行搞定
    std::scoped_lock lock(m1, m2, m3);
    
    std::cout << "scoped_lock: locked 3 mutexes\n";
    // 离开作用域自动解锁
}

void compare() {
    std::thread t1([]{
        for (int i = 0; i < 100; ++i) {
            using_lock_guard();
        }
    });
    
    std::thread t2([]{
        for (int i = 0; i < 100; ++i) {
            using_scoped_lock();
        }
    });
    
    t1.join();
    t2.join();
}

int main() {
    compare();
    return 0;
}

死锁

死锁 4 条件(破坏一个就解决):

  • 互斥(锁独占)
  • 持有并等待(拿着锁等别的锁)
  • 不可抢占(锁不能抢)
  • 循环等待(A 等 B,B 等 A)

工程解法:固定加锁顺序、避免嵌套锁、用超时锁。


条件变量

线程需要等 “某个事情发生” 才能继续干活。如果没有条件变量,你只能这么写:

while (队列是空的) {
    // 啥也不干,一直循环检查
}

这种写法叫轮询。问题非常大:线程一直在跑,CPU 占用 100%;浪费电量、浪费性能;系统变卡。
条件变量就是解决这个问题的。它的作用只有一个:。让线程 “休眠等待”,直到别的线程通知它 “条件满足了”,它再醒来干活。休眠时,线程完全不占用 CPU,操作系统会把它挂起。

  1. 条件变量必须搭配互斥锁(mutex),因为:
  • 你要检查的 “条件”(比如队列是否为空)是共享变量
  • 多个线程会同时读写这个条件
  • 所以必须用锁保护,否则会出现数据竞争
  1. 条件变量只能用 std::unique_lock
  2. cv.wait(你的unique_lock锁, 条件判断函数);wait 会顺序执行以下 3 步,完全自动,你不用管:
  • 自动解锁
  • 线程进入休眠,直到有人调用 notify_one() 或 notify_all()。(通知 notify 不需要加锁)
  • 被唤醒后,自动重新加锁
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

// 共享队列
std::queue<int> q;

// 互斥锁,保护队列
std::mutex mtx;

// 条件变量,用于等待队列非空
std::condition_variable cv;

// 消费者线程
void consumer() {
    while (true) {
        // 消费者必须先加锁
        std::unique_lock<std::mutex> lock(mtx);

        // 等待:队列不为空
        // 如果为空,wait 会自动解锁并休眠
        // 被唤醒后,自动重新加锁
        while (q.empty()) {
            cv.wait(lock);
        }

        // 走到这里:
        // 1. 队列一定不为空
        // 2. 锁已经被当前线程持有

        int value = q.front();
        q.pop();

        // 用完数据,解锁
        lock.unlock();

        // 打印不需要锁
        std::cout << "消费者拿到数据:" << value << std::endl;

        // -1 代表结束
        if (value == -1) {
            break;
        }
    }
}

// 生产者线程
void producer() {
    // 生产 5 个数据
    for (int i = 1; i <= 5; ++i) {
        // 修改共享数据必须加锁
        std::lock_guard<std::mutex> lock(mtx);

        // 往队列放数据 → 条件发生变化
        q.push(i);

        // 锁自动释放
    }

    // 通知消费者:数据来了
    cv.notify_one();

    // 模拟耗时
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 最后发送结束信号
    {
        std::lock_guard<std::mutex> lock(mtx);
        q.push(-1);
    }
    cv.notify_one();
}

int main() {
    std::thread t_consumer(consumer);
    std::thread t_producer(producer);

    t_consumer.join();
    t_producer.join();

    return 0;
}

原子操作

原子操作是不可分割的操作,要么完全执行,要么完全不执行,不会被其他线程的操作打断。核心特性:

  1. 不可分割性
// 原子操作是硬件保证的
std::atomic<int> x(0);

// 这些操作是原子的:
x.load();           // 读
x.store(42);        // 写
x.fetch_add(1);     // 读-修改-写
x.exchange(100);    // 交换
x.compare_exchange_weak(expected, desired);  // 比较并交换
  1. 内存序
std::atomic<int> data(0);
std::atomic<bool> ready(false);

// 线程1:生产者
data.store(42, std::memory_order_relaxed);
ready.store(true, std::memory_order_release);  // 释放屏障

// 线程2:消费者
while (!ready.load(std::memory_order_acquire)) {}  // 获取屏障
int val = data.load(std::memory_order_relaxed);
// 这里保证看到 data = 42

无锁编程

不使用互斥锁来实现线程安全,通常依赖原子操作和CAS。CAS(Compare-And-Swap)是无锁编程的核心操作。

Future/Promise模式

多线程之间,怎么把一个 “结果” 从线程 A 传给线程 B?
以前只能用:全局变量、锁、条件变量。很麻烦,很容易写错。C++ 给了一个高级、安全、自带同步的工具:

  • promise 是 “放数据的人”
  • future 是 “拿数据的人”

它们天生线程安全,不需要锁,不需要条件变量。
std::promise 生产者:在某个线程里负责存入一个值或者抛出一个异常
std::future 消费者:在另一个线程里负责等待结果。结果没好就阻塞休眠,结果好了就拿到值。
它们是一对一对出现的。

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

// 子线程:生产者,用 promise
func1(std::promise<int> p) {
    // 计算...
    int result = 999;

    // 把结果放进去
    p.set_value(result);
}

// 主线程:消费者,用 future
int main() {
    // 创建一对:promise 和 future
    std::promise<int> p;
    std::future<int> f = p.get_future();

    // 把 promise 交给子线程
    std::thread t(func1, std::move(p));

    // 等待结果(阻塞等,不浪费CPU)
    int value = f.get();

    std::cout << value << std::endl; // 输出 999

    t.join();
    return 0;
}

std::future 的重要规则:

  • get() 只能调用一次
  • 拿完就失效了
  • 调用 get() 如果结果没好,会阻塞等待
  • 不需要锁,线程安全
  • 一个 future 只能被一个线程使用

std::future的用法

  1. wait():等待结果,但不获取。
  2. get():等待并获取结果。只能调用一次,如果异步函数抛出异常,get会把异常传递出来。
  3. auto status = f.wait_for(std::chrono::seconds(1));等待一段时间,看是否就绪:
    • std::future_status::ready:结果好了
    • std::future_status::timeout:超时了
    • std::future_status::deferred:延迟执行
  4. valid():检查是否有效,如果get过,就是false,无效了。
  5. share():把普通future转为shared_future

std::shared_future 是什么?

  • 可以被多个线程共享、多次 get () 的 future。
  • 可以拷贝
  • 可以给多个线程
  • 每个线程都能调用 get()
  • 大家拿到同一个结果
std::future<int> f = p.get_future();
std::shared_future<int> sf = f.share();

异常传播机制

子线程抛出异常,子线程捕获

func1(std::promise<int> p) {
    try {
        // 子线程出错
        throw std::runtime_error("子线程爆炸了");
    } catch (...) {
        // 把异常放进 promise
        p.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future();

    std::thread t(func1, std::move(p));

    try {
        // 主线程 get() 的时候,异常会被抛出来!
        int v = f.get();
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
        // 输出:子线程爆炸了
    }

    t.join();
    return 0;
}

异步执行

  1. std::async: 异步执行。让一个函数 “在后台异步运行” 的工具。不用自己创建 thread,不用自己写 promise,自动给你返回 future 拿结果。
#include <iostream>
#include <future>
using namespace std;

int func() {
    return 100;
}

int main() {
    // 异步执行 func()
    future<int> f = async(func);

    // 等待执行完,拿结果
    int res = f.get();
    cout << res << endl;  // 输出 100
}
  1. std::async 的两种启动策略
  • std::launch::async:立即开新线程,立刻后台运行async(launch::async, func);
  • std::launch::deferred:不开线程,延迟执行,直到你调用 get () 才跑!async(launch::deferred, func);

打包任务

std::packaged_task 就是一个包装器。把一个普通函数,包装成 “可以异步执行、并且能通过 future 获取结果 / 异常” 的任务。三种使用方式:

  1. 直接使用,顺序执行
packaged_task<int()> task([]{ return 123; });
task();  // 直接运行
auto f = task.get_future();
cout << f.get();
  1. 传给 std::thread(异步)
#include <iostream>
#include <future>
#include <thread>
using namespace std;

// 普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 1. 把函数包装成 packaged_task
    packaged_task<int(int, int)> task(add);

    // 2. 从任务里拿 future
    future<int> f = task.get_future();

    // 3. 把任务交给线程执行
    thread t(std::move(task), 10, 20);

    // 4. 拿结果
    int res = f.get();
    cout << res << endl;  // 输出 30

    t.join();
    return 0;
}
  1. 传给线程池(最真实、最常用)。这才是 packaged_task 存在的真正意义!你可以把任务存起来、排队、交给线程池执行。async 做不到,promise 太麻烦。
// 线程池里放一堆任务
std::vector<std::packaged_task<int()>> tasks;

// 添加任务
std::packaged_task<int()> task([]{ return 100; });
tasks.push_back(std::move(task));

// 线程池取出执行
auto& t = tasks[0];
t();

注意:

  • packaged_task 不能拷贝,只能移动(move)
  • 必须用 std::move 传给线程
  • 一个任务只能执行一次
  • 执行完之后,任务就无效了
  • 必须先 get_future (),再执行任务
  • 异常会自动传给 future

线程池

  1. 线程池解决 3 个问题:
  • 避免频繁创建 / 销毁线程的开销
  • 控制同时运行的线程数量
  • 任务来了直接执行,不用等线程创建
  1. 线程池内部长啥样?(结构超级简单)
  • 一个标准线程池,只由 4 部分组成:
  • 一批工作线程(固定数量,比如 4、8、16 个)
  • 一个任务队列(存放要执行的函数 / 任务)
  • 一把互斥锁(保护任务队列)
  • 一个条件变量(线程等待任务时睡觉,不占 CPU)
  1. 线程数量怎么设?
    (1)CPU 密集型任务(计算多):线程数 = CPU 核心数(或 +1)
    IO 密集型任务(网络、文件、数据库):线程数可以多一些,比如 CPU 核心数 × 2 或更多
    (2)任务队列:用 std::queue 存放任务,先进先出。
    (3)为什么必须用条件变量?没有任务时,线程睡觉不占 CPU;任务来了,再叫醒。
    (4)为什么必须用锁?多个线程同时取任务,队列是共享的,必须互斥。
    (5)线程池销毁时做什么?
    • 标记停止
    • 唤醒所有线程
    • 等待所有线程执行完当前任务并退出避免内存泄漏、崩溃

同步工具

信号量

栅栏

门闩

并行算法

执行策略

并行STL算法

协程

线程安全设计


子线程函数参数传递

  1. 值传递:参数会被复制到新线程的独立空间
  2. 引用传递:需用 std::ref(或只读引用 std::cref)包装,否则会被当作值拷贝。
void modifyValue(int& value) {
    value *= 2;
}
int main() {
    int sharedValue = 42;
    // 错误:std::thread t(modifyValue, sharedValue); 会尝试拷贝int&,编译失败
    std::thread t(modifyValue, std::ref(sharedValue)); // 正确
    t.join();
    std::cout << "Modified value: " << sharedValue << std::endl; // 输出 84
    return 0;
}
  1. 指针传递:传递的是地址的拷贝,但指向同一块内存。
  2. 移动语义传递:对于像 std::unique_ptr或大数据容器,可用 std::move转移所有权,避免拷贝。
void takeOwnership(std::unique_ptr<int> ptr) {
    std::cout << "Value: " << *ptr << std::endl;
}
int main() {
    auto uniquePtr = std::make_unique<int>(100);
    // std::thread t(takeOwnership, uniquePtr); // 错误:unique_ptr不可拷贝
    std::thread t(takeOwnership, std::move(uniquePtr)); // 正确:转移所有权
    t.join();
    // 此后uniquePtr不再拥有对象
    return 0;
}

子线程获取返回值

线程函数本身不直接返回给调用者,需通过异步操作工具获取结果。

  1. std::async自动创建线程(或利用线程池)执行任务,并返回一个 std::future对象用于获取结果
#include <future>
int computeSquare(int n) {
    return n * n;
}
int main() {
    // 异步执行任务,std::launch::async确保在新线程中执行
    std::future<int> result = std::async(std::launch::async, computeSquare, 12);
    // ... 主线程可同时做其他工作 ...
    int value = result.get(); // 获取结果(若未完成会阻塞等待)
    std::cout << "The square is: " << value << std::endl; // 输出 144
    return 0;
}
  1. std::packaged_task将可调用对象包装起来,允许您获取与之关联的 std::future。它本身不自动创建线程,需手动将其传递给 std::thread
#include <future>
int heavyComputation() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}
int main() {
    // 包装任务
    std::packaged_task<int()> task(heavyComputation);
    // 获取与任务关联的future
    std::future<int> result = task.get_future();
    // 将任务移动到线程中执行
    std::thread t(std::move(task));
    t.detach(); // 或使用join()
    std::cout << "Waiting for result..." << std::endl;
    std::cout << "Result: " << result.get() << std::endl; // 阻塞直到获取结果
    return 0;
}
  1. 通过 std::promise对象,在线程中任意位置显式设置返回值
#include <future>
void workerTask(std::promise<int>&& resultPromise, int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    int result = a + b;
    resultPromise.set_value(result); // 设置结果
}
int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future(); // 获取future
    std::thread t(workerTask, std::move(prom), 10, 20);
    // ... 其他工作 ...
    int sum = fut.get(); // 获取结果
    std::cout << "Sum received: " << sum << std::endl; // 输出 30
    t.join();
    return 0;
}
  • 异常处理:若在线程函数中抛出异常且未捕获,异常会被传递到 std::future的 get()方法处重新抛出
  • std::future::get()的阻塞性:get()方法会阻塞调用线程,直到结果可用。确保在需要结果时才调用,或使用 wait_for/wait_until查询状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值