最近在用c++搞项目,因为多线程要做一个类似cnt的保护,今天学习了c++的原子操作。
std::atomic 类型是 C++ 提供的一种机制,用于实现多线程之间的安全共享数据。它通过原子操作来确保对共享变量的操作是不可分割的。在多线程环境下,如果没有适当的同步机制,对共享变量的读写可能会导致竞争条件,进而引发不确定的行为。std::atomic 类型提供了一种解决方案,让我们能够以线程安全的方式访问这些变量。
关于具体的函数和详细介绍可以访问这里:https://cplusplus.com/reference/atomic/atomic/?kw=atomic
这里介绍几个常用的:
这里原子操作后为什么要返回之前的值呢?
以fetch_add为例,fetch_add是用于对原子变量进行原子性地增加操作。它执行一个原子的加法操作,并返回加法操作之前的原子变量的值。
这种设计是基于并发编程中的常见需求。返回之前的值允许程序员在执行加法操作后,获取加法之前的原始值。这样做有以下几个方面的优点:
这里做一个简单的线程池,并实现一个task,task的任务就是对原子变量counter进行递增,最后我们看结果是否与预期一致,这里线程池实现10个线程,给线程池推送100000个task。
#include <IOStream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <atomic>
class ThreadPool {
public:
ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; ++i) {
threads.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wAIt(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template <class F>
void AddTask(F&& f) {
{
std::lock_guard<std::mutex> lock(queueMutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
~ThreadPool() {
{
std::lock_guard<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& worker : threads) {
worker.join();
}
}
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop;
};
int main() {
std::atomic<int> counter(0);
ThreadPool pool(10);
constexpr int numTasks = 100000;
for (int i = 0; i < numTasks; ++i) {
pool.AddTask([&counter]() {
counter++;
});
}
std::cout << "Waiting for tasks to complete..." << std::endl;
//注意:这里不会确保所有任务已经执行完毕,仅仅是等待一段时间以展示结果
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "Final Counter Value: " << counter << std::endl;
return 0;
}
我们预期最后的结果是100000。g++编译,不要忘记加-lpthread,执行:
细心的小伙伴可能发现我的代码直接使用的counter++,这里需要注意,这只是个简单的测试代码,实际项目中要最好使用counter.fetch_add(1),因为counter++不保证++是个原子操作。我在项目中遇到了该问题,最后加出来总会比预期值少,后来换成fetch_add后就正常了。