std::atomic与自定义类型结合需满足平凡可复制且大小适中,否则会退化为有锁实现;应检查is_lock_free()确认无锁性能,若不满足则推荐使用std::mutex或std::atomic<std::shared_ptr<T>>等替代方案。

std::atomic
std::atomic
std::mutex
将
std::atomic
简单来说,如果你的自定义类型是一个“平凡可复制”的结构体(或类),且没有用户定义的构造函数、析构函数、拷贝/移动构造函数或赋值运算符,并且它的所有非静态数据成员也都是平凡可复制的,那么你就有机会直接使用
std::atomic<YourCustomType>
#include <atomic>
#include <iostream>
#include <string> // 注意:std::string不是平凡可复制的
// 示例1:一个平凡可复制的自定义类型
struct Point {
int x;
int y;
// 默认构造函数、拷贝构造函数、赋值运算符、析构函数都由编译器生成,且是平凡的
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// 示例2:一个非平凡可复制的自定义类型 (因为它有std::string成员)
struct UserData {
int id;
std::string name; // std::string不是平凡可复制的
// 如果这里手动定义了任何构造函数、析构函数、拷贝/移动操作,也会使其非平凡
// UserData() = default;
// ~UserData() = default;
};
int main() {
// 对于Point,可以直接使用std::atomic
std::atomic<Point> current_point;
Point initial_point = {10, 20};
current_point.store(initial_point);
Point new_point = {30, 40};
Point expected_point = initial_point;
// 原子地比较并交换整个Point对象
if (current_point.compare_exchange_strong(expected_point, new_point)) {
std::cout << "Successfully updated point to {" << current_point.load().x << ", " << current_point.load().y << "}\n";
} else {
std::cout << "Failed to update point, current value is {" << current_point.load().x << ", " << current_point.load().y << "}\n";
}
// 检查是否是无锁的,这很重要
if (current_point.is_lock_free()) {
std::cout << "std::atomic<Point> is lock-free.\n";
} else {
std::cout << "std::atomic<Point> is NOT lock-free (likely uses a mutex internally).\n";
}
// 对于UserData,直接使用std::atomic<UserData>通常是不可行的,或者会退化为有锁
// std::atomic<UserData> current_user_data; // 可能会编译失败或不是lock-free
// 我个人建议,对于UserData这种类型,直接使用互斥锁或者std::atomic<std::shared_ptr<UserData>>是更好的选择。
return 0;
}代码中
current_point.is_lock_free()
false
std::atomic
std::mutex
立即学习“C++免费学习笔记(深入)”;
std::atomic
要让
std::atomic
首先,也是最关键的,你的自定义类型必须是平凡可复制(Trivially Copyable)的。这意味着:
说白了,就是你的类型必须足够“原始”,编译器可以像处理
int
char
memcpy
std::string
std::vector
std::unique_ptr
std::shared_ptr
std::atomic
其次,类型的大小也是一个重要考量。底层硬件通常只能原子地操作特定大小的数据块,比如一个机器字(通常是4字节或8字节)。如果你的自定义类型大小恰好是这些硬件支持的原子操作尺寸(例如8字节、16字节),那么它更有可能实现无锁。如果类型过大,即使是平凡可复制的,
std::atomic
最后,也是最直接的验证方式,就是始终检查std::atomic<T>::is_lock_free()
std::atomic
false
std::atomic
std::mutex
is_lock_free()
false
std::atomic
std::atomic
当你的自定义类型不满足
std::atomic
std::string
std::atomic
使用std::mutex
std::mutex
std::lock_guard
std::unique_lock
#include <mutex>
#include <string>
#include <iostream>
struct ComplexData {
int id;
std::string name;
// 构造函数、析构函数、拷贝/移动操作等...
ComplexData(int i, const std::string& n) : id(i), name(n) {}
};
class ThreadSafeComplexData {
public:
// 默认构造函数
ThreadSafeComplexData() : data_(0, "Default") {}
// 带参数构造函数
ThreadSafeComplexData(int id, const std::string& name) : data_(id, name) {}
void update(int new_id, const std::string& new_name) {
std::lock_guard<std::mutex> lock(mtx_);
data_.id = new_id;
data_.name = new_name;
}
ComplexData get() const {
std::lock_guard<std::mutex> lock(mtx_);
return data_; // 返回一份拷贝
}
private:
mutable std::mutex mtx_; // mutable 允许在 const 成员函数中锁定
ComplexData data_;
};
// 使用示例
// ThreadSafeComplexData my_data(1, "Initial");
// my_data.update(2, "Updated Name");
// ComplexData current = my_data.get();
// std::cout << current.id << " " << current.name << std::endl;对于大多数应用场景,这种“粗粒度”的锁足以满足需求,并且比尝试使用复杂的无锁技巧更不容易出错。
使用std::atomic<std::shared_ptr<T>>
T
T
std::shared_ptr
#include <atomic>
#include <memory> // For std::shared_ptr
#include <string>
#include <iostream>
struct ImmutableComplexData {
int id;
std::string name;
// 构造函数,一旦创建,数据就不再修改
ImmutableComplexData(int i, const std::string& n) : id(i), name(n) {}
// 禁止修改操作
// void update_id(int new_id) { id = new_id; } // 不允许
};
std::atomic<std::shared_ptr<ImmutableComplexData>> current_immutable_data;
void writer_thread() {
// 首次初始化
current_immutable_data.store(std::make_shared<ImmutableComplexData>(1, "Initial"));
// 更新数据:创建新实例,然后原子交换指针
auto new_data = std::make_shared<ImmutableComplexData>(2, "Updated Name");
current_immutable_data.store(new_data); // 原子地更新指针
}
void reader_thread() {
// 原子地加载指针,然后安全地访问数据
std::shared_ptr<ImmutableComplexData> data_snapshot = current_immutable_data.load();
if (data_snapshot) {
std::cout << "Reader: ID=" << data_snapshot->id << ", Name=" << data_snapshot->name << std::endl;
}
}
// main函数中可以启动这两个线程这种模式的优点是读取操作几乎是无锁的(只需要原子加载指针),非常高效。缺点是每次写入都需要创建新的对象,可能会有内存分配和垃圾回收的开销,并且需要确保你的自定义类型确实是不可变的。
使用专门的并发数据结构: 对于某些特定场景(如队列、哈希表),如果标准库的
std::atomic
boost::lockfree
folly
选择哪种方案取决于你的具体需求:数据结构的复杂性、读写频率、性能要求以及你对并发编程的熟悉程度。对于大多数情况,
std::mutex
std::atomic
即便你的自定义类型满足了
std::atomic
is_lock_free()
假共享(False Sharing): 这是一个隐蔽的性能杀手。即使你的
std::atomic<T>
std::atomic
alignas
std::atomic
alignas(64) std::atomic<Point> current_point;
is_lock_free()
is_lock_free()
std::atomic
is_lock_free()
false
std::atomic
std::mutex
std::atomic
std::mutex
复杂操作的非原子性:
std::atomic<T>
T
以上就是C++如何使用std::atomic与自定义类型结合的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号