Mutexes
When two threads change the same variable, updates get lost.
acct->balance += 1 is three steps (read, add, write back), and two threads
can both read the same value, both add one, and both write it: two deposits,
one recorded. A mutex makes a section of code run in one thread at a time.
#cmdline "-mt"
'' The shared total and the lock that guards it travel together.
type Account
as any ptr lock
as int64 balance
end type
sub Deposit( byval p as any ptr )
var acct = cptr( Account ptr, p )
for i as int32 = 1 to 100000
mutexlock( acct->lock ) '' one thread at a time from here...
acct->balance += 1
mutexunlock( acct->lock ) '' ...to here
next
end sub
dim acct as Account
acct.lock = mutexcreate( )
dim tellers( 0 to 3 ) as any ptr
for i as int32 = 0 to 3
tellers( i ) = threadcreate( @Deposit, @acct )
next
for i as int32 = 0 to 3
threadwait( tellers( i ) )
next
mutexdestroy( acct.lock ) '' no thread can use it any more
print "balance:"; acct.balance
Output
balance: 400000
Four tellers each deposit 1 a hundred thousand times, and the balance is
exactly 400000. Between mutexlock and mutexunlock only one thread runs, so
each += 1 happens whole. Without those two lines the balance comes out short,
by a different amount on each run, and nothing reports an error.
| Call | Does |
|---|---|
mutexcreate( ) | makes a mutex and returns its handle, an any ptr |
mutexlock( m ) | waits until no other thread holds m, then holds it |
mutexunlock( m ) | lets the next waiting thread in |
mutexdestroy( m ) | frees it, once no thread can use it any more |
Keep the lock and the data it protects together, as Account does, so every
procedure that can reach the data can reach its lock. Hold a mutex for as
little code as you can: while one thread holds it, the others wait, and four
threads taking turns on one lock run no faster than one.
Every mutexlock needs its mutexunlock on every path out, including an
early exit or return. A thread that leaves while holding the lock makes
every other thread wait for ever.
Activities
- Remove the
mutexlockandmutexunlocklines and run the program a few times. - Move the lock outside the loop, so each teller holds it for all its deposits. Is the result still right? What did the threads gain?