Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

CallDoesReturns
FB.AtomicFetchAddLong( p, v )adds v to *pthe value before the add
FB.AtomicCasLong( p, expected, desired )stores desired only if *p is expectedthe value before; success is result = expected
FB.AtomicLoadLong( p )reads *pthe 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

  1. Use the value AtomicFetchAddLong returns to give each of the four threads a distinct number from 0 to 3, and print the sum of the numbers.
  2. Replace the atomic add with r->hits += 1 and run the program a few times.