更新时间:2023-11-10 09:36:22
为什么不直接适用粗粒度的锁?只是锁定整个队列。
一个更复杂(但不一定更有效,取决于你的使用模式)的解决方案是使用读写锁,读,写,分别为。
使用无锁的操作似乎对我没有对你的情况很不错的主意。想象一些线程遍历队列,并在同一时刻的当前项被删除。不要紧,你遍历算法多少额外的链接认为,所有的项目可能会被删除,所以你的code就没有机会完成穿越。
使用比较和交换的另一个问题是,使用指针你永远不知道是否真的指向相同的旧结构,或者旧的结构已被释放,并在同一地址的一些新的结构分配。这可以或者为您的算法可能不会成为一个问题。
有关的本地锁定(即可能性分别锁定每个列表项)的情况下,一个想法是,以有序的锁。订购锁保证了僵局是不可能的。所以,你的操作是这样的:
由指针p到 previous 的项目删除
插入开头:
这似乎是正确的,我没有尝试但这个想法。
从本质上讲,这使得双链表工作就好像它是一个单链接列表。
如果你没有指针previous列表元素(当然这是通常的情况下,因为它几乎不可能保持一致的状态,这样的指针),你可以做到以下几点:
由指针c删除到要删除的选项:
请注意,仅仅有一个指向某个列表项不能确保该项目不释放,所以你需要有一种引用计数,使该项目不会在非常时刻摧毁你试图锁定
请注意,在最后算法重试次数是有界的。事实上,新的项不能显示在C的左侧(插入是在最右边的位置)。如果我们的步骤5失败,因此,我们需要一个重试,这只能由具有p-从同时列表中删除引起的。这种去除可以发生大于N-1次,其中N为c的列表中的初始位置不多。当然,这个最坏的情况下是相当不太可能发生。
I'm trying to implement a (special kind of) doubly-linked list in C, in a pthreads environment but using only C-wrapped synchronization instructions like atomic CAS, etc. rather than pthread primitives. (The elements of the list are fixed-size chunks of memory and almost surely cannot fit pthread_mutex_t
etc. inside them.) I don't actually need full arbitrary doubly-linked list methods, only:
So perhaps a better way to describe this data structure would be a queue/fifo with the possibility of removing items mid-queue.
Is there a standard approach to synchronizing this? I'm getting stuck on possible deadlock issues, some of which are probably inherent to the algorithms involved and others of which might stem from the fact that I'm trying to work in a confined space with other constraints on what I can do.
Edit: In particular, I'm stuck on what to do if adjacent objects are to be removed simultaneously. Presumably when removing an object, you need to obtain locks on both the previous and next objects in the list and update their next/prev pointers to point to one another. But if either neighbor is already locked, this would result in a deadlock. I've tried to work out a way that any/all of the removals taking place could walk the locked part of the list and determine the maximal sublist that's currently in the process of removal, then lock the nodes adjacent to that sublist so that the whole sublist gets removed as a whole, but my head is starting to hurt.. :-P
Conclusion(?): To follow up, I do have some code I want to get working, but I'm also interested in the theoretical problem. Everyone's answers have been quite helpful, and combined with details of the constraints outside what I expressed here (you really don't want to know where the pointer-to-element-to-be-removed came from and the synchronization involved there!) I've decided to abandon the local-lock code for now and focus on:
Thanks again to everybody who gave answers. If my experiment doesn't go well I might come back to the approaches outlined (especially Vlad's) and try again.
Why not just apply a coarse-grained lock? Just lock the whole queue.
A more elaborate (however not necessarily more efficient, depends on your usage pattern) solution would be using a read-wrote lock, for reading and writing, respectively.
Using lock-free operations seem to me not a very good idea for your case. Imagine that some thread is traversing your queue, and at the same moment the "current" item is deleted. Doesn't matter how many additional links your traverse algorithm holds, all that items may be deleted, so your code would have no chance to finish the traversal.
Another issue with compare-and-swap is that with pointers you never know whether it really points to the same old structure, or the old structure has been freed and some new structure is allocated at the same address. This may or may not be an issue for your algorithms.
For the case of "local" locking (i.e., the possibility to lock each list item separately), An idea would be to make the locks ordered. Ordering the locks ensures the impossibility of a deadlock. So your operations are like that:
Delete by the pointer p to the previous item:
Insert into the beginning:
This seems to be correct, I didn't however try this idea.
Essentially, this makes the double-linked list work as if it were a single-linked list.
If you don't have the pointer to the previous list element (which is of course usually the case, as it's virtually impossible to keep such a pointer in consistent state), you can do the following:
Delete by the pointer c to the item to be deleted:
Note that just having a pointer to some list item cannot ensure that the item is not deallocated, so you'll need to have a kind of refcounting, so that the item is not destroyed at the very moment you try to lock it.
Note that in the last algorithm the number of retries is bounded. Indeed, new items cannot appear on the left of c (insertion is at the rightmost position). If our step 5 fails and thus we need a retry, this can be caused only by having p removed from the list in the meanwhile. Such a removal can occur not more than N-1 times, where N is the initial position of c in the list. Of course, this worst case is rather unlikely to happen.