
I'm new to multi-threaded programming. So I apologize for aksing what is possibly a silly question. Will below code give me the behaviour of what is commonly called a "spinlock"? boost::unique_lock< boost::shared_mutex > guard( mutex, boost::try_to_lock ); if ( !guard.owns_lock() ) { while ( !guard.try_lock() ) { boost::thread::yield(); } } // access data Cheers, Moritz

On Wed, Jan 20, 2010 at 10:00 PM, Moritz Moeller
I'm new to multi-threaded programming. So I apologize for aksing what is possibly a silly question.
Will below code give me the behaviour of what is commonly called a "spinlock"?
boost::unique_lock< boost::shared_mutex > guard( mutex, boost::try_to_lock );
if ( !guard.owns_lock() ) { while ( !guard.try_lock() ) { boost::thread::yield(); } }
// access data
Cheers,
Moritz
No. A spinlock is for cases where you don't use a mutex (for whatever reason). If you have a mutex, just wait on it: mutex.lock(); // yield to other threads until we can get the mutex // now have mutex, do some work... mutex.unlock(); A spinlock spins on a simple variable: static int lock = 0; // lock the lock while (XCHG(&lock, 1) != 0) yield(); do_work(); // unlock XCHG(&lock, 0); Where XCHG ("exchange') is an atomic operation that sets lock to 1 and returns the old value - atomically! ie in a single 'step' that can't be interrupted. It also, and this is important, it also does the proper memory barriers to ensure that other memory instructions do not get reordered to happen before it. (The CPU can reorder your memory reads/writes otherwise - as an optimization technique.) If the old value (returned by XCHG) was already 1, your 'set' didn't really do anything, the lock was already held by someone else. So yield() to others than try again. If the old (returned) value was 0, then no one else had the lock right before you set it to 1, so you now have it. Leave the while() loop. Typically you shouldn't use spinlocks, unless you are working really low level and know what you are doing, and if do_work() is very short - ie a few processor instructions. If you are new to multi-threading, stick to simple mutex locking/unlocking (using scoped_lock etc). Once you are experienced with MT programming, STILL stick to simple mutex locking/unlocking. Tony

