c++ 多线程知识汇总

一、std::thread

std::thread 是 C++11 引入的标准库中的线程类,用于创建和管理线程

1. 带参数的构造函数

cpp 复制代码
template <class F, class... Args>
std::thread::thread(F&& f, Args&&... args);

F&& f:线程要执行的函数;Args&&... args:可变参数,用于将参数转发到函数 f

2. 方法

cpp 复制代码
void join(); 等待线程结束。 注:线程必须是可 join 的(即线程正在运行且未被分离)
void detach(); 分离线程,使其独立运行。 注:线程必须正在运行且未被分离
void swap(std::thread& other); 交换两个线程对象
std::thread::id get_id() const; 获取线程的唯一标识符
bool joinable() const; 检查线程是否可以被 join
std::thread& operator=(std::thread&& other); 移动赋值运算符,用于将一个线程对象移动到另一个线程对象

3. 简单例子

3.1 普通函数作为线程执行函数

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

void threadFunction(int id) {
    std::cout << "Thread " << id << " is running" << std::endl;
}

int main() {
    std::thread t(threadFunction, 1); // 创建线程并传递参数
    if (t.joinable()) {
        t.join(); // 等待线程结束
    }
    // t.dedetach(); // 分离线程 ,分离的线程会在后台独立运行,即使创建它的线程已经结束
    return 0;
}

注意 : 如果需要传引用时,临时变量不行,可以用std::ref(变量名)把他转成引用类型
        传递指针或引用时不能用临时变量。离开作用域就销毁了,线程函数里就取不到了
        临时的类对象也不行,都会导致错误,总之,要控制好声明周期,保证线程执行时能正确使用。

3.2 类的成员函数作为线程执行函数

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

class MyClass {
public:
    void memberFunction(int id) {
        std::cout << "Thread " << id << " is running in MyClass" << std::endl;
    }
};

int main() {
    MyClass obj;
    std::thread t(std::bind(&MyClass::memberFunction, &obj, 1)); // 使用 std::bind 绑定成员函数和对象
    t.join();
    return 0;
}

4. 互斥量

线程安全:线程安全的代码能够保证在并发执行时,程序的行为符合预期,且不会因线程之间的竞争条件(Race Condition)而产生错误。下面代码,线程不安全,输出是不固定的。

cpp 复制代码
int shared_data = 0;

void myThread()
{
    for (int i=0; i < 10000; ++ i)
    {
        shared_data++;
    }
}

int main()
{
    std::thread t1(myThread);
    std::thread t2(myThread);

    t1.join();
    t2.join();

    std::cout<<"shared_data = "<<shared_data<<std::endl;
    return 0;
}

互斥量是一种同步原语,用于保护共享资源,防止多个线程同时访问。互斥量确保同一时间只有一个线程可以持有互斥量,从而保证对共享资源的互斥访问。确保线程安全。

C++11 引入了 <mutex> 头文件,其中定义了多种类型的互斥量。

4.1 std::mutex

**std::mutex**是最基本且最常用的互斥量类型,它不支持递归加锁。

cpp 复制代码
int shared_data = 0;
std::mutex mtx; // 定义互斥量

void myThread()
{
    mtx.lock();
    for (int i=0; i < 10000; ++ i)
    {
        shared_data++;
    }
    mtx.unlock();
}

int main()
{
    std::thread t1(myThread);
    std::thread t2(myThread);

    t1.join();
    t2.join();

    std::cout<<"shared_data = "<<shared_data<<std::endl;
    return 0;
}

此时无论运行多少次,输出都是相同的。

4.2 std::lock_guard

为了简化互斥量的使用 C++ 提供了 std::lock_guard 它是一个 RAII 风格的工具,用于自动管理互斥量的加锁和解锁。**lock_guard 对象**不能复制或移动,因此他只能在局部作用域中。

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

int shared_data = 0;
std::mutex mtx; // 定义互斥量

void myThread()
{
    std::lock_guard<std::mutex> lock(mtx); // 自动加锁(析构中自动解锁)
    for (int i=0; i < 10000; ++ i)
    {
        shared_data++;
    }
}

int main()
{
    std::thread t1(myThread);
    std::thread t2(myThread);

    t1.join();
    t2.join();

    std::cout<<"shared_data = "<<shared_data<<std::endl;
    return 0;
}

4.3 std::unique_lock

4.3.1 概念

