文章详情 学习记录

C++学习(十五)

发布时间:2026-09-17 16:20 浏览次数:77 最后更新:2026-09-17 16:20

c++异步编程与线程同步,condition variable、future、atomic

正文内容

C++ 标准库 <condition_variable>

在多线程编程中,线程间的同步是一个非常重要的问题。

C++11 标准引入了 <condition_variable> 头文件,它提供了一种机制,允许线程在某些条件不满足时挂起,直到其他线程通知它们条件已经满足。

condition_variable是用于线程间同步的一种高级工具,它比使用低级同步原语(如互斥锁和条件变量)更加安全和方便。

condition_variable是一个类模板,用于在多线程环境中实现线程间的同步。它允许一个或多个线程等待某个条件变为真,而其他线程可以唤醒这些等待的线程。

语法

以下是condition_variable的基本语法:

#include <condition_variable>

void notify_one() {
    // 唤醒一个等待的线程
}

void notify_all() {
    // 唤醒所有等待的线程
}

template <class Mutex>
class condition_variable {
public:
    condition_variable();
    ~condition_variable();

    void wait(unique_lock<mutex>& lock);
    void wait_for(unique_lock<mutex>& lock, chrono::duration<Rep, Period> const& rel_time);
    void wait_until(unique_lock<mutex>& lock, chrono::time_point<Clock, Duration> const& abs_time);

    void notify_one() noexcept;
    void notify_all() noexcept;
};

实例

下面是一个使用condition_variable的简单示例,展示了生产者-消费者问题的基本实现。

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

std::mutex mtx;
std::condition_variable cv;
std::queue<int> product;

void producer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lck(mtx);
        product.push(id * 100 + i);
        std::cout << "Producer " << id << " produced " << product.back() << std::endl;
        cv.notify_one();
        lck.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lck(mtx);
        cv.wait(lck, []{ return !product.empty(); });
        if (!product.empty()) {
            int prod = product.front();
            product.pop();
            std::cout << "Consumer " << id << " consumed " << prod << std::endl;
        }
        lck.unlock();
    }
}

int main() {
    std::thread producers[2];
    std::thread consumers[2];

    for (int i = 0; i < 2; ++i) {
        producers[i] = std::thread(producer, i + 1);
    }

    for (int i = 0; i < 2; ++i) {
        consumers[i] = std::thread(consumer, i + 1);
    }

    for (int i = 0; i < 2; ++i) {
        producers[i].join();
        consumers[i].join();
    }

    return 0;
}

这个程序的输出可能会有所不同,因为它依赖于线程调度。但是,你会看到生产者生产产品,然后消费者消费这些产品。输出将类似于:

Producer 1 produced 100
Producer 2 produced 200
Consumer 1 consumed 100
Producer 1 produced 101
Consumer 2 consumed 200
Producer 2 produced 201
Consumer 1 consumed 101
...

注意事项

  • 使用condition_variable时,必须确保在等待之前获取互斥锁,并且在唤醒后释放互斥锁。
  • wait、wait_for和wait_until函数都会释放互斥锁,然后在等待期间重新获取它。
  • notify_one唤醒一个等待的线程,而notify_all唤醒所有等待的线程。


C++ 标准库 <future>

C11 引入了 <future> 头文件,它提供了一种异步编程的机制,允许程序在等待某个操作完成时继续执行其他任务。<future> 库是 C 标准库中并发编程的一部分,它允许程序员以一种更简洁和安全的方式处理异步操作。

<future> 库中定义了几个关键的类型:

  • std::future:表示异步操作的结果,可以查询操作的状态,获取结果或等待操作完成。
  • std::promise:用于与 std::future 配对,用于设置异步操作的结果。
  • std::packaged_task:封装一个函数或可调用对象,使其可以作为异步任务执行。

std::promise

std::promise 用于设置异步操作的结果。它与 std::future 配对使用。

#include <iostream>
#include <future>

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    // 在另一个线程中设置结果
    std::thread t([prom]() {
        prom.set_value(10);
    });

    // 等待结果
    std::cout << "Future value: " << fut.get() << std::endl;

    t.join();
    return 0;
}

输出结果:

Future value: 10

std::packaged_task

std::packaged_task 封装一个函数或可调用对象,使其可以作为异步任务执行。

#include <iostream>
#include <future>
#include <cmath>

