问题
I am just starting to look into multi-threaded programming and thread safety. I am familiar with busy-waiting and after a bit of research I am now familiar with the theory behind spin locks, so I thought I would have a look at OSSpinLock's implementation on the Mac. It boils down to the following function (defined in objc-os.h):
static inline void ARRSpinLockLock(ARRSpinLock *l)
{
again:
/* ... Busy-waiting ... */
thread_switch(THREAD_NULL, SWITCH_OPTION_DEPRESS, 1);
goto again;
}
(Full implementation here)
After doing a bit of digging, I now have an approximate idea of what thread_switch
's parameters do (this site is where I found it). My interpretation of what I have read is that this particular call to thread_switch will switch to the next available thread, and decrease the current thread's priority to an absolute minimum for 1 cycle. 'Eventually' (in CPU time) this thread will become active again and immediately execute the goto again;
instruction which starts the busy-waiting all over again.
My question though, is why is this call actually necessary? I found another implementation of a spin-lock (for Windows this time) here and it doesn't include a (Windows-equivalent) thread switching call at all.
回答1:
I actually don't think they're that different. In the first case:
static inline void ARRSpinLockLock(ARRSpinLock *l)
{
unsigned y;
again:
if (__builtin_expect(__sync_lock_test_and_set(l, 1), 0) == 0) {
return;
}
for (y = 1000; y; y--) {
#if defined(__i386__) || defined(__x86_64__)
asm("pause");
#endif
if (*l == 0) goto again;
}
thread_switch(THREAD_NULL, SWITCH_OPTION_DEPRESS, 1);
goto again;
}
We try to acquire the lock. If that fails, we spin in the for
loop and if it's become available in the meantime we immediately try to reacquire it, if not we relinquish the CPU.
In the other case:
inline void Enter(void)
{
int prev_s;
do
{
prev_s = TestAndSet(&m_s, 0);
if (m_s == 0 && prev_s == 1)
{
break;
}
// reluinquish current timeslice (can only
// be used when OS available and
// we do NOT want to 'spin')
// HWSleep(0);
}
while (true);
}
Note the comment below the if
, which actually says that we could either spin or relinquish the CPU if the OS gives us that option. In fact the second example seems to just leave that part up to the programmer [insert your preferred way of continuing the code here], so in a sense it's not a complete implementation like the first one.
My take on the whole thing, and I'm commenting on the first snippet, is that they're trying to achieve a balance between being able to get the lock fast (within 1000 iterations) and not hogging the CPU too much (hence we eventually switch if the lock does not become available).
回答2:
You can implement a spin lock in many different ways. If you find another SpinLock
implementation for Windows you'll see another algorithm for that (it may involves SetThreadPriority
, Sleep
or SwitchToThread
).
Default implementation for ARRSpinLockLock
is clever enough and after one first spinning cycle it "depress" thread priority for a while, this has following advantages:
- it gives more opportunities to the thread that owns the lock to release it;
- it wastes less CPU time (and power!) performing
NOP
orPAUSE
.
Windows implementation doesn't do it because Windows API doesn't offer that opportunity (there is no equivalent thread_switch()
function and multiple calls to SetThreadPriority
could be less efficient).
来源:https://stackoverflow.com/questions/12949028/spin-lock-implementations-osspinlock