正确使用compare_exchange_weak

时间:2015-11-12 05:32:23

标签: multithreading c++11 atomic clang++ lock-free

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

随机分配或释放内存

1 个答案:

答案 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()将指向第二个元素,它不再属于列表。)