正文内容
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++程序员可以更容易地编写线程安全的代码,同时保持高性能。
正文内容已启用复制保护,代码块与行内代码仍支持复制。
读者留言
还没有评论,来发表第一条评论吧。