views:

1679

answers:

8

Asking this question with C# tag, but if it is possible, it should be possible in any language.

Is it possible to implement a doubly linked list using Interlocked operations to provide no-wait locking? I would want to insert, add and remove, and clear without waiting.

+4  A: 

I don't believe this is possible, since you're having to set multiple references in one shot, and the interlocked operations are limited in their power.

For example, take the add operation - if you're inserting node B between A and C, you need to set B->next, B->prev, A->next, and C->prev in one atomic operation. Interlocked can't handle that. Presetting B's elements doesn't even help, because another thread could decide to do an insert while you're preparing "B".

I'd focus more on getting the locking as fine-grained as possible in this case, not trying to eliminate it.

Reed Copsey
"getting the locking as fine-grained as possible" - that could make it slower, as the cost of taking out many short locks may be more significant than the avoided waiting time, depending on the macro-behaviour of the application.
Daniel Earwicker
Good point, Earwicker. Profiling would be of benefit here
Reed Copsey
This answer is incorrect. Doubly linked lists have been implemented. The key is that the back pointer isn't always up to date and so when you traverse backwards, you have to do some extra work.
Blank Xavier
I don't think @BlankXavier gets the question...
John Gietzen
I don't think @JohnGietzen gets the question. The OP wants to know if it's possible to implement a wait free doubly linked list. It is. The back pointer cannot be trusted, so extra work is done when walking the list backward, but that's not waiting. The data structure has very similar performance characteristics to a "real" doubly linked list. But of course low-lock data structures can often be slower than their much simpler locking counterparts - that's why you need to profile first, and verify that the locking is actually the bottleneck.
Eloff
Reed Copsey
@Reed Copsey: True as far as I know. But of course a spin lock is just interlocked compare exchange in a loop, likewise a lock-free single linked list using CAS is really the same as a spin lock, you're just spinning on the pointer instead of a separate lock state variable.
Eloff
A: 

I would say that the answer is a very deeply qualified "yes, it is possible, but hard". To implement what you're asking for, you'd basically need something that would compile the operations together to ensure no collisions; as such, it would be very hard to create a general implementation for that purpose, and it would still have some significant limitations. It would probably be simpler to create a specific implementation tailored to the precise needs, and even then, it wouldn't be "simple" by any means.

McWafflestix
"something that would compile the operations together to ensure no collisions" - what does that mean?
Daniel Earwicker
Think of it like how modern CPUs utilize multiple execution units on one core to achieve high instruction throughput; the interactions of the instructions with the execution units is checked to be non-interdependent, and the microinstructions are interleaved so as to preserve any order-dependent operations. It's fundamentally a compilation operation.
McWafflestix
This is a pretty much a non-issue as most languages supply some sort of "memory barrier" that disallows both the compiler and the CPU from moving read/write operations beyond it, so you can make sure it doesn't rearrange the operations in a way that invalidates you're code. (Its why the volatile keyword in c/c++ was invented, though volatile only ensures this against other volatile variables, hence the introduction of memory barriers).
Grant Peters
There are better methods than the method suggested here. I don't think anyone has tried doing this - it seems basically to be a form of serialisation, not unlike a multiple-reader/single-writer lock.
Blank Xavier
+11  A: 

A simple google search will reveal many lock-free doubly linked list papers.

However, they are based on atomic CAS (compare and swap).

I don't know how atomic the operations in C# are, but according to this website

http://www.albahari.com/threading/part4.aspx

C# operations are only guaranteed to be atomic for reading and writing a 32bit field. No mention of CAS.

Unknown
C# (.NET) absolutely has an atomic Compare-and-Swap. It is called Interlocked.CompareExchange. http://msdn.microsoft.com/en-us/library/system.threading.interlocked.compareexchange.aspx
Cheeso
@ Cheeso, then in that case, its possible.
Unknown
It could be used to implement a multi-threaded user-interface - no more single UI Thread.
luvieere
+4  A: 

Here is a paper which discribes a lock free doublly linked list.

We present an efficient and practical lock-free implementation of a concurrent deque that is disjoint-parallel accessible and uses atomic primitives which are available in modern computer systems. Previously known lock-free algorithms of deques are either based on non-available atomic synchronization primitives, only implement a subset of the functionality, or are not designed for disjoint accesses. Our algorithm is based on a doubly linked list, and only requires single-word compare-and-swap...

Ross Bencina has some really good links I just found with numerious papers and source code excamples for "Some notes on lock-free and wait-free algorithms".

RandomNickName42
I believe however, AFAIK, *ALL* doubly-linked lists (and indeed, it might even be all singly-linked lists) require GC.
Blank Xavier
I believe Valois' 1995 singly-linked list does not require GC - I need to read the paper though.
Blank Xavier
A: 

FWIW, .NET 4.0 is adding a ConcurrentLinkedList, a threadsafe doubly linked list in the System.Collections.Concurrent namespace. You can read the documentation or the blog post describing it.

Rick
Threadsafe by no means means lock-free.
Blank Xavier
A: 

Read the footnote - they plan to pull ConcurrentLinkedList from 4.0 prior to the final release of VS2010

+2  A: 

What I've seen so far here are papers which demonstrate the implementation of a double-ended queue without locks, not the implementation of a general doubly-linked list. The former only supports operations on the ends of the list, where the latter must allow insertions and deletions from the middle.

It seems to me that a general doubly-linked list is possible, depending on what kind of consistency you're looking for. I can think of two relevant levels of consistency right off:

  1. Everybody sees the same list all the time.

    This is the highest level of consistency you can seek, because it forbids any discrepancy between, say, a forward-reader and a backward-reader. I cannot find a paper which implements this without locking for a doubly-linked list. Singly-linked implementations are out there.

  2. Every traversal of the list is successful.

    There are two pointers in a doubly-linked node: next and previous. One of these may be safely removed at a time using only non-lock primitives, just like a singly-linked list. Swapping both of the pointers atomically is not supported on any common multicore architecture (as noted here).

    Basically, this second level of consistency is made by treating a doubly-linked list as two related singly-linked lists. By removing just one pointer at a time, you may keep the list consistent for all readers in a particular direction. If it's okay for forward-readers and backward-readers to occasionally see a slightly different list, then this will totally work.

    This does make some assumptions about how nodes are de-allocated. It would be very easy, in an explicit-deallocation memory system, to delete a node which is currently being iterated through in a different thread. I think garbage-collected environments would be fine.

Andres Jaan Tack
That paper you quoted - there was a revised version published a year later which extends the work into a general doubly linked-list.
Blank Xavier
Would you link us to that work?
Andres Jaan Tack
A: 

It's relatively (lock-free stuff is all complex) to implement an add-only doubly linked list. You can prolly extend it easily to being able to logically delete elements - just they won't be actually physically deleted, not until the whole list is deleted.

That might be enough for you.

Blank Xavier