THREADS

A program normally does one thing at a time, line after line. A thread lets it do several: a capsule runs on a thread of its own, beside the rest of your program, and your program collects its answer when it is ready. On a machine with several cores, threads really do run at the same moment.

Making, starting and joining a thread

A thread is held in a satellite.variable.thread, and it takes three steps:

  1. satellite.thread.new(my_capsule(arguments)) makes the thread. The arguments are worked out now, but the capsule does not begin yet.
  2. t.start() sets the capsule running on its own thread and carries straight on to your next line.
  3. t.join() waits until the capsule is done, and answers what it handed back. t.wait() is a second name for the same thing.

What goes inside satellite.thread.new must be a call of one of your own capsules: satellite.thread.new(5) is refused before the program starts, with S721. Displaying a thread shows which capsule it runs and how far it has got.

square.satlSATELLITE 004 Download
One thread, from start to answer
satellite.include(satellite)

satellite.capsule square(satellite.variable.number n)
{
    satellite.return(n * n)
}

satellite.capsule satellite.main()
{
    satellite.variable.thread t = satellite.thread.new(square(12))
    satellite.console.display(t)
    t.start()
    satellite.variable.number answer = t.join()
    satellite.console.display(answer)
    satellite.console.display(t)
    satellite.return(satellite)
}
OUTPUT
(thread square, not started)
144
(thread square, finished)

A thread runs once. Joining it again answers the same value again, with a one-line notice that it was already joined, but starting it a second time is refused with S722. To run the capsule again, make a new thread.

Sharing out the work

Threads earn their keep when a job splits into parts that do not depend on each other. Here four threads each add up a quarter of the numbers from 1 to 1000, and the program adds their four answers together:

four_sums.satlSATELLITE 004 Download
Four threads, one sum
satellite.include(satellite)

satellite.capsule add_up(satellite.variable.number from, satellite.variable.number to)
{
    satellite.variable.number total = 0
    satellite.statement.for(satellite.variable.number i = from; i <= to; i + 1)
    {
        total = total + i
    }
    satellite.return(total)
}

satellite.capsule satellite.main()
{
    satellite.variable.thread first = satellite.thread.new(add_up(1, 250))
    satellite.variable.thread second = satellite.thread.new(add_up(251, 500))
    satellite.variable.thread third = satellite.thread.new(add_up(501, 750))
    satellite.variable.thread fourth = satellite.thread.new(add_up(751, 1000))
    first.start()
    second.start()
    third.start()
    fourth.start()
    satellite.variable.number sum = first.join() + second.join() + third.wait() + fourth.wait()
    satellite.console.display(sum)
    satellite.console.display(add_up(1, 1000))
    satellite.return(satellite)
}
OUTPUT
500500
500500

All four threads are started before any is joined, so they run at the same time. The last line runs add_up the ordinary way, on one thread, to check the answer.

The four answers can be added in any order, which is what makes this work. When threads display lines of their own, every line comes out whole, but which thread’s line comes first is up to the machine. Join a thread before you display what it found, and your output stays in order.

Stopping a thread

t.stop() asks a thread to stop at its next statement. It never cuts a statement off in the middle. Join it afterwards to wait until it has really stopped:

spinner.satlSATELLITE 004 Download
Stop a thread that would never end by itself
satellite.include(satellite)

satellite.capsule spin()
{
    satellite.variable.number turns = 0
    satellite.statement.while(turns >= 0)
    {
        turns = turns + 1
    }
}

satellite.capsule satellite.main()
{
    satellite.variable.thread spinner = satellite.thread.new(spin())
    spinner.start()
    satellite.console.display(spinner)
    spinner.stop()
    spinner.join()
    satellite.console.display(spinner)
    satellite.return(satellite)
}
OUTPUT
(thread spin, running)
(thread spin, stopped)

You do not have to stop every thread yourself. When your program reaches satellite.return(satellite), satellite asks every thread still running to stop, and waits for it.

When a thread goes wrong

Joining a thread that was never started is refused, because there would be nothing to wait for:

too_soon.satlSATELLITE 004 Download
join before start
satellite.include(satellite)

satellite.capsule square(satellite.variable.number n)
{
    satellite.return(n * n)
}

satellite.capsule satellite.main()
{
    satellite.variable.thread t = satellite.thread.new(square(3))
    satellite.console.display(t.join())
    satellite.return(satellite)
}
OUTPUT
--------------------------------------------------------------------------------
SATELLITE CRITICAL ERROR REPORT
--------------------------------------------------------------------------------
S723: JOIN_BEFORE_START
satl(run): t.join() -- square was never started, so there is nothing to wait
for; t.start() comes first

directory: too_soon.satl:11
syntax: satellite.console.display(t.join())
        /\

join() and wait() wait for a thread that is running, and this one has not been
started -- start() comes first.
machine code 58 thread_not_started -- satl exits with this.

--------------------------------------------------------------------------------

If a capsule fails on its thread, for example by dividing by zero, the report shows the line on the thread where it happened. Joining that thread then stops your program with the same code. A thread that fails and is never joined still fails the whole run.

What threads share

Numbers, strings and lists are values: each thread gets its own copy, so one thread changing its copy never surprises another. An object made from a spacesuit, or a file, is different. Hand one to a thread and both sides hold the same object. That is useful, and it needs care when two threads change it at once, which is what LOCKS are for. Windows stay with the main thread: a window word used on a thread is refused with S727.

Where next

Every thread word is in the FULL REFERENCE. Next, read LOCKS: SHARING AN OBJECT BETWEEN THREADS, and for the objects themselves, SPACESUITS.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *