26. Concurrency

Overview

Fantom tackles concurrency using a very different path from Java and C#. Java and C# use a shared memory model - all threads have access to each other's memory space. Synchronization locks are required to ensure that threads share data in a consistent manner. This concurrency model is quite powerful, but operates at a low level of abstraction. As such, even skilled programmers have a hard time writing code which is free of both race conditions and deadlocks. This model also makes it very hard to create composable systems because all system components must orchestrate their use of locking consistently.

The Fantom model of concurrency is based upon the following principles:

  • No Shared Mutable State: threads never share mutable state under any circumstances;
  • Immutability: the notion of immutability is embedded into the language itself. Immutable data can be efficiently and safely shared between threads (for example via a static field);
  • Message Passing: the actor API is built around the idea of passing messages between asynchronous workers;

Immutability

An object is said to be immutable if we can guarantee that once constructed it never changes state. Fantom supports these types of immutable objects:

By definition a const class is immutable - the compiler verifies that all the instance fields are themselves immutable and only set in the object's constructor.

Func objects are determined as mutable or immutable by the compiler depending on if the function captures mutable state from its environment. See Functions for more details.

Memory backed Buf objects can made immutable by calling the toImmutable method. The original bytes are transfered to a new readonly Buf instance.

The toImmutable method supported by List and Map is a mechanism to return a readonly, deep copy of the collection which ensures all that all values and keys are themselves immutable. The compiler will allow assignment to const List/Map fields during construction, but it implicitly makes a call to toImmutable. For example to declare a const list of strings:

// what you write
class SouthPark
{
  const static Str[] names := ["Stan", "Cartman", "Kenny"]
}

// what the compiler generates
class SouthPark
{
  const static Str[] names := ["Stan", "Cartman", "Kenny"]?.toImmutable
}

You can check if an object is immutable via the Obj.isImmutable method.

Shared Objects

Actors allow objects to be shared between threads. Specific APIs which will pass an object to another thread include:

All of these APIs use the same pattern to safely pass an object between threads. If an object is immutable then we can safely pass it to another thread by reference. Otherwise we assume the object is serializable and pass a serialized copy of the object to another thread. Both of these approaches have their pros and cons to consider in your application design:

  • immutable:
    • ideal for simple structures if fast to create
    • deeply structured objects expensive to change inside a single thread
    • always extremely efficient to pass between threads
  • serializable:
    • deeply structured objects can be modified efficiently in a single thread
    • moderately expensive to pass between threads