13

I am trying to design a linked list in c++ that allows concurrent access. Clearly using a single lock for this list is grossly inefficient since disjoint areas may be updated in parallel. Now what are my options other than storing a lock per node?

Also, would a non-blocking version be a better bet in this case? Any relevant links, anyone?

EDIT: Thanks for the responses people. Couple of things I'd like to add:

  1. How about storing N locks for every M nodes instead of 1:1 lock:node ratio? Some threads would wait but it's kind of a trade off. What do you think?
  2. If I intend to find some node in this linked list looks like all the mutexes need be locked. This is an issue, because locking and unlocking of all the mutexes is time consuming. Does anyone have a better alternative?
  3. I am open to non-blocking algorithms. But how do I use CAS in good old C++ without using assembly? I hear that gcc has some __sync attributes that do similar work but not sure.
  4. With non-blocking approach, how do you do a find on the linked list?
Phil Miller
  • 36,389
  • 13
  • 67
  • 90
Fanatic23
  • 3,378
  • 2
  • 28
  • 51

5 Answers5

5

It's possible to implement a non-blocking singly linked list using interlocked functions:

Interlocked Singly Linked Lists

I don't think it's possible to implement a non-blocking doubly linked list without interlocked compare-and-swap (CAS), which isn't widely available.

  • 4
    Does this question suggest windows in any way? – Falmarri Nov 12 '10 at 18:08
  • 2
    CAS is on every modern processor and has been for a few years I think. DCAS is the one that's not always available. – Joseph Garvin Nov 12 '10 at 18:28
  • 1
    Yeah, CAS is pretty damn common. It's an implementation-specific feature but most uses of it devolve to a lock/mutex in cases where CAS proper isn't available (ARM comes to mind.) – Jonathan Grynspan Nov 12 '10 at 18:53
  • 1
    @Falmarri, that was an example. –  Nov 12 '10 at 19:07
  • 1
    I believe it is possible to do double-linked list if you don't mind the list being inconsistent, but not corrupt during the update. (next and back will always go to valid nodes, just not always the node expected) – Zan Lynx Nov 12 '10 at 22:49
  • @Martin Thanks for the link but I would prefer something platform independent. – Fanatic23 Nov 13 '10 at 05:29
3

Linked lists are inherently sequential data structures. No matter what kind of machinery you use to implement it, the interface and expected behavior imply sequential logic.

What are you trying to do? That should determine what data structure you use, not the familiarity of the data structures you already are comfortable with.

MSN
  • 53,214
  • 7
  • 75
  • 105
  • They aren't necessarily inherently sequential. Different threads could hold pointers to different areas of the list and independently move forwards/backwards. – Joseph Garvin Nov 12 '10 at 18:25
  • 1
    A linked list implies a global ordering on the entire data structure. If you don't want a global ordering on the entire data structure, don't use a single linked list as a shared data structure. – MSN Nov 12 '10 at 19:30
  • @MSN Multiple writer threads intend to insert/append data from different parts of the list concurrently. Clearly using a single lock is not optimal here. – Fanatic23 Nov 13 '10 at 05:25
  • 1
    @Fanatic23, why do you need a single global list? Please read the PDF I linked to. It was written by Mike Acton; it has a bit of a low level perspective on concurrency, but it should help point out why a global linked list is not such a great shared data structure. – MSN Nov 13 '10 at 20:29
  • @MSN I just want a store of data that'd be easy to insert/delete with multiple threads. Specific insertion and deletion points are required so I have to use a list -- stack, queue, vectors they don't work for me. I also read through the pdf, the takeaways are good but there's nothing remotely concrete about implementation tradeoffs. Which is what I need. – Fanatic23 Nov 14 '10 at 14:14
  • 1
    @Fanatic23, what kind of operations will you do on the entire list? – MSN Nov 14 '10 at 18:08
1

There are lots of different ways you could go about this, depending on how you're going to use the list.

First, for a list, a single mutex lock is not necessarily a big deal because lists can support splicing. Say you have a list of chars AF and another list BCDE. You don't have to lock the mutex, insert B, lock it again, insert C, etc. You can insert BCDE all at once between A and F by setting A's next pointer to B and E's next pointer to F, forming ABCDEF. No matter how many elements you want to insert at once you only need one lock. This is true even for a doubly linked list. So it might not be a bottleneck in your application.

Assuming that it would be a bottleneck though, you need to consider whether you're going to have one or multiple writers and one or multiple readers.

Assuming you have a single writer and multiple readers, you can avoid mutex locks entirely by using atomic instructions, assuming they're available for your architecture. In GCC these are available through the builtin __sync_* functions and in Visual C++ they're available via Interlocked*, but if you're stuck with a compiler that lacks direct support for them you can still use them via inline assembly. The writer will use the atomic instructions to atomically set next pointers to patch elements in and out of the list.

Hopefully that gets you started. To get a deep answer I'd suggest asking another question and including:

  1. Are there one/multiple readers?
  2. Are there one/multiple writers?
  3. Singly or doubly linked list?
  4. Some idea of what your use case is.
Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • Thanks so much for the response, +1 from my side. I would prefer to stick with mutexes for now. My concern is implementation tradeoffs, what works and when: having a single mutex isn't good and single mutex per node perhaps too expensive. – Fanatic23 Nov 14 '10 at 14:16
0

What about Skip Lists? Have a look at java implementation in "concurrent" package.

Stas
  • 1,707
  • 15
  • 25
-1

Are you sure this is a problem worth solving? Would it perhaps be more useful to encapsulate the actual end-use into a class that handles the locking of a normal list and provides a thread-safe interface? This way you don't try to put locking at too low of a level (which as you surmised is not an easy problem to solve).

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 1
    I'm sure this is a problem worth solving. If I lock the normal list class using a single mutex clearly I'd be able to get the work done. But this is not a fast system and imposes serious constraints on system scalability. Consider multiple writer threads which would want to append/edit different disjoint sections of the linked list. They could do without a single lock. – Fanatic23 Nov 13 '10 at 05:27