PPP Exercises: Persistent Threads


Demo: Threads

Tycoon supports threads, i.e. concurrent activities. Try this script:
import runtimeCore thread fmt print int real;

let random = bind(:Fun() :Int runtimeCore.libraryName()  "rtos_random" "=");

let f(n :Int) =
  begin
    let sleepingTime = int.abs(random()) % 20
    print.string(fmt.int(n) <> ": sleeping for " <> fmt.int(sleepingTime) <> "\n")
    thread.sleep(real.val(sleepingTime))
    print.string(fmt.int(n) <> ": waking up\n")
  end;

for i = 0 upto 10 do
  thread.fork(fun(self :thread.T(Ok)) f(i))
end;

Demo: Semaphores

This is a small implementation of semaphores, extracted from a much more sophisticated implementation by Andreas Piellusch that will soon be delivered with Tycoon.
import thread queue;

Let Semaphore =
Tuple
  T <:Ok
  new(:Int) :T
  wait(:T) :Ok
  post(:T) :Ok
end;

let semaphore :Semaphore =
begin
  Let T =
  Tuple
    var count   :Int
    waitqueue   :queue.T(thread.T(Ok))
  end
 
  let new(count :Int) :T = 
      tuple
        let var count  = count
        let waitqueue  = queue.new(:thread.T(Ok))
      end

  let wait(sem :T) :Ok =
    thread.atomic(fun()
      begin
        sem.count := sem.count - 1
        if sem.count < 0 then
          queue.push(sem.waitqueue thread.self())
          thread.suspend(thread.self())
        end
      end)

  let post(sem :T):Ok =
    thread.atomic(fun()
      begin
        sem.count := sem.count + 1
        if sem.count <= 0 then
          thread.run(queue.pop(sem.waitqueue))
        end
      end)

  let result :Semaphore =
  tuple
    Let T = T
    let new = new
    let wait = wait
    let post = post
  end
end;

Exercise: Producer/consumer with synchronized container

Now build a generic container 'synchronizedContainer' with a specific capacity that synchronizes via semaphores and fulfills this type:
Let SynchronizedContainer =
Tuple
  T(E <:Ok) <:Ok
  new(E <:Ok  capacity :Int) :T(E)
  put(E <:Ok  :T(E)  :E) :Ok
  get(E <:Ok  :T(E)) :E
end;
Test the container with this code:
import runtimeCore print fmt int real thread checkpoint;

let random = bind(:Fun() :Int runtimeCore.libraryName()  "rtos_random" "=");

let myContainer = synchronizedContainer.new(:Int 4);

let producer(no :Int) :Ok =
  for i = 1 upto 5 do
    let sleepingTime = int.abs(random()) % 10
    thread.sleep(real.val(sleepingTime))
    let partNo = int.abs(random()) % 100
    print.string("Producer no. " <> fmt.int(no) <> 
                 " produced part no. " <> fmt.int(partNo) <> "\n")
    synchronizedContainer.put(myContainer partNo)
  end;

let consumer(no :Int) :Ok =
  for i = 1 upto 5 do
    let sleepingTime = int.abs(random()) % 10
    thread.sleep(real.val(sleepingTime))
    let partNo = synchronizedContainer.get(myContainer)
    print.string("Consumer no. " <> fmt.int(no) <> 
                 " consumed part no. " <> fmt.int(partNo) <> "\n")
  end;

for i = 1 upto 5 do
  thread.fork(fun(self :thread.T(Ok)) producer(i))
  thread.fork(fun(self :thread.T(Ok)) consumer(i))
end;

Exercise: Persistent Timer Thread

Implement a thread that runs forever in parallel to the toplevel and reminds you of the actual time. The thread should be named 'timeThread', so that he can be killed by 'thread.kill'. He should depend on two mutable global variables 'showTime :Bool' (that tells the thread if he should stay silent) and 'sleepingTime :Real' that determines the interval between his messages:
let var showTime = true;
let var sleepingTime = 10.0;
let timeThread = 
  ...
sleepingTime := 1.0;
do saveSystem;
do exit;
tycoon -restart
showTime := false;
thread.kill(timeThread);
showTime := true;

Gerald Schröder, 1996