POSIX Threads: Semi-FAQ Revision 5.2
© 2001-2006 Michael M. Lampkin
email: michael.lampkin<at>ieee.org
© 2001-2006 Michael M. Lampkin
email: michael.lampkin<at>ieee.org
A spin lock is another type of mutual exclusion lock. To see the differences between a spin lock and mutex lets look at the sequence for mutex lock acquisition :
* A thread attempts to gain a lock on the mutex.
* If the mutex IS NOT held by another thread then the current thread is given the lock.
* If the mutex IS held by another thread then the current thread is suspended by the scheduler and resumed only when it can successfully gain the lock.
On the other hand, acquiring a spin lock follows this sequence:
* A thread attempts to gain a lock on the spin lock.
* If the spin lock IS NOT held by another thread then the current thread is given the lock.
* If the spin lock IS held by another thread then the current thread will go into a tight loop and in each loop iteration attempt to gain a lock on the spin lock.
The result of the "spin" while trying to gain a lock on a spin lock can help provide a faster acquistion of the lock when it is released. This is due to the fact that the context switch required when suspending and resuming a thread that is trying to gain a lock on a regular mutex which is held by another thread is no longer required.
There is a downside to using spin locks. Since the calling thread can spin when trying to acquire the lock, that means it will be consuming CPU cycles without actually performing any work. It is even possible for such a spin to consume more cycles than the amount that would be consumed doing a context switch using a normal mutex.
To determine if a system implements spin locks you can check the unistd.h system header file and check if _POSIX_SPIN_LOCKS is defined. If it defined and the value is greater than zero, then spin locks are provided and if the value is 200112L then the implementation should be fully compliant with the most recent specification.
A mutex is of type pthread_spinlock_t so the first step of creating an object of this type for use is to include a declaration in your application source code as follows:
pthread_spinlock_t [SPIN_VARIABLE_NAME];
Even though the spinlock variable has been declared and will be instantiated on execution of the given code, it is still not ready for use.
Prior to the first use of the spinlock it must be initialized by calling the function:
int pthread_spin_init ( pthread_spinlock_t * lock, int pshared );
Where the lock parameter specifies the memory address of the spinlock we wish to initialize and the pshared parameter specifies if the spinlock will be accessible only by the current process or all processes on the system. To set the latter behavior we use either the value PTHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED respectively. Since some systems may not provide spin locks which can be shared between processes so you should verify that unistd.h defines _POSIX_THREAD_PROCESS_SHARED to a value greater than zero, with the expected value being 200112L.
On success the function will return a value of zero, the spinlock will be initialized and in an unlocked state. If the system does not have enough memory to allocate a init a new spinlock the error ENOMEM will be returned while if there is some other resource is lacking an error of EAGAIN will be returned.
Unlike mutex initialization, spinlocks do not have a static initializer and have no additional attributes.
As with most pthread objects, once a spinlock is no longer needed it should be destroyed to release any system resources which were allocated to it during initialization.
Since the a spinlock may be allocated system resources during initialization, you should free those resources when the spinlock is no longer required.
To do this you use the function: int pthread_spin_destroy ( pthread_spinlock_t * lock );
If the spinlock is actually still in use when this function is called an error of EBUSY will be returned to the caller and the spinlock will be left unmodified.
There is no specified limit to the number of spinlocks which may exist at the same time. The primary limiting factors are memory and other system resources. The "other system resources" are implementation specific.
To gain an exclusive lock on a spinlock you call the function:
int pthread_spin_lock ( pthread_spinlock_t * lock );
Where the lock parameter is the spinlock to which the calling thread desires exclusive access and on success a zero is returned.
Here is another situation where a mutex and spinlock differ. For a default "normal" type mutex, a thread attempting to lock the mutex when it already holds it will result in a deadlock condition and the thread never returning. For a spinlock though, attempting to gain a lock which is already held results in the pthread_spin_lock( ) function returning an errno of EDEADLK immediately.
Of course, as with any mutual exclusion type lock, any thread that acquires the lock should eventually release it. The function used to release a spinlock is:
int pthread_spin_unlock ( pthread_spinlock_t * lock );
This call will return a zero if it succeeds. You can get a return value of EBUSY if you attempt to release a spinlock that is held by a thread other than the caller.
There is no way to directly test a spinlock to see if it is owned by another thread but there is a function supplied which allows you to attempt to acquire the spinlock and return if it cannot be acquired immediately.
That prototype for that function is:
int pthread_spin_trylock ( pthread_spinlock_t * lock );
Where lock is the spinlock that the calling thread is attempting to acquire.
A successful call to this function will result in the calling thread acquiring the spinlock and a value of zero being returned. If another thread currently holds the spinlock then the function again returns immediately but in this case with a error value of EBUSY.
There is no EDEADLK error returned as with a normal pthread_spin_lock( ) function call where the calling thread already owns the lock in question. So expect an EBUSY in such a case and that you will not be able to tell if it was a result of a recursive locking attempt or just due to another thread currently holding the lock.
There is no POSIX function or parameter to specify how long a thread will spin when attempting to acquire a spinlock held by another thread. If you desire this type of functionality then possibly the best way to do it is by creating your own spin loop in user code and utilizing the pthread_spin_trylock.
There is no POSIX function that supplies this functionality. The rationale, as with threads crashing that hold a mutex, is that the critical code that the mutex was protecting will in all likelihood be in an indeterminate state after such a crash. If that is the case then you wouldn't want some other thread accessing that critical section.
Which thread gains ownership of a spinlock when several are simultaneously contending for it is governed mostly be the same rules as those followed under the same conditions with mutexes.
Those basic rules are:
* looking at the scheduling policies of the spinning threads and order them according to an implementation specific preference.
* order the threads with the prefered scheduling policy by priority.
* if there is more than one thread with the same priority then give the lock to the one which either would get the next available time slice on the CPU.
The third rule (especially) may not make immediate sense until you consider the scenario where the total number of active threads in the process exceeds the available CPUs on the system. In such it is possible for not just one but all the threads spinning on the spinlock to be put into a suspended state by the scheduler and to require time slice acquisition to continue their spin.
The best time to use a spinlock is when the protected critical section of code is performing a simple CPU bound task that should be completed as quickly as possible. An example of a simple CPU bound task would be something like incrementing counters or perhaps reading or modifying a memory mapped IO port.
A spinlock should NOT be used if the critical section has code in it that is IO bound. An example of this would be waiting for keyboard input from a user. It should also not be used to provide a lock around code that is doing CPU bound operations which will take an extended amount of time and cannot be used when recursive locking is required.
The reason you don’t want to use a spinlock when you critical section is performing any type of IO operations is quite simple. Such code can block for an extremely long amount of time. That means that the situation can arise where one thread holds the spinlock and one or more are spinning and attempting to acquire it. The spinning threads will not only waste CPU cycles but there is also a very good chance the scheduler will suspend them after a while and only resume them when the lock is released. The latter is the same thing which happens with a regular mutex except now you have also wasted additional CPU cycles by spinning.