If you've ever worked on parallel or multiprocessor software, you've almost certainly encountered bugs owning to race conditions between concurrently-executing components. While race conditions intuitively seem bad, it turns out there are cases in which we can use them to our advantage! In this talk, we'll discuss a number of ways that race conditions are used in improving throughput and reducing latency in high-performance systems, without sacrificing correctness along the way.
We'll begin this exploration with a discussion of how various mutual exclusion primitives like locks are implemented efficiently in modern hardware using benign race conditions. From there, we'll investigate how one can implement non-blocking algorithms and concurrent data structures in a correct and deterministic manner using freely-available open source libraries.
26. Allocation Protocol
• An request to allocate is followed by a response
containing an object
• A request to free is followed by a response after the
supplied object has been released
• Allocation requests must not respond with an already-
allocated object
• A free request must not release an already-unallocated
object
31. An Execution History
Time
A(allocate request)
B(allocate request)
A(allocate response)
B(allocate response)
“X happened before Y” =>
“Y may observe X to have occurred”
97. Atomic i
=
i+1;
void
atomic_inc(int
*ptr)
{
int
i,
i_plus_one;
do
{
i
=
*ptr;
i_plus_one
=
i
+
1;
}
while
(!cas(i,
i_plus_one,
ptr));
}
98. void
atomic_inc(int
*ptr)
{
int
i,
i_plus_one;
do
{
i
=
*ptr;
i_plus_one
=
i
+
1;
}
while
(!cas(i,
i_plus_one,
ptr));
}
Atomic i
=
i+1;
99. void
atomic_inc(int
*ptr)
{
int
i,
i_plus_one;
do
{
i
=
*ptr;
i_plus_one
=
i
+
1;
}
while
(!cas(i,
i_plus_one,
ptr));
}
Atomic i
=
i+1;
100. void
atomic_inc(int
*ptr)
{
int
i,
i_plus_one;
do
{
i
=
*ptr;
i_plus_one
=
i
+
1;
}
while
(!cas(i,
i_plus_one,
ptr));
}
Atomic i
=
i+1;
101. void
atomic_inc_mod_32(int
*ptr)
{
int
i,
new_i;
do
{
i
=
*ptr;
new_i
=
i
+
1;
new_i
=
new_i
%
32;
}
while
(!cas(i,
new_i,
ptr));
}
Atomic i
=
(i+1)
%
32;
102. TAS using CAS
void
tas_loop(spinlock
*m)
{
do
{
;
}
while
(!cas(UNLOCKED,
LOCKED,
m));
}
105. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head
));
return
a;
}
slab head
A B …
106. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head
));
return
a;
}
slab head
A B …
107. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head
));
return
a;
}
A B …slab head
108. B …
slab head
A
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head
));
return
a;
}
109. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(
,
,
));
return
a;
}
slab head
a
a b
Cmpr and *
&s->head
A B …
b
a
110. slab head
Cmpr and
Z
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(
,
,
));
return
a;
}
a
a b &s->head
b
a
111. slab head
Z A B
Cmpr and
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(
,
,
));
return
a;
}
a
a b &s->head
b
a
112. slab head
B …
Cmpr and
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(
,
,
));
return
a;
}
a
a b &s->head
b
a
113. void
free(slab
*s,
obj
*o)
{
do
{
obj
*t
=
s-‐>head;
o-‐>next
=
t;
}
while
(!cas(t,
o,
&s-‐>head));
}
B …slab head
114. void
free(slab
*s,
obj
*o)
{
do
{
obj
*t
=
s-‐>head;
o-‐>next
=
t;
}
while
(!cas(t,
o,
&s-‐>head));
}
slab head
A B …
117. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
A B Cslab head
118. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
A B Cslab head
119. A B C
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
slab head
120. A B C
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
slab head
121. A B C
some_object
=
allocate(&shared_slab);
slab head
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
122. A B C
some_object
=
allocate(&shared_slab);
slab head
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
123. B C
A
slab head
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
124. B C
A
slab head
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
125. B C
another_obj
=
allocate(&shared_slab);
A
slab head
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
126. B C
another_obj
=
allocate(&shared_slab);
A
slab head
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
127. C
A
B
slab head
another_obj
=
allocate(&shared_slab);
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
128. C
A
B
slab head
another_obj
=
allocate(&shared_slab);
some_object
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
129. B
C
A
slab head
another_obj
=
allocate(&shared_slab);
free(&shared_slab,
some_object);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
130. B
C
A
slab head
another_obj
=
allocate(&shared_slab);
free(&shared_slab,
some_object);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
131. B
A Cslab head
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
free(&shared_slab,
some_object);
132. B
A Cslab head
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
free(&shared_slab,
some_object);
133. B
A Cslab head
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
free(&shared_slab,
some_object);
134. B
A Cslab head
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
free(&shared_slab,
some_object);
135. free(&shared_slab,
some_object);
B
B Cslab head
A
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
136. free(&shared_slab,
some_object);
B
B Cslab head
A
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
137. free(&shared_slab,
some_object);
B
B Cslab head
A
another_obj
=
allocate(&shared_slab);
obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
138. The ABA Problem
“A reference about to be modified by a CAS
changes from a to b and back to a again. As a
result, the CAS succeeds even though its effect on
the data structure has changed and no longer has
the desired effect.” —Herlihy & Shavit, p. 235
139. obj
*allocate(slab
*s)
{
obj
*a,
*b;
do
{
a
=
s-‐>head;
if
(a
==
NULL)
return
NULL;
b
=
a-‐>next;
}
while
(!cas(a,
b,
&s-‐>head));
return
a;
}
A B …slab head
166
140. obj
*allocate(slab
*s)
{
slab
orig,
update;
do
{
orig.gen
=
s.gen;
orig.head
=
s.head;
if
(!orig.head)
return
NULL;
update.gen
=
orig.gen
+
1;
update.head
=
orig.head-‐>next;
}
while
(!dcas(&orig,
&update,
s));
return
orig.head;
}
A B …slab head
166
141. free(slab
*s,
obj
*o)
{
do
{
obj
*t
=
s-‐>head;
o-‐>next
=
t;
}
while
(!cas(t,
o,
&s-‐>head));
}
148.
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
LDR
R0,
[R1,
4]
;
a
=
s-‐>head
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
B
LR
;
return
;;;;
IN
lock()
lock_loop:
;;;;
IN
allocate()
149.
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
LDR
R0,
[R1,
4]
;
a
=
s-‐>head
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
B
LR
;
return
;;;;
IN
allocate()
;;;;
IN
lock()
lock_loop:
150. LDR
R0,
[R1,
4]
;
a
=
s-‐>head
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
B
LR
;
return
;;;;
IN
allocate()
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
;;;;
IN
lock()
lock_loop:
156.
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
LDR
R0,
[R1,
4]
;
a
=
s-‐>head
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
B
LR
;
return
;;;;
IN
allocate()
;;;;
IN
lock()
lock_loop:
157.
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
LDR
R0,
[R1,
4]
;
a
=
s-‐>head
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
B
LR
;
return
;;;;
IN
allocate()
;;;;
IN
lock()
lock_loop:
163.
LDREX
R5,
[m]
;
TAS:
fetch.
.
.
STREXEQ
R5,
LOCKED,
[m]
;
TAS:
.
.
.
and
set
CMPEQ
R5,
#0
;
Did
we
succeed?
BEQ
lock_done
;
Yes:
we
are
all
done
BL
snooze
;
No:
Call
snooze()…
B
lock_loop
;
…then
loop
again
lock_done:
DMB
;
Ensure
all
previous
reads
;
have
been
completed
B
LR
;
return
;;;;
IN
unlock()
MOV
R0,
UNLOCKED
DMB
;
Ensure
all
previous
reads
have
;
been
completed
STR
R0,
LR
;;;;
IN
lock()
lock_loop:
178. “lock-free programming is
hard; let’s go ride bikes”?
• high-level performance necessitates an
understanding of low level performance
179. “lock-free programming is
hard; let’s go ride bikes”?
• high-level performance necessitates an
understanding of low level performance
• your computer is a distributed system
180. “lock-free programming is
hard; let’s go ride bikes”?
• high-level performance necessitates an
understanding of low level performance
• your computer is a distributed system
• (optional third answer: it’s real neato)
181.
182.
183.
184.
185.
186.
187. Come see us at the booth!
Nathan Taylor | nathan.dijkstracula.net | @dijkstracula
Thanks
credits, code, and additional material at
https://github.com/dijkstracula/Surge2015/