Ocamlunix book: Ocaml systhreads

Hi All,

I was going through the threads chapter in ocamlunix book (Threads). In it it mentions that thread Thread.t can execute in parallel to each other. Quoted below:

But unlike coroutines, which pass control explicitly from one to another and cannot execute in parallel, threads can execute in parallel and can be scheduled preemptively by the system. From this viewpoint, threads resemble processes.

To be more precise should this be changed to mention concurrency rather than parallel? I think concurrency was the right term even before multicore ocaml but maybe not so much now since Thread.t in different domains do run in parallel. However, AIUI Thread.ts in the same domain run concurrently not in parallel to each other.

Also, if Thread.ts run concurrently in the same domain, do we really need concurrency mechanism such as Mutex.t for shared data? Wouldn’t this be the same model as Lwt.t where shared data is not required to be synchronised?

1 Like

Two quick things:

  • threads can run in parallel if one of them is inside a C function that releases the runtime lock
  • you still need mutex, because preemption can happen anywhere, even inside the middle of some update. With lwt you control where threads can be interrupted so it’s safer.
1 Like

The text you point to explains the general concept of threads, independently of the way it is implemented in OCaml, so I believe the exposition is fine as it is. Furthermore, the peculiarity of the runtime lock is clarified a bit later in the same chapter (“Sequentialization of OCaml code”), and as mentioned there, the fact that threads do not actually run in parallel in the (old) OCaml runtime is not something that can be depended on as the interleaving can happen basically anywhere (unlike, say, in Lwt where the interleaving can occur only at bind points).

Yes, because of the point mentioned above: the interleaving of different threads can occur virtually at any point, not necessarily at a “safe” point for shared data, so you still need these primitives to enforce threads not to access the shared resources at a random (potentially unsafe) point during execution.

Cheers,
Nicolas

1 Like

@nojb Thanks for the details reply. I just tried a simple ocaml program below to try to understand OCaml systhreads behavior a bit better,

let total = ref 100
let withdraw amt = total:= !total - amt
let deposit amt = total := !total + amt
let bank_txn () =
  let current_t = Thread.self () in
  let amt = Random.int 100 in
  withdraw amt;
  Printf.printf "w(%d):%d," (Thread.id current_t) amt;
  if Random.bool () then Thread.yield () else ();
  Printf.printf "d(%d):%d " (Thread.id current_t) amt;
  deposit amt

let[@tailrec_mod_constr] rec spawn_threads n_threads =
  if n_threads = 0 then []
  else
    let t = Thread.create bank_txn () in
    t :: spawn_threads (n_threads - 1)

let () =
  Printf.printf "Before total: %d\n" !total;
  Printf.printf "Executing: ";
  let threads = spawn_threads 1_000 in
  List.iter Thread.join threads;
  Printf.printf "\nAfter total: %d\n" !total;

However, the Before total: 100 and After total: 100 always seem to correct. I can see that the spawn threads do interleave in a non deterministic manner as you suggested. However, my intuition tells me that because withdraw and deposit happens non atomically, the final total != 100. However, I can’t seem to produce that result. Am I missing something?

B.

And that makes sense, right? At the end of your program you have executed withdraw amt and deposit amt exactly 1000 times (interleaved, but it doesn’t matter), so the number in total stays the same.

Cheers,
Nicolas

In the runtime < 5.0.0 the assignments in withdraw and deposit always execute atomically because there’s no opportunity for another thread to be rescheduled between the time you access total and the time you assign it (and no signal can occur because the assignements do not allocate). See this explanation and this other one in ocamlunix.