const int SIZE = 20;
struct Node { Node* next; };
std::atomic<Node*> head (nullptr);
void push (void* p)
{
Node* n = (Node*) p;
n->next = head.load ();
while (!head.compare_exchange_weak (n->next, n));
}
void* pop ()
{
Node* n = head.load ();
while (n &&
!head.compare_exchange_weak (n, n->next));
return n ? n : malloc (SIZE);
}
void thread_fn()
{
std::array<char*, 1000> pointers;
for (int i = 0; i < 1000; i++) pointers[i] = nullptr;
for (int i = 0; i < 10000000; i++)
{
int r = random() % 1000;
if (pointers[r] != nullptr) // allocated earlier
{
push (pointers[r]);
pointers[r] = nullptr;
}
else
{
pointers[r] = (char*) pop (); // allocate
// stamp the memory
for (int i = 0; i < SIZE; i++)
pointers[r][i] = 0xEF;
}
}
}
int main(int argc, char *argv[])
{
int N = 8;
std::vector<std::thread*> threads;
threads.reserve (N);
for (int i = 0; i < N; i++)
threads.push_back (new std::thread (thread_fn));
for (int i = 0; i < N; i++)
threads[i]->join();
}
compare_exchange_weak的这种用法有什么问题?上面的代码使用clang ++(MacOSX)崩溃了5次。
崩溃时的head.load()将具有“0xEFEFEFEFEF”。 pop
就像malloc一样,push
就像是免费的。每个线程(8个线程)从head
答案 0 :(得分:1)
它可能是很好的无锁分配器,但ABA-problem出现了:
A :假设某些thread1
执行pop()
,它将head
的当前值读入n
变量,但在此之后立即执行线程已被预先发布,并发 thread2
执行完整的pop()
调用,即从head
读取相同的值并执行 successfull { {1}}。
B :现在compare_exchange_weak
中由n
引用的对象不再属于列表,可以由thread1
进行修改。所以thread2
通常是垃圾:从中读取可以返回任何值。例如,它可以是n->next
,其中前5个字节是戳(0xEFEFEFEFEF
),由EF
写入,最后3个字节仍为thread2
,来自 nullptr 。 (总值以 little-endian 方式在数字上解释)。看来,由于0
值已更改,head
将无法通过thread1
调用,但是......
A :并发compare_exchange_weak
thread2
es指针返回列表。因此push()
看到thread1
的初始值,并执行成功head
,将不正确的值写入compare_exchange_weak
。列表已损坏。
注意,问题不仅仅是可能性,其他线程可以修改head
的内容。问题是n->next
的值不再与列表相关联。因此,即使它没有被同时修改,它也会变得无效(对于替换n->next
),例如,当列表中的其他线程head
2个元素但pop()
仅返回时第一个。 (所以push()
将指向第二个元素,它不再属于列表。)