7

我想知道,只能在 C++11 中声明原始数据类型 std::atomic 吗?例如,是否可以将库类对象声明为“原子地”变异或访问?

例如,我可能有

using namespace std::chrono;
time_point<high_resolution_clock> foo;

// setter method
void set_foo() {
  foo = high_resolution_clock::now();
}

// getter method
time_point<high_resolution_clock> get_foo() {
  return foo;
}

但是,如果在不同的线程中调用这些 setter 和 getter 方法,我认为这可能会导致未定义的行为。如果我可以声明 foo 之类的东西会很好:

std::atomic<time_point<high_resolution_clock>> foo;

...因此对 foo 的所有操作都将以原子方式进行。在我的项目的应用程序中,可能在几十个类中声明了数百个这样的 foo 变量,我觉得可以说让对象变异和访问“原子”会更方便,而不必声明和 lock_guard到处都是互斥锁。

这是不可能的,还是有更好的方法,或者我真的必须在任何地方使用互斥锁和 lock_guard 吗?

更新

  • 有接盘侠吗?我一直在网上寻找体面的信息,但是使用原子的例子太少了,我不能确定它可以应用到什么程度。
4

2 回答 2

3

atomic<>不限于原始类型。允许与可简单复制atomic<>的类型一起使用。从 c++11 标准的第29.5 节 Atomic types(它也在 中声明):Tstd::atomic

有一个通用类模板原子。模板参数 T 的类型应该是可简单复制的(3.9)。

如果无法使用需要原子访问的atomic<>对象,则定义新对象,包含原始对象和std::mutex. 这意味着lock_guard<>仅在新线程安全对象的 getter 和 setter 中使用,而不是在整个代码中乱扔垃圾。Atemplate可能能够定义所需的线程安全机制:

template <typename T>
class mutable_object
{
public:
    mutable_object() : t_() {}
    explicit mutable_object(T a_t) : t_(std::move(a_t)) {}
    T get() const
    {
        std::lock_guard<std::mutex> lk(mtx_);
        return t_;
    }
    void set(T const& a_t)
    {
        std::lock_guard<std::mutex> lk(mtx_);
        t_ = a_t;
    }
private:
    T t_;
    mutable std::mutex mtx_;
};

using mutable_high_resolution_clock =
        mutable_object<std::chrono::time_point<
            std::chrono::high_resolution_clock>>;

using mutable_string = mutable_object<std::string>;

mutable_high_resolution_clock c;
c.set(std::chrono::high_resolution_clock::now());
auto c1 = c.get();

mutable_string s;
s.set(std::string("hello"));
auto s1 = s.get();
于 2013-05-24T19:26:41.550 回答
1

Atomics 仅限于可简单复制的类(即没有自定义复制构造函数且其成员也可简单复制的类)。

这个要求对原子有很大的好处:

  • 没有原子操作可以抛出,因为构造函数抛出
  • 所有原子都可以用锁(自旋锁或互斥锁)和 memcpy 来建模以复制数据。
  • 所有原子都有有限的运行时间(有界)。

后者特别有用,因为有时使用自旋锁来实现原子,并且非常希望在持有自旋锁的同时避免无界任务。如果允许任何构造函数,则实现往往需要依靠成熟的互斥锁,对于非常小的关键部分来说,这比自旋锁要慢。

于 2013-09-03T06:13:58.203 回答