int compute_square_root(double x) {
    return std::sqrt(x);
}

int main() {
    std::packaged_task<double(double)> task(compute_square_root);
    std::future<double> result = task.get_future();
    std::thread th(std::move(task), 9.0);

    std::cout << "Result: " << result.get() << std::endl;
    th.join();
    return 0;
}

输出结果:

Result: 3

std::async

std::async 是一个方便的函数,用于启动异步任务。它可以立即返回一个 std::future 对象。

#include <iostream>
#include <future>

int main() {
    std::future<int> fut = std::async(std::launch::async, [](int x) {
        return x * x;
    }, 5);

    std::cout << "Result: " << fut.get() << std::endl;
    return 0;
}

输出结果:

Result: 25

异常处理

当异步操作抛出异常时,std::future 会捕获这个异常,并且可以通过调用 .get() 方法来重新抛出它。

#include <iostream>
#include <future>

void throw_exception() {
    throw std::runtime_error("Exception thrown");
}

int main() {
    std::future<void> fut = std::async(throw_exception);

    try {
        fut.get();
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

输出结果:

Caught exception: Exception thrown

<future> 库为 C++ 程序员提供了一种简单而强大的异步编程方式。通过使用 std::promise、std::packaged_task 和 std::async,我们可以轻松地在 C++ 程序中实现并发和异步操作。同时,异常处理机制也确保了程序的健壮性。



C++ 标准库 <atomic>

在多线程编程中,数据的同步和线程安全是一个重要的问题。

C++11 标准引入了 <atomic> 库,它提供了一组原子操作,用于保证在多线程环境下对单个数据的访问是原子的,即不可分割的。这可以避免数据竞争和保证线程安全。

原子操作是指在执行过程中不会被其他线程中断的操作。

<atomic>库中的原子类型提供了这样的操作,它们可以保证在多线程环境中对共享数据的访问是安全的。

语法

<atomic>库提供了多种原子类型,包括

  • atomic<bool>
  • atomic<char>
  • atomic<short>
  • atomic<int>
  • atomic<long>
  • atomic<long long>
  • atomic<wchar_t>
  • atomic<char16_t>
  • atomic<char32_t>
  • atomic<unsigned char>
  • atomic<unsigned short>
  • atomic<unsigned>
  • atomic<unsigned long>
  • atomic<unsigned long long>
  • atomic<float>
  • atomic<double>
  • atomic<long double>

基本操作

  • load(): 安全地读取原子变量的值。
  • store(value): 安全地将值写入原子变量。
  • exchange(value): 将原子变量的值替换为value,并返回原子变量的旧值。
  • compare_exchange_weak(expected, desired): 如果原子变量的当前值等于expected,则将其设置为desired,并返回true。否则,将expected设置为原子变量的当前值,并返回false。
  • compare_exchange_strong(expected, desired): 与compare_exchange_weak类似,但循环直到成功。

实例

下面是一个使用<atomic>库的简单示例,演示了如何在多线程环境中安全地更新一个共享计数器。

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0); // 初始化原子计数器

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

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

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

    std::cout << "Final counter value: " << counter << std::endl; // 输出最终的计数器值

    return 0;
}

上述程序,你将看到输出类似于:

Final counter value: 20000

这个输出表明两个线程成功地在没有数据竞争的情况下,各自增加了10000次计数器的值。

注意事项

  • 使用<atomic>库时,需要确保所有对共享数据的访问都是通过原子操作进行的,以避免数据竞争。
  • 不同的原子操作有不同的内存顺序要求,std::memory_order_relaxed是最低的内存顺序要求,但可能不保证操作的可见性。根据需要选择合适的内存顺序。
  • 原子操作的性能开销通常比非原子操作要高,因此在单线程环境中,使用普通变量可能更高效。

通过使用<atomic>库,C++程序员可以更容易地编写线程安全的代码,同时保持高性能。

正文内容已启用复制保护,代码块与行内代码仍支持复制。

评论区

欢迎参与讨论,评论提交与展示状态会在下方同步更新。

当前站点暂未开放评论提交功能。

读者留言

还没有评论,来发表第一条评论吧。

共 0 条评论 已展示 0 / 0 条评论
当前还没有评论,来发表第一条评论吧。若评论需要审核,通过后会展示在这里。