Tony,
Where XCHG ("exchange') is an atomic operation [...]
thanks heaps for the detailed explanation. That makes a lot more sense to me now. Are there any books on this whole subject matter, you could suggest?
Typically you shouldn't use spinlocks, unless you are working really low level and know what you are doing, and if do_work() is very short - ie a few processor instructions.
Well, what I am doing is setting two boolean flags. This happens in an app where interactivity/responsiveness if of utmost importance. I'm writing a plugin for this app. I find using the locking with shared- and unique locks giving me no noticable performance issues on Linux. However, with the same app, on OSX, I experience intolerable slowdowns (which disappear when I remove all locking code -- with the expected "side effects" on stability). That's why I am looking into spinlocks. Also, most libraries I use in this project (e.g. OpenImage IO -- http://www.openimageio.org/ and PTex -- http://ptex.us/), do use spinlocks more or less exclusively. And the operations they protect by these locks are absolutely comparable to what I do.
Once you are experienced with MT programming, STILL stick to simple mutex locking/unlocking.
So why would these people use spinlocks? Some of the people working on these libs do have quite extensive backgrounds in writing multi-threaded apps. Namely 3d renderers. .mm

Typically you shouldn't use spinlocks, unless you are working really low level and know what you are doing, and if do_work() is very short - ie a few processor instructions.
Well, what I am doing is setting two boolean flags. This happens in an app where interactivity/responsiveness if of utmost importance. I'm writing a plugin for this app.
you could have a look at the proposed boost.atomic library, implementing c++0x-style atomics.
Once you are experienced with MT programming, STILL stick to simple mutex locking/unlocking.
So why would these people use spinlocks?
spinlocks are usually faster to acquire and release than mutexes, but require busy waiting, which may be cause an overall performance impact if the critical section takes some time to execute (especially, if the critical section itself is blocking). a calling thread would be suspended when waiting for a mutex to be locked, which could lead to some issues for certain (actually very little) use cases. nevertheless, i would be happy to see spinlocks and reader-writer spinlocks as part of boost.thread. they could easily be implemented via boost.atomic. tim -- tim@klingt.org http://tim.klingt.org Silence is only frightening to people who are compulsively verbalizing. William S. Burroughs

nevertheless, i would be happy to see spinlocks and reader-writer spinlocks as part of boost.thread. they could easily be implemented via boost.atomic.
can we implement it with test-and-test-and-set instead of just test-and-set? while (locked || XCHG(&locked, 1)) yield(); ie only do the memory barrier check if there is likelyhood of it succeeding. Tony

Zitat von Tim Blechmann
Typically you shouldn't use spinlocks, unless you are working really low level and know what you are doing, and if do_work() is very short - ie a few processor instructions.
Well, what I am doing is setting two boolean flags. This happens in an app where interactivity/responsiveness if of utmost importance. I'm writing a plugin for this app.
you could have a look at the proposed boost.atomic library, implementing c++0x-style atomics.
http://www.chaoticmind.net/~hcb/projects/boost.atomic/doc/atomic/usage_examp...
Once you are experienced with MT programming, STILL stick to simple mutex locking/unlocking.
So why would these people use spinlocks?
spinlocks are usually faster to acquire and release than mutexes, but require busy waiting, which may be cause an overall performance impact if the critical section takes some time to execute (especially, if the critical section itself is blocking). a calling thread would be suspended when waiting for a mutex to be locked, which could lead to some issues for certain (actually very little) use cases.
I have experimented with spinlocks within my library, and even if I only use them for mutexes whose locks are usually very short-lived I could produce usecases that end up in a performance-desaster, with 80% CPU consumed by yield() system calls. especially when 3 or more threads contend about a mutex, so 2 of them are yield()ing: if thread 1 has acquired the mutex and thread 2 and 3 are yield()ing, it seems the scheduler constantly switches between threads 2 and 3 until they´ve used up a full time slot until thread 1 is continued and releases the lock. I guess there are some use cases for pure spinlocks, without yield()ing, if you know that you have > 1 CPUs and the locks are short-lives, but I ended up using not a single spinlock. a combination of a Boost.Atomic atomic<> and a boost::mutex I found useful however: combined_mutex() : atomic_count(-1){ this->system_mutex.lock(); } lock(){ if(atomic_count.fetch_add(1,...) >= 0){ this->system_mutex.lock(); } } unlock(){ if(atomic_count.fetch_sub(1,...) > 0){ this->system_mutex.unlock(); } } for low-contention mutexes that's faster than calling pthread for every lock.

Once you are experienced with MT programming, STILL stick to simple mutex locking/unlocking.
So why would these people use spinlocks?
spinlocks are usually faster to acquire and release than mutexes, but require busy waiting, which may be cause an overall performance impact if the critical section takes some time to execute (especially, if the critical section itself is blocking). a calling thread would be suspended when waiting for a mutex to be locked, which could lead to some issues for certain (actually very little) use cases.
I have experimented with spinlocks within my library, and even if I only use them for mutexes whose locks are usually very short-lived I could produce usecases that end up in a performance-desaster, with 80% CPU consumed by yield() system calls. especially when 3 or more threads contend about a mutex, so 2 of them are yield()ing: if thread 1 has acquired the mutex and thread 2 and 3 are yield()ing, it seems the scheduler constantly switches between threads 2 and 3 until they´ve used up a full time slot until thread 1 is continued and releases the lock.
as rule of thumb, i am using spinlocks in the following cases: - locks are acquired very rarely - critical region is very short (a instructions) - the number of threads to be synchronized is lower than the number of cpu cores - the synchronized threads have a similar (possibly real-time) priority btw, using yield() inside a spinlock may cause unwanted behavior, since it preempts the calling thread, but the scheduler keeps it as `ready' instead of `blocked'. so it may wake up, before it can acquire the mutex, burning cpu cycles. tim -- tim@klingt.org http://tim.klingt.org Lesser artists borrow, great artists steal. Igor Stravinsky

Zitat von Tim Blechmann
as rule of thumb, i am using spinlocks in the following cases: - locks are acquired very rarely - critical region is very short (a instructions) - the number of threads to be synchronized is lower than the number of cpu cores - the synchronized threads have a similar (possibly real-time) priority
a set of condition also known as "never" ;-) if the lock is acquired "very rarely" what is the advantage of a spinlock anyway?
btw, using yield() inside a spinlock may cause unwanted behavior, since it preempts the calling thread, but the scheduler keeps it as `ready' instead of `blocked'. so it may wake up, before it can acquire the mutex, burning cpu cycles.
yes, I guess that´s what I was observing, switching between the yield()ing threads instead of continueing with the thread that acquired the lock.

On 01/21/2010 09:10 PM, strasser@uni-bremen.de wrote:
Zitat von Tim Blechmann
: as rule of thumb, i am using spinlocks in the following cases: - locks are acquired very rarely - critical region is very short (a instructions) - the number of threads to be synchronized is lower than the number of cpu cores - the synchronized threads have a similar (possibly real-time) priority
a set of condition also known as "never" ;-)
almost never ;)
if the lock is acquired "very rarely" what is the advantage of a spinlock anyway?
sry, i wanted to write: `in very rare cases different threads try to acquire a lock' spinlocks are low-level primitives and they shouldn't be used, unless you want to avoid that the os preempts the locking thread. cheers, tim -- tim@klingt.org http://tim.klingt.org There's no such entity as "most people". These are generalities. All generalities are meaningless. You've got to pin it down to a specific person doing a specific thing at a specific time and space. William S. Burroughs
participants (4)
-
Gottlob Frege
-
Moritz Moeller
-
strasser@uni-bremen.de
-
Tim Blechmann