Concurrency
A program that does one thing at a time leaves most of the machine idle. With concurrency, several pieces of work run at once: on different cores, or while another piece waits for a disk, a network or a timer.
#import "std/tasks"
#cmdline "-mt"
'' Add up one slice of the range and send the answer back.
sub SumRange( byval lo as int64, byval hi as int64, _
byval results as tasks.SendChan( of int64 ) )
dim as int64 total = 0
for i as int64 = lo to hi
total += i
next
results.Send( total )
end sub
'' Four goroutines, a quarter of the work each.
var results = tasks.Channel( of int64 )( 4 )
for part as int32 = 0 to 3
spawn SumRange( part * 250000 + 1, ( part + 1 ) * 250000, results )
next
'' The parts arrive in any order; their sum does not depend on it.
dim as int64 total = 0, partial
for part as int32 = 1 to 4
results.Receive( partial )
total += partial
next
print "sum of 1 to 1000000:"; total
Output
sum of 1 to 1000000: 500000500000
spawn SumRange( ... ) starts a goroutine: the call runs alongside the rest
of the program, and spawn returns at once. A goroutine has no return value,
so each one sends its partial sum down a channel, and the main program
receives four of them. They can arrive in any order, which is why the program
adds them up rather than printing each one.
A program that uses goroutines needs two lines at its top:
#import "std/tasks"
#cmdline "-mt"
std/tasks holds goroutines, channels and the tools for coordinating them.
-mt builds the program with the runtime that is safe to call from several
threads at once. You have to ask for it yourself: std/tasks refuses to
compile without it, with
TKO0309: #error: "std/tasks needs the multithreaded runtime: build with -mt".
A program that starts threads of its own, without std/tasks,
gets the thread-safe runtime without asking. When the compiler sees a call
to threadcreate, it links that runtime by itself.
Mutexes and condition variables alone do not count: a program with no second
thread does not need the thread-safe runtime. Writing #cmdline "-mt" in such
a program does no harm, and the examples in this section all carry it.
The section has two levels:
| Level | Pages |
|---|---|
Goroutines and channels, from std/tasks | spawn, channels, select, worker pool, cancellation |
| Operating-system threads and shared memory | threads, mutexes, condition variables, atomics |
Reach for goroutines and channels first: values move between them by copy, and the order of events is visible in the code. Threads and locks are the foundation underneath, for the cases that need them.
Activities
- Split the work into eight goroutines instead of four.
- Print each partial sum as it arrives, run the program a few times, and watch the order change while the total does not.