在测试程序的可伸缩性时,我遇到了必须将memcpy操作作为原子操作的情况。我必须将64字节的数据从一个位置复制到另一个位置。
我遇到了一个解决方案,即使用旋转变量是:
struct record{
volatile int startFlag;
char data[64];
volatile int doneFlag;
};
和伪代码如下
struct record *node;
if ( node->startFlag ==0 ) { // testing the flag
if( CompareAndSwap(node->startFlag , 0 ,1 ) ) { // all thread tries to set, only one will get success and perform memcpy operation
memcpy(destination,source,NoOfBytes);
node->doneFlag = 1; // spinning variable for other thread, those failed in CompAndSwap
}
else {
while ( node->doneFlag==0 ) { // other thread spinning
; // spin around and/or use back-off policy
}
}}
这可以作为原子memcpy执行吗?虽然如果执行memcpy的线程被抢占(在memcpy之前或之后但在设置doneFlag之前),那么其他人将继续旋转。或者可以做些什么才能使这个原子化。
情况就像其他线程必须等待,除非数据被复制,因为它们必须与插入的数据进行比较,并使用自己的数据。
我在startFlag的情况下使用test-and-test-and-set方法来减少一些昂贵的原子操作。
自旋锁也是可扩展的,但我已经测量过原子调用比自旋锁具有更好的性能,而且我正在寻找这个代码片段中可能出现的问题。
因为我使用自己的内存管理器,所以内存分配和免费调用对我来说代价很高,所以使用另一个缓冲区并在其中复制内容,然后设置指针(因为指针大小在原子操作下)是昂贵的,因为它会需要很多mem-alloc和mem-free调用。
编辑我没有使用互斥锁,因为它们似乎不是可扩展而且这只是程序的一部分,因此关键部分不是很小(据我所知,对于较大的临界区,很难使用原子操作)。
答案 0 :(得分:5)
您的代码段肯定会被破坏。在node-> startFlag
上有一场比赛不幸的是,没有原子方法可以复制64个字节。我想你在这里有很多选择。
希望它有所帮助。 亚历克斯。
答案 1 :(得分:2)
使用同步机制。互斥体似乎是合理的。
如果您担心可扩展性,请尝试使用显示器。
答案 2 :(得分:2)
现在已经很晚了,但是对于其他人来说这个问题,以下内容更容易,缓存更少。
注意:我将CAS更改为GCC中相应的原子内置。不需要“易失性”,CAS引入了内存屏障。
// Simpler structure
struct record {
int spin = 0;
char data[64];
};
struct record *node;
while (node->spin || ! __sync_bool_compare_and_swap(&node->spin , 0 , 1)); // spin
memcpy(destination,source,NoOfBytes);
node->spin = 0;
PS:我不确定CAS而不是node-> spin = 0是否可以提高效率。
答案 3 :(得分:1)
不要使用锁,请使用 CriticalSection 。锁是重量级的,CriticalSections非常,非常快速(根据平台只有几个指令)。您没有指定操作系统,我在这里发布的信息在Windows中有经验,但其他操作系统应该类似。
如果CriticalSections包含大量代码,那么您是否担心CriticalSections可能可扩展 根本原因(可能是你读到的地方的论点)是,如果线程长时间保持在CS上,则CriticalSection不能在多个线程中交错。你可以通过仅将CS包装在真正需要原子的那部分代码中来避免这种情况。另一方面:如果您使用CS 太细粒度,百分比开销当然会增加。通过任何同步都无法避免这种权衡。
你说你需要的原子操作是64字节的副本:在这种情况下,CS的同步开销将可忽略不计。就试一试吧。使用您同步的粒度(围绕64个字节的单个副本或大约4个这样的副本),您可以通过进行一些实验来平衡线程交错粒度与百分比开销。但总的来说:CS足够快且足够可扩展。