Atomics
For a single shared counter or flag, a mutex is more than you need. An atomic operation reads, changes and writes a value in one step that no other thread can interrupt.
#cmdline "-mt"
#include once "fbatomic.bi"
'' Shared by four threads, with no mutex anywhere.
type Race
as int32 hits '' counted by every thread
as int32 winner '' 0 until one thread claims it
as int32 claims '' how many claims succeeded
end type
sub Runner( byval p as any ptr )
var r = cptr( Race ptr, p )
for i as int32 = 1 to 100000
FB.AtomicFetchAddLong( @r->hits, 1 ) '' one indivisible add
next
'' Store 1 only if the value is still 0; the old value says who won.
if FB.AtomicCasLong( @r->winner, 0, 1 ) = 0 then
FB.AtomicFetchAddLong( @r->claims, 1 )
end if
end sub
dim r as Race
dim runners( 0 to 3 ) as any ptr
for i as int32 = 0 to 3
runners( i ) = threadcreate( @Runner, @r )
next
for i as int32 = 0 to 3
threadwait( runners( i ) )
next
print "hits:"; FB.AtomicLoadLong( @r.hits )
print "successful claims:"; FB.AtomicLoadLong( @r.claims )
Output
hits: 400000
successful claims: 1
fbatomic.bi declares three operations on a 32-bit integer, reached through a
pointer to it:
| Call | Does | Returns |
|---|---|---|
FB.AtomicFetchAddLong( p, v ) | adds v to *p | the value before the add |
FB.AtomicCasLong( p, expected, desired ) | stores desired only if *p is expected | the value before; success is result = expected |
FB.AtomicLoadLong( p ) | reads *p | the value |
Four runners add 1 a hundred thousand times each, with no lock, and hits is
exactly 400000. Then each runner tries to change winner from 0 to 1. The
compare-and-swap succeeds for exactly one of them, because only one can find
the 0 still there; the other three see the 1 it stored, and do nothing. Which
runner wins changes from run to run, so the program prints only how many did.
AtomicLoadLong exists for its ordering, not its atomicity: it makes sure the
read sees what the other threads wrote, where an ordinary read of a variable
another thread is changing may not. Every one of the three is a full memory
barrier.
An atomic protects one variable. When an update touches two values that must stay consistent with each other, use a mutex.
Activities
- Use the value
AtomicFetchAddLongreturns to give each of the four threads a distinct number from 0 to 3, and print the sum of the numbers. - Replace the atomic add with
r->hits += 1and run the program a few times.