Concurrent system programming

The Scheme Shell provides the user with support for concurrent programming. The interface consists of several parts:

Whereas the user deals with threads and synchronization explicitly, the process state abstractions are built into the rest of the system, almost transparent for the user. Section 9.5 describes the interaction between process state and threads.

9.1  Threads

A thread can be thought of as a procedure that can run independently of and concurrent to the rest of the system. The calling procedure fires the thread up and forgets about it.

The current thread interface is completely taken from Scheme 48. This documentation is an extension of the file doc/threads.txt.

The thread structure is named threads, it has to be opened explicitly.

(spawn thunk [name])     --->     undefined         (procedure) 

Create and schedule a new thread that will execute thunk, a procedure with no arguments. Note that Scsh's spawn does not return a reference to a thread object. The optional argument name is used when printing the thread.

The new thread will not inherit the values for the process state from its parent, see the procedure fork-thread in Section 9.5 for a way to create a thread with semantics similar to process forking.

(relinquish-timeslice)     --->     undefined         (procedure) 

Let other threads run for a while.

(sleep time)     --->     undefined         (procedure) 

Puts the current thread into sleep for time milliseconds. The time at which the thread is run again may be longer of course.

(terminate-current-thread)     --->     does-not-return         (procedure) 

Kill the current thread.

Mainly for debugging purposes, there is also an interface to the internal representation of thread objects:

(current-thread)     --->     thread-object         (procedure) 

Return the object to which the current thread internally corresponds. Note that this procedure is exported by the package threads-internal only.

(thread? thing)     --->     boolean         (procedure) 

Returns true iff thing is a thread object.

(thread-name thread)     --->     name         (procedure) 

Name corresponds to the second parameter that was given to spawn when thread was created.

(thread-uid thread)     --->     integer         (procedure) 

Returns a unique identifier for the current thread.

9.2  Locks

Locks are a simple mean for mutual exclusion. They implement a concept commonly known as semaphores. Threads can obtain and release locks. If a thread tries to obtain a lock which is held by another thread, the first thread is blocked. To access the following procedures, you must open the structure locks.

(make-lock)     --->     lock         (procedure) 

Creates a lock.

(lock? thing)     --->     boolean         (procedure) 

Returns true iff thing is a lock.

(obtain-lock lock)     --->     undefined         (procedure) 

Obtain lock. Causes the thread to block if the lock is held by a thread.

(maybe-obtain-lock lock)     --->     boolean         (procedure) 

Tries to obtain lock, but returns false if the lock cannot be obtained.

(release-lock lock)     --->     boolean         (procedure) 

Releases lock. Returns true if the lock immediately got a new owner, false otherwise.

(lock-owner-uid lock)     --->     integer         (procedure) 

Returns the uid of the thread that currently holds lock or false if the lock is free.

9.3  Placeholders

Placeholers combine synchronization with value delivery. They can be thought of as special variables. After creation the value of the placeholder is undefined. If a thread tries to read the placeholders value this thread is blocked. Multiple threads are allowed to block on a single placeholder. They will continue running after another thread sets the value of the placeholder. Now all reading threads receive the value and continue executing. Setting a placeholder to two different values causes an error. The structure placeholders features the following procedures:

(make-placeholder)     --->     placeholder         (procedure) 

Creates a new placeholder.

(placeholder? thing)     --->     boolean         (procedure) 

Returns true iff thing is a placeholder.

(placeholder-set! placeholder value)     --->     undefined         (procedure) 

Sets the placeholders value to value. If the placeholder is already set to a different value an exception is risen.

(placeholder-value placeholder)     --->     value         (procedure) 

Returns the value of the placeholder. If the placeholder is yet unset, the current thread is blocked until another thread sets the value by means of placeholder-set!.

9.4  The event interface to interrupts

Scsh provides an synchronous interface to the asynchronous signals delivered by the operation system14. The key element in this system is an object called sigevent which corresponds to the single occurrence of a signal. A sigevent has two fields: the Unix signal that occurred and a pointer to the sigevent that happened or will happen. That is, events are kept in a linked list in increasing-time order. Scsh's structure sigevents provides various procedures to access this list:

(most-recent-sigevent)     --->     sigevent         (procedure) 

Returns the most recent sigevent -- the head of the sigevent list.

(sigevent? object)     --->     boolean         (procedure) 

The predicate for sigevents.

(next-sigevent pre-event type)     --->     event         (procedure) 

Returns the next sigevent of type type after sigevent pre-event. If no such event exists, the procedure blocks.

(next-sigevent-set pre-event set)     --->     event         (procedure) 

Returns the next sigevent whose type is in set after pre-event. If no such event exists, the procdure blocks.

(next-sigevent/no-wait pre-event type)     --->     event or #f         (procedure) 

Same as next-sigevent, but returns #fif no appropriate event exists.

(next-sigevent-set/no-wait set pre-event)     --->     event or #f         (procedure) 

Same as next-sigevent-set, but returns #fif no appropriate event exists.

As a small example, consider this piece of code that toggles the variable state by USR1 and USR2:

(define state #t)

(let lp ((sigevent (most-recent-sigevent)))
  (let ((next (next-sigevent sigevent interrupt/usr1)))
    (set! state #f)
    (let ((next (next-sigevent next interrupt/usr2)))
      (set! state #t)
      (lp next))))

Warning: The current version of scsh also defines asynchronous handlers for interrupts (See Section The default action of some of these handlers is to terminate the process in which case you will most likely not see an effect of the synchronous event interface described here. It is therefore recommended to disable the corresponding interrupt handler using (set-interrupt-handler interrupt #f).

9.5  Interaction between threads and process state

In Unix, a number of resources are global to the process: signal handlers, working directory, umask, environment, user and group ids. Modular programming is difficult in the context of this global state and for concurrent programming things get even worse. Section 9.4 presents how scsh turns the global, asynchronous signals handlers into modular, synchronous sigevents. Concurrent programming also benefit from sigevents as every thread may chase down the sigevent chain separately.

Scsh treats the working directory, umask, environment, and the effective user/group ID as thread-local resources. The initial value of the resources is determined by the way a thread is started: spawn assigns the initial values whereas fork-thread adopts the values of its parent. Here is a detailed description of the whole facility:

(spoon thunk)     --->     undefined         (procedure) 

This is just an alias for fork-thread suggested by Alan Bawden.

For user and group identities arbitrary changing is not possible. Therefore they remain global process state: If a thread changes one of these values, all other threads see the new value. Consequently, scsh does not provide with-uid and friends.

14 Olin's paper ``Automatic management of operation-system resources'' describes this system in detail.