views:

177

answers:

6

This is pure just for interest question, any sort of questions are welcome.

So is it possible to create thread-safe collections without any locks? By locks I mean any thread synchronization mechanisms, including Mutex, Semaphore, and even Interlocked, all of them. Is it possible at user level, without calling system functions? Ok, may be implementation is not effective, i am interested in theoretical possibility. If not what is the minimum means to do it?

EDIT: Why immutable collections don't work.

This of class Stack with methods Add that returns another Stack.

Now here is program:

Stack stack = new ...;

ThreadedMethod()
{
   loop
   {
      //Do the loop
      stack = stack.Add(element);
   }
}

this expression stack = stack.Add(element) is not atomic, and you can overwrite new stack from other thread.

Thanks, Andrey

+4  A: 

Yes, immutable collections! :)

Dimitris Andreou
i thought of that and tried to create one, but failed. You still have pointer/reference to this collection shared and while you add elements someone can override the reference.
Andrey
@Andrey: You can't add elements to an immutable collection.
Matti Virkkunen
If you can add elements, it's not really immutable...
Uri
@Uri - adding element produce new collection. Once you did it you have to share the pointer with other threads. then see your answer :)
Andrey
"Override the reference": what do you mean by that?
Dimitris Andreou
@Dimitris Andreou check edit
Andrey
@Uri @Matti Virkkunen you can create new collection with n+1 element. check editted question.
Andrey
@Andrey, oh, "overwrite" the reference. Well, you implicitly make the (mutable) pointer part of the collection, the combined structure is certainly not immutable then.
Dimitris Andreou
@Andrey: Even in that case the collection itself is quite thread safe. However, your reference to it isn't.
Matti Virkkunen
@Dimitris Andreou pointer to collection is not part of collection. but overall setting is not thread safe, right
Andrey
Yes, I meant the same, the composition of the collection with the extra (mutable) pointer.
Dimitris Andreou
+2  A: 

I don't think so. The problem is that at some point you will need some mutual exclusion primitive (perhaps at the machine level) such as an atomic test-and-set operation. Otherwise, you could always devise a race condition. Once you have a test-and-set, you essentially have a lock.

That being said, in older hardware that did not have any support for this in the instruction set, you could disable interrupts and thus prevent another "process" from taking over but effectively constantly putting the system into a serialized mode and forcing sort of a mutual exclusion for a while.

Uri
you might be right. do you speak from your view or there is some kind of proof of that?
Andrey
Vague recollection from college. I am sure most textbooks discuss this exact question. The point is that you always need a way to do mutual exclusion. To do that, you need support from your hardware and way to access that support from your language.
Uri
I don't agree. test-and-set is not "essentially a lock".
Il-Bhima
@Il-Bhima it falls into my definition of lock in this question (Interlocked)
Andrey
take a look at answer by Strilanc
Andrey
+1  A: 

At the very least you need atomic operations. There are lock free algorithms for single cpu's. I'm not sure about multiple CPU's

sylvanaar
take a look at answer by Strilanc
Andrey
+10  A: 

There seem to be misconceptions by even guru software developers about what constitutes a lock.

One has to make a distinction between atomic operations and locks. Atomic operations like compare and swap perform an operation (which would otherwise require two or more instructions) as a single uninterruptible instruction. Locks are built from atomic operations however they can result in threads busy-waiting or sleeping until the lock is unlocked.

In most cases if you manage to implement an parallel algorithm with atomic operations without resorting to locking you will find that it will be orders of magnitude faster. This is why there is so much interest in wait-free and lock-free algorithms.

There has been a ton of research done on implementing various wait-free data-structures. While the code tends to be short, they can be notoriously hard to prove that they really work due to the subtle race conditions that arise. Debugging is also a nightmare. However a lot of work has been done and you can find wait-free/lock-free hashmaps, queues (Michael Scott's lock free queue), stacks, lists, trees, the list goes on. If you're lucky you'll also find some open-source implementations.

Just google 'lock-free my-data-structure' and see what you get.

For further reading on this interesting subject start from The Art of Multiprocessor Programming by Maurice Herlihy.

Il-Bhima
take a look at answer by Strilanc
Andrey
Petersen's lock is a spin lock. You asked for algorithms without locking. You may very well find that the Semaphore and Mutex implementation in your language of choice is based on Peterson's.Algorithms are locking if they can postpone indefinitely the execution of a thread. Algorithms are non-blocking otherwise. Lock-free and wait-free add further restrictions.
Il-Bhima
If you feel Strilanc's reply answers your question better then you can change the accepted answer, don't worry :-)
Il-Bhima
+2  A: 

Yes, it is possible to do concurrency without any support from the system. You can use Peterson's algorithm or the more general bakery algorithm to emulate a lock.

Strilanc
does it rely on atomic instructions?
Andrey
thank you very much for your answer, this algorithm is very interesting
Andrey
@Andrey It doesn't use any special instructions.
Strilanc
people here explained that it is not possible, i believed them and now i don't know what to think :)
Andrey
The reason they said it's impossible is because the bakery algorithm is typically considered a 'lock'. You phrased your question poorly but I guessed at what you meant.
Strilanc
You link to Peterson's algorithm says it does not work on multiprocessor systems that reorder memory accesses.
Daniel Newby
@daniel Inserting memory barriers at certain places would make it work on multiprocessor systems. But you're correct that, if the cpu re-orders some of the instructions, it stops working correctly.
Strilanc
@Strilanc by lock i meant something low level, something that calls operating system.
Andrey
Would any of you actually use these algorithms? They are very interesting though for sure.
sylvanaar
+1  A: 

It really depends on how you define the term (as other commenters have discussed) but yes, it's possible for many data structures, at least, to be implemented in a non-blocking way (without the use of traditional mutual-exclusion locks).

I strongly recommend, if you're interested in the topic, that you read the blog of Cliff Click -- Cliff is the head guru at Azul Systems, who produce hardware + a custom JVM to run Java systems on massive and massively parallel (think up to around 1000 cores and in the hundreds of gigabytes of RAM area), and obviously in those kinds of systems locking can be death (disclaimer: not an employee or customer of Azul, just an admirer of their work).

Dr Click has famously come up with a non-blocking HashTable, which is basically a complex (but quite brilliant) state machine using atomic CompareAndSwap operations.

There is a two-part blog post describing the algorithm (part one, part two) as well as a talk given at Google (slides, video) -- the latter in particular is a fantastic introduction. Took me a few goes to 'get' it -- it's complex, let's face it! -- but if you presevere (or if you're smarter than me in the first place!) you'll find it very rewarding.

Cowan
Thanks for the video :)
Matthieu M.