[英]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内在函数,例如
InterlockedIncrement
, InterlockedExchange
。 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.