std::unique_lock 是 C++ 标准库中用于管理互斥锁的一种智能锁类,它提供了比 std::lock_guard 更灵活的锁管理功能。std::unique_lock 不仅可以自动管理锁的获取和释放,还可以在需要时手动控制锁的行为,例如延迟锁的获取、尝试锁的获取以及在特定条件下释放锁等。

4.3.2 特点

std::unique_lock允许在构造时延迟锁的获取,或者在运行时尝试获取锁,而 std::lock_guard 只能在构造时立即获取锁

4.3.3 方法

cpp 复制代码
// 绑定到互斥量的构造函数
std::unique_lock(std::mutex& m):立即获取互斥量 m。
std::unique_lock(std::mutex& m, std::defer_lock_t):延迟获取互斥量 m,需要后续手动调用 lock()。
std::unique_lock(std::mutex& m, std::try_to_lock_t):尝试立即获取互斥量 m,如果失败则不会阻塞。
std::unique_lock(std::mutex& m, std::adopt_lock_t):假设互斥量 m 已经被当前线程锁定,std::unique_lock 只是接管锁的所有权。

成员函数

void lock(); 尝试获取互斥量。如果互斥量已被其他线程锁定,则当前线程会阻塞,直到互斥量可用。
bool try_lock(); 尝试立即获取互斥量。如果成功获取,则返回 true;如果失败,则返回 false,且不会阻塞。

template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
尝试在指定的相对时间 rel_time 内获取互斥量。

template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time);
尝试在指定的绝对时间 abs_time 之前获取互斥量

void unlock();
释放互斥量。如果互斥量未被当前线程锁定,则行为未定义。
bool owns_lock() const;
返回 true 如果当前 std::unique_lock 对象拥有互斥量的所有权,否则返回 false。
std::mutex* mutex() const;
返回绑定的互斥量的指针,如果没有绑定互斥量,则返回 nullptr。
void swap(std::unique_lock& other);
交换当前对象和另一个 std::unique_lock 对象的状态
std::mutex* release();
返回指向它所管理的互斥量的指针,并释放所有权。
4.3.4 代码示例

① 立即获取锁,与**lock_guard相同,立即获取锁**

cpp 复制代码
int shared_data = 0;
std::mutex mtx;

void myThread()
{
    std::unique_lock<std::mutex> u_lock(mtx);
    for (int i=0; i < 10000; ++ i)
    {
        shared_data++;
    }
}

int main()
{
    std::thread t1(myThread);
    std::thread t2(myThread);

    t1.join();
    t2.join();

    std::cout<<"shared_data = "<<shared_data<<std::endl;
    return 0;
}

② 尝试立即加锁

cpp 复制代码
std::mutex mtx;
void foo() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock); // 尝试立即加锁
    if (lock.owns_lock()) {
        // 成功获取锁
        // 临界区代码
    } else {
        // 未能获取锁
    }
}

③ 尝试在指定的时间段内获取互斥锁

需要结合可超时互斥量,后面介绍。

cpp 复制代码
int shared_data = 0;
std::timed_mutex mtx; // 可超时互斥量

void myThread()
{
    // 构造函数传入defer_lock 表示构造但是不加锁,需要手动加锁,结合延迟使用
    std::unique_lock<std::timed_mutex> u_lock(mtx, std::defer_lock);
    //尝试加锁,只等待1秒,1秒内没获取到资源直接返回了
    if (u_lock.try_lock_for(std::chrono::seconds(1)))
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));// 睡三秒
        for (int i=0; i < 10000; ++ i)
        {
            shared_data++;
        }
    }
}

int main()
{
    std::thread t1(myThread);
    std::thread t2(myThread);

    t1.join();
    t2.join();

    std::cout<<"shared_data = "<<shared_data<<std::endl;
    return 0;
}
// 输出1000 或两千

⑤ try_lock_until 等待获取资源,超过了指定时间,就不阻塞了,直接返回。(不常用)

4.4 std::timed_mutex

4.4.1 概念

std::timed_mutex 是 C++ 标准库中 <mutex> 头文件定义的一种互斥量类型,它是一种可超时的互斥量,提供了在尝试锁定时支持超时的功能。它继承自 std::mutex,和普通互斥量一样,std::timed_mutex 保证同一时刻只有一个线程可以持有锁,用于保护共享资源。

4.4.2 主要成员函数
cpp 复制代码
1. lock()
阻塞当前线程,直到获取锁为止。
如果其他线程已经持有锁,则当前线程会阻塞,直到锁被释放。
2. try_lock()
尝试立即获取锁。
如果锁当前可用,则获取锁并返回 true;如果锁已被占用,则返回 false,不会阻塞。
3. try_lock_for(std::chrono::duration)
尝试在指定的时间间隔内获取锁。
如果在超时期间内获取到锁,则返回 true;否则返回 false。
4. try_lock_until(std::chrono::time_point)
尝试在指定的时间点之前获取锁。
如果在指定时间点之前获取到锁,则返回 true;否则返回 false。
5. unlock()
释放锁,允许其他线程获取锁。

4.5 互斥量死锁

4.5.1 死锁概念

死锁指的是两个或多个线程因为相互等待对方持有的资源而无法继续执行的状态。

cpp 复制代码
死锁的四个必要条件
根据死锁的理论,发生死锁需要同时满足以下四个必要条件:
互斥条件(Mutual Exclusion):
资源不能被共享,只能被一个线程独占使用。
例如,互斥量(std::mutex)确保同一时间只有一个线程可以访问资源。
请求和保持条件(Hold and Wait):
一个线程已经持有了某个资源,但又请求其他资源,而这些资源已经被其他线程持有。
例如,线程 A 持有资源 X,但请求资源 Y;线程 B 持有资源 Y,但请求资源 X。
不可剥夺条件(No Preemption):
已经分配给线程的资源不能被强制剥夺,只能由持有资源的线程主动释放。
例如,互斥量一旦被线程加锁,其他线程无法强制解锁。
循环等待条件(Circular Wait):
存在一个线程等待资源的循环链,即线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,线程 C 等待线程 A 持有的资源。
例如,线程 A 等待资源 Y,线程 B 等待资源 X,而资源 X 和 Y 分别被线程 B 和线程 A 持有。
4.5.2 死锁的示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;

void threadFunction1() {
    std::cout << "Thread 1: Trying to lock mtx1" << std::endl;
    mtx1.lock();
    std::cout << "Thread 1: Locked mtx1, trying to lock mtx2" << std::endl;
    mtx2.lock();
    std::cout << "Thread 1: Locked mtx2" << std::endl;

    // 业务逻辑
    std::this_thread::sleep_for(std::chrono::seconds(1));

    mtx2.unlock();
    mtx1.unlock();
}

void threadFunction2() {
    std::cout << "Thread 2: Trying to lock mtx2" << std::endl;
    mtx2.lock();
    std::cout << "Thread 2: Locked mtx2, trying to lock mtx1" << std::endl;
    mtx1.lock();
    std::cout << "Thread 2: Locked mtx1" << std::endl;

    // 业务逻辑
    std::this_thread::sleep_for(std::chrono::seconds(1));

    mtx1.unlock();
    mtx2.unlock();
}

int main() {
    std::thread t1(threadFunction1);
    std::thread t2(threadFunction2);

    t1.join();
    t2.join();

    return 0;
}
4.5.3 避免死锁的方法

固定加锁顺序

② 使用 std::try_lock 或超时机制

5. std::call_once(只能在线程里使用)

5.1 概念

std::call_once 是 C++11 引入的一个非常有用的工具,用于确保某个函数或操作只被调用一次,即使在多线程环境中也能保证线程安全。它常用于实现单例模式、初始化全局资源等场景。

5.2 单例模式,多线程情况下,初始化可能被执行多次,违背了单例模式,可以把初始化操作封到一个成员函数里,然后把这个函数加上 **call_once 。**避免被多次初始化。

**6.**std::condition_variable 条件变量

6.1 概念

std::condition_variable 是 C++ 标准库中用于线程同步的工具之一,它允许线程在某些条件尚未满足时进入休眠状态,并在条件满足时被唤醒。 通常与互斥锁(std::mutexstd::timed_mutex)配合使用,以实现线程间的协调和同步。

6.2 生产者与消费者模型

生产者与消费者之间存在一个任务队列,生产者负责产生任务,存入任务队列,并通知消费者需要干活了,消费者在任务队列为空时就会等待。直到有数据了并接到了生产者的通知(唤醒),被唤醒后则从队列中取出任务执行。

6.3 代码示例

wait的实现原理:函数接收一个锁定的对象,和一个谓词。当调用时,先释放锁,当前线程进入休眠,等待被唤醒,被唤醒时尝试获取锁,判断谓词是否为真,都满足了该线程继续执行,不满足则继续调用wait睡眠(谓词就是可以避免虚假唤醒)。

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
#include <atomic>

