Z: Concurrency

LANG, Z

Some rough ideas for a tiered system of concurrency in Z. No syntax, definite semantics or pretty much anything here represents a genuine design proposal. Instead, this sketches out an idea motivated by the observation that concurrency typically comes in a few, distinct fashions, and that using a single primitive (like fire-and-forget threading) is less than ideal.

There are three “tiers” of concurrency – actors, tasks and local parallelism. Local parallelism consists of iterators that have been parallelised and parallel blocks.

fun main()
  let cubes =
    parallel for i in 0 upto 5 => i ** 3
  println(cubes)
end

fun traverse (self : Tree)
  case self
    is binary(let left, let right)
      parallel
        do left.traverse()
      and
        do right.traverse()
      and
        do println(self.data)
      end
    else
      null
    end
  end case
end traverse

The parallel keyword instructs the language to optionally perform whatever code it encompasses in parallel if possible. This is meant to be a lightweight operation, and the compiler may choose to emit sequential code (though with no restrictions on ordering).

Tasks are more slightly more heavyweight, akin to either runtime-scheduled, OS-scheduled or M:N scheduled threads. Tasks may be declared within a function-like scope, and such a scope will only return once all its contained tasks have terminated.

fun main
  storage.store(5)
  println(storage.fetch())
where
  type Storage = task
    fun store(x: Int)
    fun fetch: Int
  end

  let storage: Storage = task
    let value
    accept store(x)
      value = x
    end store

    accept fetch
      return value
    end fetch
  end task
end main
        

Tasks are ran concurrently, potentially with a mechanism for data-sharing/synchronization similar to Ada's entries.

Finally, actors are isolated processes/system threads to which messages can be sent in an asynchronous, non-blocking manner and which handle those messages in a single-threaded fashion (unless some other concurrency primitive is used).

actor Counter
  fun inc(by: Int)
    set value += by
  end

  fun display
    do println(value)
  end
private
  var value: Int = 0
end

fun main
  do Counter.inc(5)
  do Counter.display()
end

Local parallelism is useful for spawning array computations or for easily doing calculations in parallel. Tasks are more suitable for situations where a semi-cooperative mode of concurrency is appropriate, and where a procedure needs to do several things in some unspecified order. Finally, actors are suitable for long-running background tasks which don't need to be updated synchronously with the rest of the program, like databases, UI threads or language servers.