在测试程序的可扩展性时,我遇到了必须将 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 之前或之后但在设置 didFlag 之前),则其他线程将继续旋转。或者可以做什么来使这个原子化。
情况就像其他线程必须等待,除非数据被复制,因为它们必须与插入的数据和自己的数据进行比较。
我在 startFlag 的情况下使用测试和测试和设置方法来减少一些昂贵的原子操作。
自旋锁也是可扩展的,但我已经测量到原子调用比自旋锁具有更好的性能,而且我正在寻找此代码片段中可能出现的问题。
由于我使用自己的内存管理器,因此内存分配和免费调用对我来说成本高昂,因此使用另一个缓冲区并复制其中的内容,然后设置指针(因为指针大小处于原子操作下)成本高昂,因为它会需要许多 mem-alloc 和 mem-free 调用。
编辑我没有使用互斥锁,因为它们似乎不可可扩展而且这只是程序的一部分,所以关键部分不是这么小(我知道对于较大的关键部分很难使用原子操作)。
最佳答案
您的代码片段肯定已损坏。 node->startFlag 上有一场竞赛
不幸的是,没有原子方法来复制 64 字节。我认为您在这里有很多选择。
- 以原子方式访问node->startFlag。我已经就这个主题写了几篇文章:here和 here .
- 使用用户模式自旋锁保护整个事物。 Here's a post on the subject
- 使用类似 RCU 的方法。您可以阅读有关 RCU here 的信息。简而言之,这个想法是使用指针引用要复制的缓冲区。然后你就可以:
- 分配新缓冲区。
- 创建其内容(从您的源中进行 memcpy)。
- 自动用新缓冲区替换缓冲区。
- 等待所有访问旧缓冲区的线程到期并释放它。
希望有帮助。 亚历克斯。
关于performance - 原子 memcpy 建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6704252/