// 共享资源
std::queue<int> data_queue; // 数据队列
std::mutex mtx;             // 互斥锁
std::condition_variable cv; // 条件变量
std::atomic<bool> done(false); // 标志位,表示生产者是否完成

// 生产者函数
void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        data_queue.push(i); // 生产数据并放入队列
        std::cout << "Produced: " << i << std::endl;
        cv.notify_one(); // 唤醒一个消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间
    }
    done = true; // 生产者完成
    cv.notify_all(); // 唤醒所有消费者
}

// 消费者函数
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !data_queue.empty() || done.load(); }); // 等待队列非空或生产者完成

        if (done.load() && data_queue.empty()) {
            break; // 如果生产者完成且队列为空,退出循环
        }

        int data = data_queue.front(); // 从队列中取出数据
        data_queue.pop();
        std::cout << "Consumed-------: " << data << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费时间
    }
}

int main() {
    std::thread producer_thread(producer); // 创建生产者线程
    std::thread consumer_thread(consumer); // 创建消费者线程

    producer_thread.join(); // 等待生产者线程完成
    consumer_thread.join(); // 等待消费者线程完成

    return 0;
}

7. 线程池

7.1 概述

线程池是一种用来管理和复用线程的技术。它通过提前创建一定数量的线程(工作线程),这些线程处于等待状态,等待从任务队列中获取任务并执行。任务执行完毕后,线程返回线程池等待处理下一个任务。线程池的核心思想是减少线程创建和销毁的开销,提高程序的性能和资源利用率。

cpp 复制代码
优势:
①减少线程创建和销毁的开销:
线程的创建和销毁是一个相对耗时的操作,线程池通过复用线程避免了频繁创建和销毁线程的开销。
②提高程序的并发性能:
线程池可以合理控制线程的数量,避免因线程过多导致系统资源耗尽,从而提高程序的并发性能。
③简化线程管理:
线程池提供了一种统一的线程管理机制,使得线程的创建、调度和销毁更加方便和高效

7.2 示例代码

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <vector>
#include <memory>
#include <future>

class ThreadPool {
public:
    ThreadPool(size_t num_threads);
    ~ThreadPool();
    template <class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

ThreadPool::ThreadPool(size_t num_threads) : stop(false) {
    for (size_t i = 0; i < num_threads; ++i) {
        workers.emplace_back([this] {
            while (true) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(this->queue_mutex);
                    this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
                    if (this->stop && this->tasks.empty()) {
                        return;
                    }
                    task = std::move(this->tasks.front());
                    this->tasks.pop();
                }
                task();
            }
        });
    }
}

ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for (std::thread& worker : workers) {
        worker.join();
    }
}

template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;
    auto task = std::make_shared<std::packaged_task<return_type()>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );
    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        if (stop) {
            throw std::runtime_error("enqueue on stopped ThreadPool");
        }
        tasks.emplace([task]() { (*task)(); });
    }
    condition.notify_one();
    return res;
}

// 示例用法
int main() {
    ThreadPool pool(4);

    auto result1 = pool.enqueue([](int answer) { return answer; }, 42);
    auto result2 = pool.enqueue([](int a, int b) { return a + b; }, 5, 7);

    std::cout << "Result 1: " << result1.get() << std::endl;
    std::cout << "Result 2: " << result2.get() << std::endl;

    return 0;
}
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>

class TheradPool
{
public:
    TheradPool(int thread_num) : stop_(false)
    {
        for (int i=0; i < thread_num; ++i)
        {
            threads_.emplace_back([this]()
            {
                while(1)
                {
                    std::unique_lock<std::mutex> uptr_lock(mtx_);
                    condition_.wait(uptr_lock, [this]()
                    {
                        return !tasks_.empty() || stop_;
                    });

                    if (stop_ && tasks_.empty())
                    {
                        return;
                    }

                    std::function<void()> task(std::move(tasks_.front()));
                    tasks_.pop();
                    uptr_lock.unlock();
                    task();
                }
            });
        }
    }


// F 是函数参数类型,表示可调用对象的类型(如函数指针、lambda 表达式、函数对象等)
// Args... 是可变参数模板,表示可调用对象的参数类型列表。... 表示参数包,可以接受任意数量和类型的参数
    template <typename F, typename... Args>
    void enqueue(F&& f, Args&&... args)
    {
        // F&& f 是一个右值引用,表示可调用对象。使用右值引用是为了支持完美转发,
         //即可以将 f 作为左值或右值传递给后续的调用
        std::function<void()> task = std::bind(std::forward<F> (f), std::forward<Args>(args)...);
        {
            std::unique_lock<std::mutex> uptr_lock(mtx_);
            tasks_.emplace(std::move(task));
        }

        condition_.notify_one();
    }


