Sequential Composition and Explicit Thread Creation


Sequential Composition

The major change from Oz 1 to Oz 2 is that Oz 2 is based on sequential composition while Oz 1 is based on concurrent composition. Oz1 creates new threads automatically when needed. Oz 2 creates new threads only when this is explicitly requested with the statement

thread Statement end

Sequential composition and explicit thread creation lead to a different concurrent programming style. To port a concurrent Oz 1 program to Oz 2, it will be necessary to insert thread ... end at the right places. To do this, you will have to understand the concurrent structure of your program.

We changed to sequential composition because it makes programming and debugging substantially easier.

Objects and Locks

Since Oz 2 has sequential composition, it also has first-class locks to synchronize data structures that are accessed by more than one thread.

The change to sequential composition led to a redesign of the object system. While in Oz 1 objects are always synchronized, they must be synchronized explicitly with locks in Oz 2. Sequential object-oriented programming in Oz 2 is easier than in Oz 1 since it is impossible to create a deadlock. Concurrent object-oriented programming in Oz 2 requires the explicit use of locks. Locks provide more flexibility than the built-in object synchronization of Oz 1.

Implicit Thread Creation

That thread creation is always explicit in Oz 2 (i.e., caused by thread ... end) is not completely true. There are two simple exceptions:

  1. Every statement that is fed through the programming interface is automatically enclosed into thread ... end. Thus statements fed through the programming interface run concurrently.
  2. The finite domain propagators always run concurrently.

Terminology Change

We have changed our terminology. In Oz 2 we call a statement what is called an expression in Oz 1, and we call an expression what is called a term in Oz 1.

Oz 1 Oz 2
expression becomes statement
term becomes expression

Example

Let's discuss the difference between Oz 1 and Oz 2 with an example. Consider the statement

local X=2 Y=3 Z U in
   U = Z*Z
   Z = X+Y
   {Browse Z}
end

In Oz 1 this statement will display 25 in the browser. It will do so by creating a new thread for the statement U = Z*Z.

In Oz 2 this statement will not display anything. The executing thread will simply block for ever at the statement U = Z*Z because Z is not determined.

To make the statement work in Oz 2, we can rewrite it as follows:

local X=2 Y=3 Z U in
   thread U = Z*Z end
   Z = X+Y
   {Browse Z}
end

Reduction Strategy

Oz 1's reduction strategy cannot be expressed in Oz 2. The reason is that

thread Statement end

will create a new thread no matter whether it is needed or not. It turns out that the automatic thread creation of Oz 1 is not needed.

There is no direct translation of Oz 2 programs into Oz 1 programs. One can try to simulate sequential composition with data flow synchronization, but this may require many auxiliary variables and nonlocal program transformations. For instance, procedures will need an extra argument so that they can signal that they have done their job completely.

Example

Here is another example for rewriting an Oz 1 program into an Oz 2 program:

fun {Map Xs F}
   case Xs
   of nil then nil
   [] X|Xr then {F X} | {Map Xr F}
   end
end

In Oz 1, Map is automatically concurrent. For instance,

{Browse {Map [1 2 _ 3 4] fun {$ X} X+1 end}}

will show [2 3 _ 4 5] in the browser. In Oz 2 you will see nothing since Map will block on the undetermined element of the list and hence Browse will not be applied. However, it is easy to write a concurrent Map in Oz 2:

fun {Map Xs F}
   case Xs
   of nil then nil
   [] X|Xr then thread {F X} end | {Map Xr F}
   end
end

Gert Smolka