我遇到了需要内存中两个值的原子总和的情况。我继承的代码是这样的:
int a = *MemoryLocationOne;
memory_fence();
int b = *MemoryLocationTwo;
return (a + b) == 0;
a和b的单独读取是原子的,并且代码中其他位置写入这两个内存位置的所有内容也是无锁原子的。然而问题是两个位置的值可以并且确实在两次读取之间发生变化。
那么如何使这个操作成为原子?我知道关于CAS的所有内容,但它往往只涉及使读取 - 修改 - 写入操作成为原子,这不是我想要做的。
有没有办法,或者是重构代码的最佳选择,这样我只需要检查一个值?
编辑:谢谢,我没有提到我想在第一次修订中无锁地执行此操作,但有些人在我的第二次修订后接受了它。我知道当他们说这样的话时没有人相信人,但我实际上不能使用锁。我必须用原子来模拟一个互斥体,这比重构代码以跟踪一个值而不是两个值更有用。
现在我的调查方法涉及利用这些值是连续的这一事实,并使用64位读取原子地抓取它们,我确信这些目标平台上是原子的。如果有人有新想法,请捐款!感谢。
答案 0 :(得分:3)
您必须确保在读取或写入两个值中的任何一个时,它们都被内存屏障(锁定或关键部分)包围。
// all reads...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
a = *MemoryLocationOne;
b = *MemoryLocationTwo;
}
...
// all writes...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
*MemoryLocationOne = someValue;
*MemoryLocationTwo = someOtherValue;
}
答案 1 :(得分:3)
如果您确实需要确保在进行此测试时a
和b
没有更改,那么您需要对所有访问使用相同的同步到a
和b
。那是你唯一的选择。每次读取和每次写入这些值都需要使用相同的内存栅栏,同步器,信号量,时间片锁或任何使用的机制。
有了这个,你可以确保如果:
memory_fence_start();
int a = *MemoryLocationOne;
int b = *MemoryLocationTwo;
int test = (a + b) == 0;
memory_fence_stop();
return test;
然后a
在您阅读b
时不会改变。但同样,您必须使用相同的同步机制来所有访问a
和b
。
要反映稍后对您的问题的修改,您正在寻找无锁方法,那么它完全取决于您使用的处理器以及a
和b
的长度和时间。关于这些内存位置是否连续并正确对齐。
假设它们在内存中是连续的并且每个都是32位并且您的处理器具有原子64位读取,那么您可以发出原子64位读取来读取这两个值,从64中解析出两个值比特值,做数学并返回你想要返回的内容。假设您永远不需要同时对“a
和b
进行原子更新”,而只是单独对“a
”或“b
”进行原子更新,那么如果没有锁,这将做你想做的事。
答案 2 :(得分:3)
如果您的目标是x86,则可以使用64位比较/交换支持,并将两个int打包成一个64位字。
在Windows上,您可以这样做:
// Skipping ensuring padding.
union Data
{
struct members
{
int a;
int b;
};
LONGLONG _64bitData;
};
Data* data;
Data captured;
do
{
captured = *data;
int result = captured.members.a + captured.members.b;
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData,
captured._64BitData,
captured._64bitData) != captured._64BitData);
真的很难看。我建议使用锁 - 更易于维护。
编辑: 要更新和阅读各个部分:
data->members.a = 0;
fence();
data->members.b = 0;
fence();
int captured = data->members.a;
int captured = data->members.b;
答案 3 :(得分:1)
没有锁定就没有办法做到这一点。据我所知,没有处理器具有双原子读数。