同时附加到没有互斥锁的列表尾部

时间:2014-07-03 20:43:03

标签: c++ multithreading linked-list atomic

我有一个单链表,可以被多个线程同时访问。

实际上同时执行的唯一操作是将数据附加到列表的尾部。 现在,我使用互斥锁来确保一致性。这就是函数的样子:

void AppendCommand(BackEndCommand * beCmd)
{
    Lock();

    if (cmdHead == nullptr)
    {
        beCmd->next = nullptr;
        cmdHead = beCmd;
        cmdTail = beCmd;
    }
    else
    {
        cmdTail->next = beCmd;
        cmdTail = beCmd;
    }

    ++numCommands;

    Unlock();
}

相关的列表数据和BackEndCommand类型:

struct BackEndCommand {
    // Command number. Any of the BackEndCommandId.
    BackEndCommandId id;
    // Next on the command queue or null if end of list.
    struct BackEndCommand * next;
};

BackEndCommand * cmdHead = nullptr;
BackEndCommand * cmdTail = nullptr;
uint32_t numCommands = 0;

现在我很好奇在我的情况下是否可以通过某种无锁/原子操作替换该互斥锁,可能使用新的C ++ 11 atomics库或类似的? / p>

4 个答案:

答案 0 :(得分:2)

一些答案​​显示了如何通过在列表的开头添加元素来完成它。也可以在列表的末尾添加元素。注意:在以下代码中,_tail_hint并不总是指向最后一个元素,而是指向靠近尾部的元素。

我还通过添加虚拟元素简化了if条件。

struct list
{
    struct node
    {
        int _id;
        std::atomic<node*> _next;
        explicit node(int id, node* next)
            : _id{id}, _next{next}
        { }
    };
    node _head{0, nullptr};
    std::atomic<node*> _tail_hint{&_head};
    void append(node* n)
    {
        node* tail = _tail_hint;
        node* expected = nullptr;
        while (!tail->_next.compare_exchange_weak(expected, n)) {
            if (expected) {
                tail = expected;
                expected = nullptr;
            }
        }
        _tail_hint = n;
    }
};

答案 1 :(得分:1)

是的,这很有可能,只要该列表是仅添加的(至少在并发访问期间)。

如果您不按相反顺序排列列表,这是最简单的,因为那时只有一个变量需要更新(尾部),而且您不必做遍历时的原子载荷很多。

我已经从我的一个项目中调整了一些工作代码(经过彻底的单元测试)。它实现了一个只添加LIFO链表。 (请注意,为了清楚起见,我已将您的next重命名为prev。)

#include <atomic>

std::atomic<BackEndCommand*> tail(nullptr);

// Thread-safe
void add(BackEndCommand* element)
{
    assert(element != nullptr);

    auto prevTail = tail.load(std::memory_order_relaxed);
    do {
        element->prev = prevTail;
    } while (!tail.compare_exchange_weak(prevTail, element, std::memory_order_release, std::memory_order_relaxed));
}

// Thread-safe
void iterate()
{
    for (auto ptr = tail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->prev) {
        // Do something with ptr
    }
}

答案 2 :(得分:1)

是的,你可以

以下是来自的示例代码 http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange

您甚至可以在网页上运行它

#include <atomic>
template<typename T>
struct node
{
    T data;
    node* next;
    node(const T& data) : data(data), next(nullptr) {}
};

template<typename T>
class stack
{
    std::atomic<node<T>*> head;
 public:
    void push(const T& data)
    {
        node<T>* new_node = new node<T>(data);

//      // put the current value of head into new_node->next
//      new_node->next = head.load(std::memory_order_relaxed);
//
//      // now make new_node the new head, but if the head
//      // is no longer what's stored in new_node->next
//      // (some other thread must have inserted a node just now)
//      // then put that new head into new_node->next and try again
//      while(!head.compare_exchange_weak(new_node->next, new_node,
//                                        std::memory_order_release,
//                                        std::memory_order_relaxed))
//          ; // the body of the loop is empty
//
// Note: the above use is not thread-safe in at least 
// GCC prior to 4.8.3 (bug 60272), clang (bug 18899), MSVC (bug 819819).
// The following is a workaround:
        node<T>* old_head = head.load(std::memory_order_relaxed);
        do {
            new_node->next = old_head;
        } while(!head.compare_exchange_weak(old_head, new_node,
                                            std::memory_order_release,
                                            std::memory_order_relaxed));
    }
};
int main()
{
    stack<int> s;
    s.push(1);
    s.push(2);
    s.push(3);
}

答案 3 :(得分:1)

是的,你可以。您需要使用atomic_compare_exchange_weak()atomic_compare_exchange_strong()将前一个尾部的next指针与指向新元素的指针交换。算法如下:

  1. 设置新元素,将其next指针设置为零。重要的是,在调用atomic<>魔法之前完成此操作。

  2. 确定列表中的最后一个元素。使用你喜欢的任何方法。确保其next指针为nullptr。让我们称之为oldTail

  3. 执行atomic_compare_exchange_xxx(),指明您希望oldTail->next仍为nullptr。这可能会失败。如果是这样,其他一些进程会在您不查看时将一个元素附加到列表中。在这种情况下,您需要返回2以确定新尾部。

  4. 如果atomic_compare_exchange_xxx()成功,您就完成了。

  5. 此方法要求列表末尾仅附加。它还要求列表元素保持有效,只要另一个线程可以保存指向它的指针以便附加另一个元素。确保这很棘手,特别是如果你试图以无锁方式进行,但它是可能的(可以通过RCU模式实现,见http://en.wikipedia.org/wiki/Read-copy-update)。