    ~TheradPool()
    {
        // 控制锁的作用域,设置完stop_立即释放锁,确保其他线程能拿到锁,避免死锁
        {
            std::unique_lock<std::mutex> uptr_lock(mtx_);
            stop_ = true;
        }
        
        condition_.notify_all();
        for (auto &thread : threads_)
        {
            thread.join();
        }
    }

private:
    std::vector<std::thread> threads_;
    std::queue<std::function<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable condition_;
    bool stop_;

};

8. 异步并发 (async , future)

8.1 async

s**td::**async 是 C++11 引入的一个用于异步任务执行的函数模板,它提供了一种简单的方式来启动一个任务,并通过 std::future 获取任务的返回值。

std::async :用于启动一个异步任务,返回一个 std::future 对象,通过该对象可以获取任务的返回值或处理任务中的异常;std::future:表示异步操作的结果,可以用来获取异步任务的返回值或检查任务是否完成。

cpp 复制代码
std::future<R> std::async(std::launch policy, F&& f, Args&&... args);
返回值:std::async 返回一个 std::future<R> 对象,其中 R 是函数 f 的返回类型。std::future 是一个用于获取异步操作结果的对象。
参数:
std::launch policy:启动策略,用于指定任务的执行方式。
F&& f:可调用对象(如函数指针、lambda 表达式、函数对象等)。
Args&&... args:可调用对象的参数列表,支持可变参数。
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int add(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return a + b;
}

int main() {
    std::future<int> result = std::async(add, 5, 3); // 使用默认策略
    std::cout << "Waiting for the result..." << std::endl;
    int sum = result.get(); // 阻塞等待任务完成
    std::cout << "Result: " << sum << std::endl;
    return 0;
}

8.2 std::packaged_task

std::packaged_task 是 C++11 引入的一个模板类,用于包装可调用对象(如函数、lambda 表达式、函数对象等),使其能够异步执行,并通过 std::future 获取任务的执行结果。

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

// 一个简单的函数,计算整数的平方
int square(int x) {
    return x * x;
}

int main() {
    // 创建一个 std::packaged_task,包装 square 函数
    std::packaged_task<int(int)> task(square);
    // 获取与 packaged_task 关联的 std::future 对象
    std::future<int> result = task.get_future();
    // 将任务交给一个线程异步执行
    std::thread t(std::move(task), 10); // 传递参数 10 给 square 函数
    // 等待任务完成并获取结果
    int value = result.get();
    std::cout << "The square of 10 is " << value << std::endl;
    t.join(); // 等待线程结束
    return 0;
}

8.3 std::promise

std::promise 是 C++11 引入的一个模板类,用于存储异步操作的结果,并通过 std::future 将结果传递给其他线程。std::promisestd::future 通常一起使用,用于实现线程间的异步通信和结果传递。

cpp 复制代码
主要功能
存储异步操作的结果:std::promise 可以存储一个值或异常,作为异步操作的结果。
与 std::future 关联:通过 std::promise 的 get_future() 方法,可以获取一个与之关联的 std::future 对象。
设置结果:通过 std::promise 的 set_value() 方法设置操作的返回值,或通过 set_exception() 方法设置异常。
线程间通信:std::promise 和 std::future 提供了一种机制,允许在不同线程之间安全地传递异步操作的结果。
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

void worker(std::promise<int> promise) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    promise.set_value(42); // 设置异步操作的结果
}

int main() {
    std::promise<int> promise; // std::promise 对象,用于存储异步操作的结果
    std::future<int> future = promise.get_future(); // 获取与 promise 关联的 future 对象

    std::thread t(worker, std::move(promise)); // 启动一个线程,将 promise 传递给 worker 函数
    std::cout << "Waiting for the result..." << std::endl;

    int result = future.get(); // 阻塞等待结果
    std::cout << "Result: " << result << std::endl;

    t.join(); // 等待线程结束
    return 0;
}

8.4 std::async 和 std::thread 有什么区别?

std::asyncstd::thread 都是 C++ 标准库中用于实现并发和多线程的工具,但它们在功能、使用方式和用途上存在一些重要区别。

**std::thread **它直接创建一个线程,并且完全由用户控制线程的生命周期。适合需要手动管理线程的场景,例如需要精确控制线程的创建、启动、销毁以及线程间的同步;

