by keithgabryelski on 5/4/14, 11:04 PM with 25 comments
by colanderman on 5/5/14, 12:16 AM
1. A sets g1 = A
2. A reads g2 = none
3. A reads g1 = A
4. B sets g1 = B
5. B reads g2 = none
6. B reads g1 = B
7. A sets g2 = A (read in #3)
8. A reads g1 = B
9. A retries
10. A reads g2 = A
11. A succeeds
12. B sets g2 = B (read in #6)
13. B reads g1 = B
14. B reads g2 = B
15. B succeeds
You also are missing memory fences, which are needed to ensure proper ordering of memory operations. (Otherwise the processor is free to speculate reads and delay writes between cores.) See https://www.kernel.org/doc/Documentation/memory-barriers.txt for a thorough explanation.
by jacquesm on 5/5/14, 12:22 AM
I can see a few problems with this code, please consider the following constructive criticism.
for (;;) {
if (state->g2 == locker_id) {
return;
}
while (state->g2 != SPIN_LOCK_NO_LOCKER) {
if (spin_wait_callback != NULL) {
spin_wait_callback();
}
You don't need this 'continue', the loop will do that just fine without it. continue;
}
The following four lines are problematic: state->g1 = locker_id;
if (state->g2 == SPIN_LOCK_NO_LOCKER) {
Imagine the consequences of a task switch right here. state->g2 = state->g1;
This if does nothing. if (state->g1 == locker_id) {
And you're going to do the following if without all the extra
conditionals above when you do not return from here the
next time you iterate through the loop. So all that extra
testing of state->g1 above does nothing. if (state->g2 == locker_id) {
return;
}
}
}
}
I think I can see what you're trying to do here but once you
fix the basic problems with the code and you test it under
very high load locking and unlocking a resource you'll find
that fairly soon two threads will be accessing the resource
at the same time.Try a very tight loop that locks a counter, does a load, increment, store on it and then unlocks the counter again. Keep separate tallies per thread and use a third thread to keep track of the expected sum and the actual sum.
That should show you reasonably quickly why atomicity in the basic operation of acquiring a lock is a base requirement.
Locking issues almost never show up when you are running lightly loaded on a single core. But the lack of ensuring all cores see the same information, that race conditions can't occur and that instructions are executed in the right order will show up with a terrible certainty under load on multiple cores.
by jpollock on 5/5/14, 12:23 AM
C/c++ doesn't have many guarantees about operation ordering. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLo...
by colin_mccabe on 5/5/14, 12:30 AM
The Linux kernel guys wrote an in-depth explanation of why volatile doesn't work for this use-case here: https://www.kernel.org/doc/Documentation/volatile-considered...
by comex on 5/5/14, 12:37 AM
by tsukikage on 5/5/14, 12:21 AM
if (state->g2 == SPIN_LOCK_NO_LOCKER) {
state->g2 = state->g1;
if (state->g1 == locker_id) {
if (state->g2 == locker_id) {
return;
}
}
}
You need to insert memory barriers between the writes and the reads. Without, on systems with multiple cores, other cores may not see the state changes in program order, which will make it possible for multiple threads to conclude they won the race.http://en.wikipedia.org/wiki/Memory_barrier http://en.wikipedia.org/wiki/Weak_consistency
by Moral_ on 5/5/14, 12:11 AM
real_test.c: In function ‘main’:
real_test.c:14:10: error: too many arguments to function ‘spin_allocate’
spin-lock.h:20:32: note: declared here
real_test.c:16:3: error: too few arguments to function ‘spin_lock’
spin-lock.h:19:13: note: declared here
Easy to fix, just annoying.by asveikau on 5/5/14, 12:41 AM
by nkurz on 5/5/14, 12:33 AM
void
spin_lock(struct spin_lock_state *state, int locker_id,
void (*spin_wait_callback)()) {
for (;;) {
if (state->g2 == locker_id) {
return;
}
while (state->g2 != SPIN_LOCK_NO_LOCKER) {
if (spin_wait_callback != NULL) {
spin_wait_callback();
}
continue;
}
state->g1 = locker_id;
if (state->g2 == SPIN_LOCK_NO_LOCKER) {
state->g2 = state->g1;
if (state->g1 == locker_id) {
if (state->g2 == locker_id) {
return;
}
}
}
}
}
void
spin_unlock(struct spin_lock_state *state, int locker_id)
{
state->g2 = SPIN_LOCK_NO_LOCKER;
}
by frozenport on 5/5/14, 12:15 AM
What is a `machine level exchange instruction`?
by michaelmior on 5/5/14, 12:14 AM
by keithgabryelski on 5/4/14, 11:51 PM