在线程编程中保护简单列表?

时间:2012-01-31 14:58:37

标签: c++ multithreading synchronization

我正在为一些练习阅读一本POSIX线程书,我试图在一个简单的单链表中找到我需要互斥锁的地方作为一个小练习题。例如,如果我有一个节点结构列表:

template <typename T>
struct Node
{
    Node<T>* next;
    T data;
};

Node<T>* head = NULL;

//Populate list starting at head...


[HEAD] --> [NEXT] --> [NEXT] --> [NEXT] --> [...] --> [NULL]

我有两个或更多线程。任何线程都可以在列表中的任何位置插入,删除或读取。

看起来如果你只是试图保护单个列表元素(而不是整个列表),你永远不能保证另一个线程没有修改下一个*指针指向的那个,所以你不能保证安全和维护不变量。

是否有更有效的方法来保护此列表,而不是使其上的所有操作使用相同的互斥锁?我原本以为有,但我真的想不到它。

另外,如果它是双重链表,情况会发生变化吗?

2 个答案:

答案 0 :(得分:3)

如果您想使用单链表(即每个节点一个锁)进行细粒度锁定方法,那么您需要执行以下操作:

  1. 将两个标记节点添加到headtail的列表中。这两个节点都有与之关联的锁,每个新节点都将添加到它们之间。
  2. 对于遍历列表的永久函数,在将其分配给current指针之前,需要在下一个节点上获取锁定。在获得下一个节点上的锁之前,您也无法释放当前节点上的锁。如果您还使用prev指针进行遍历,则将锁定该“上一个”节点,直到您将prev指针重新分配给current指针。
  3. 要添加节点,您需要锁定将要添加的节点两侧的两个节点。因此,例如,您将拥有prev节点指针和current节点指针。您首先要锁定prev节点上的互斥锁,然后锁定current节点上的互斥锁,并在prevcurrent节点之间添加新节点
  4. 如果要删除节点,则会再次锁定prevcurrent节点中的互斥锁(按此顺序),然​​后您可以删除current节点。
  5. 请记住,步骤#3和#4的工作原因是步骤#2遍历列表需要获取节点上的锁。如果跳过该步骤,则最终会创建悬空指针以及与错误指定的指针相关的其他问题,因为另一个线程会更改当前线程下方列表的拓扑。

答案 1 :(得分:2)

由于锁定单个节点不是线程安全的(一个线程可能尝试在其后插入,而另一个线程忙于删除下一个节点),因此也不会锁定节点的子集。你最好的选择是在整个列表中使用全局互斥,除非你愿意花一些时间来开发一个read-write lock,这至少会允许多个线程同时读取列表。

由于双向链表是一个更复杂的结构,我会保持同样的建议。