简体   繁体   English

如何编写安全的原子对象包装器?

[英]How is it possible to write a safe atomic object wrapper?

I've been attempting to write a wrapper class to wrap Win32 intrinsic function such as InterlockedIncrement , InterlockedExchange . 我一直在尝试编写包装器类来包装Win32内在函数,例如InterlockedIncrementInterlockedExchange Although my problem is probably analogous on other platforms which support similar intrinsics. 尽管我的问题可能与其他支持相似内在函数的平台相似。

I have a basic template type: 我有一个基本的模板类型:

template <typename T, size_t W = sizeof(T)>
class Interlocked {};

Which is partially specialized for datatypes of different size. 它部分地专用于不同大小的数据类型。 For example, here's the 32 bit one: 例如,这是32位元之一:

//
// Partial specialization for 32 bit types
//
template<typename T>
class Interlocked <T, sizeof(__int32)>
{
public:

    Interlocked<T, sizeof(__int32)>() {};

    Interlocked<T, sizeof(__int32)>(T val) : m_val(val) {}

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator= (T val)
    {
        InterlockedExchange((LONG volatile *)&m_val, (LONG)val);
        return *this;
    }

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator++()
    {
        return static_cast<T>(InterlockedIncrement((LONG volatile *)&m_val));
    }

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator--()
    {
        return static_cast<T>(InterlockedDecrement((LONG volatile *)&m_val));
    }

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator+(T val)
    {
        InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
        return *this;
    }

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator-(T val)
    {
        InterlockedExchangeSubtract((LONG volatile *)&m_val, (LONG) val);
        return *this;
    }

    operator T()
    {
        return m_val;
    }

private:

    T m_val;
};

However, I'm coming to the conclusion that I don't know how to safely write such an object. 但是,我得出的结论是,我不知道如何安全地编写这样的对象。 Specifically, I've realised that returning *this after performing an interlocked operation allows the possibility for another thread to alter the variable before it's returned. 具体来说,我已经意识到执行互锁操作后返回*this可以让另一个线程在返回变量之前更改该变量。 This nullifies the point of the type. 这会使类型的点无效。 Is it possible to write such a thing? 可以写这样的东西吗? Presumably std::atomic solves this problem but I don't have access to that in my compiler... 大概std :: atomic解决了这个问题,但我无法在编译器中访问它...

如果您没有std::atomic ,则可以使用boost::atomic (出现在最新的Boost 1.53中 ),它已经过跨平台实现的良好测试。

Operators + and - are meaningless. 运算符+-没有意义。 What you've actually implemented looks more like compound assignment ( += , -= ) but you need to return a value of type T and not a reference to (*this) . 您实际实现的内容更像是复合赋值( +=-= ),但是您需要返回类型T的值,而不是对(*this)的引用。 Of course this isn't following the conventions for assignment operators... std::atomic chooses to use named functions and not operator overloads for everything except ++ and -- , probably for that reason. 当然,这并不遵循赋值运算符的约定... std::atomic选择使用命名函数,而不是除++--以外的所有内容都使用运算符重载,可能是出于这个原因。

You have a data race in your code 您的代码中存在数据争用

You can simultaneously write to a variable (using InterlockedBlah(...)) and read from it using operator T. 您可以同时写入变量(使用InterlockedBlah(...))并使用运算符T从中读取。

The memory model for C++11 states that this isn't allowed. C ++ 11的内存模型指出这是不允许的。 You could possibly rely on the hardware specification for your platform which may state that 4 byte (aligned!) reads don't tear but this is brittle at best. 您可能会依赖于平台的硬件规格,该规格可能会指出4字节(对齐!)的读取不会撕裂,但这充其量是脆弱的。 And undefined behavoir is undefined. 未定义的行为是未定义的。

Also, the read doesn't have any memory barriers [which tell both the compiler and the hardware] not to reorder instructions. 而且,读取没有任何内存障碍[告诉编译器和硬件]不要重新排序指令。

Making the read an return InterlockedAdd(&val, 0) operation would probably solve all these, as the Interlocked APIs on windows are guaranteed to add the right memory barriers. 使读取返回InterlockedAdd(&val,0)操作可能会解决所有这些问题,因为Windows上的Interlocked API可以确保添加正确的内存屏障。 However, beware of Interlocked* APIs on other MS platforms which don't have this guarantee. 但是,请注意没有此保证的其他MS平台上的Interlocked * API。

Basically what you're trying to do is probably possible but really hard, and definitely relies on what the software and hardware guarantees on each platform - it isn't possible to write this in a portable way. 基本上,您要尝试执行的操作可能是可行的,但确实很难,并且绝对依赖于每个平台上的软件和硬件保证-不可能以可移植的方式编写此代码。

Use std::atomic, use boost::atomic 使用std :: atomic,使用boost :: atomic

Aside from the very good advice of "use someone else's already tested and working implementation" from nogard, I would suggest that you don't want to return *this , but the result of the operation - this is how the existing interlocked operators work (and how std::atomic works). 除了nogard的“使用别人已经过测试并正在执行的实现”这非常好的建议之外,我建议您不要返回*this ,而是操作的结果-这是现有的互锁操作符的工作方式(以及std :: atomic的工作原理)。

So in other words, your operator code should look like this: 因此,换句话说,您的操作员代码应如下所示:

T Interlocked<T, sizeof(__int32)>::operator+(T val)
{
    return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
}

There is a problem, as Ben Voigt poinst out that that this function modifies the input value, which means that: 有一个问题,因为Ben Voigt指出该函数会修改输入值,这意味着:

a = b + c;

would actually do: 实际上会做:

b += c; 
a = b;

Consider two threads performing concurrent additions on your atomic number class, where Thread #n adds an amount t_n to your number x . 考虑两个线程在您的原子数类上执行并发加法,其中线程#n将数量t_n添加到您的数x

You are worried that between performing the addition and returning the result in one thread, a second thread might perform an addition, thus messing up the return value for the first thread. 您担心在执行加法和在一个线程中返回结果之间,第二个线程可能会执行加法,从而使第一个线程的返回值混乱。

The observed behavior for a user of the class is then that the return value is (x + t_1 + t_2) instead of the expected (x + t_1) . 对于该类用户,观察到的行为是返回值是(x + t_1 + t_2)而不是预期的(x + t_1)

Now let us assume you had an implementation that would not allow that behavior, ie the result is guaranteed to be (x_1 + t_1) , where x_1 is the value of the number immediately before Thread #1 performs its addition. 现在,让我们假设您有一个不允许该行为的实现,即,保证结果为(x_1 + t_1) ,其中x_1是在线程#1执行加法之前的数字值。

If Thread #2 performs its concurrent addition immediately before Thread #1 the value you get is: 如果线程2在线程1之前立即执行其并发加法,则您获得的值为:

(x_1 + t_1) = ((x + t_2) + t_1)

Which is the exact same race . 这是完全相同的种族 Unless you introduce some additional synchronization or a check for the expected value of the number before applying the addition, you will always get this race. 除非您进行一些其他同步或在应用加法之前检查数字的期望值,否则您将始终处于竞争状态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM