Parallelism

Simple parallelism

The simplest type of parallelism is a parallel for-loop.
This returns results in the same order as the inputs.

import keen/parallel main void() inputs nat[] = 1, 2, 3 results nat[] = for x in parallel inputs x * 10 log results

Mutability

The parallel for loop takes a shared lambda as its argument.
That means you can't access mut values in the body of the loop.

import keen/parallel main void() inputs nat[] = 1, 2, 3 res nat mut[] = () for x in parallel inputs res ~= x # Not allowed log res

You can use a shared type though:

import keen/col/shared-array keen/parallel main void() inputs nat[] = 1, 2, 3 res nat shared[] = () for x in parallel inputs res ~= x log res.move-to::nat[]

Shared collections

A t shared-array is written as t shared[].
This supports a subset of the functions available for mut-arrays.

Like all shared collections, it does not define iterate. Access to a shared collection needs to quickly do just one thing before releasing the collection for other users, so it can't do bulk operations that access every element.

You can, however, use move-to to take the entire collection contents out to a new collection, leaving the original shared-array empty.

There is also (key, value) shared-map, written as value shared[key].

import keen/col/shared-map keen/parallel main void() inputs nat[] = 1, 2, 3 res nat shared[nat] = () for x in parallel inputs res[x] := x * 10 log res.move-to::nat[nat]

Shared lambdas and exclusions

A shared expression makes any lambda shared even if it accesses mutable variables.
To do so, just prefix the lambda with the shared keyword.

import keen/parallel main void() accum mut nat = 0 f void shared(x nat) = shared of x accum +:= x for x in parallel 0 .. 100 f[x] log accum

This works by using an exclusion. All Keen code runs in some exclusion; main starts with a default one.
The exclusion is a lock shared by all mut variables in the same exclusion. So it's coarse-grained parallelism.
Each new thread launched by a parallel for loop gets a new exclusion.

Exclusions ensure that mut types are safe to use.
When you use shared on a lambda, it creates a lambda that locks the exclusion before it can be entered. That way the lambda can have only one caller at a time, meaining accum will only be accessed by one thread at a time.

Below, f and g will never run at the same time, because they share the same exclusion.

import keen/parallel main void() accum mut nat = 0 f void shared(x nat) = shared of x accum +:= x g void shared(x nat) = shared of x accum -:= x for x in parallel 0 .. 100 f[x] g[x] log accum

Safe parallelism

When a safe function returns, any threads that it launched should have been waited on.
A parallel for loop satisfies this.
When calling Java and JS functions, don't mark the call as trusted unless it satisfies this property.