std::async 它用于启动一个异步任务,并且返回一个 std::future 对象,用于获取异步任务的结果。适合用于启动一个异步任务,并且希望在任务完成后获取结果的场景。它隐藏了线程管理的细节,更侧重于任务的异步执行。

std::thread 没有返回值 std::async 返回一个 std::future 对象 通过 std::futureget()wait() 方法可以获取异步任务的返回值。

  • std::thread

    • 性能:创建和销毁线程的开销较大,特别是频繁创建和销毁线程时。

    • 资源管理:需要手动管理线程资源,容易出现资源泄漏或线程死锁等问题。

  • std::async

    • 性能:内部可能使用线程池,减少了线程的创建和销毁开销。

    • 资源管理:由标准库管理,减少了资源管理的复杂性。


9. 原子操作 atomic

std::atomic 是 C++11 标准库中引入的一个模板类,用于实现原子操作。它确保对变量的读取、修改和存储操作是不可分割的(即原子的),即使在多线程环境下也不会出现中间状态被其他线程干扰的情况.

cpp 复制代码
常用类型
C++ 标准库提供了多种原子类型,包括:
std::atomic<bool>:原子布尔类型
std::atomic<int>:原子整数类型
std::atomic<T*>:原子指针类型
std::atomic<std::shared_ptr<T>>:原子共享指针类型
cpp 复制代码
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0);

void increment_counter() {
    for (int i = 0; i < 100000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed); // 原子加1
    }
}

int main() {
    std::thread t1(increment_counter);
    std::thread t2(increment_counter);

    t1.join();
    t2.join();

    std::cout << "Counter value: " << counter.load() << std::endl; // 安全地读取值
    return 0;
}

10. 线程间通信的方式

除了 前面提到的std::promisestd::future,C++ 提供了多种线程间通信机制,每种机制都有其适用场景和特点。以下是一些常见的替代方案:

  1. 共享内存 + 同步原语

共享内存是最直接的线程间通信方式,通过全局变量、静态变量或堆上分配的对象实现。为了防止数据竞争,需要使用同步原语(如互斥锁 std::mutex)来保护对共享变量的访问。

  1. 消息队列

消息队列允许线程通过发送和接收消息来交换信息,而不是直接操作共享内存。可以使用 std::queue 和条件变量 std::condition_variable 实现一个简单的线程安全消息队列。

  1. 条件变量

条件变量是一种同步原语,允许线程等待某个条件成立,然后被其他线程唤醒。它通常与互斥锁 std::mutex 配合使用,适用于复杂的同步逻辑。

  1. 信号量

信号量是一种计数器,用于控制对共享资源的访问。线程可以通过 acquire() 请求资源,通过 release() 释放资源。C++20 引入了 std::counting_semaphore,可以方便地实现线程间的同步。

  1. 读写锁

读写锁(std::shared_mutex)允许多个线程同时读取共享资源,但写操作需要独占访问。它适用于读多写少的场景。

  1. 事件

事件是一种简单的同步机制,线程可以等待某个事件的发生,而其他线程可以触发事件。可以通过条件变量实现。

相关推荐
在成都搬砖的鸭鸭13 分钟前
【LeetCode】时间复杂度和空间复杂度
算法·leetcode·golang·排序算法
Mercury_Lc14 分钟前
【力扣 - 简单题】88. 合并两个有序数组
数据结构·算法·leetcode·acm
qy发大财16 分钟前
全排列(力扣46)
算法·leetcode·职场和发展
欧了11116 分钟前
动态规划LeetCode-1049.最后一块石头的重量Ⅱ
c语言·算法·leetcode·动态规划·01背包
tamak17 分钟前
c/c++蓝桥杯经典编程题100道(21)背包问题
c语言·c++·蓝桥杯
冠位观测者18 分钟前
【Leetcode 每日一题】1760. 袋子里最少数目的球
数据结构·算法·leetcode
GGGGGGGGGGGGGG.25 分钟前
基于 openEuler 构建 LVS-DR 群集
运维·服务器·lvs
菠菠萝宝1 小时前
【代码随想录】第八章-贪心算法
算法·贪心算法·排序算法·合并区间·加油站·找零·监控二叉树
c-c-developer1 小时前
C++ Primer 条件语句
c++
啊森要自信1 小时前
【linux学习指南】模拟线程封装与智能指针shared_ptr
linux·运维·服务器·vscode·ubuntu