Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Threads and mutable data

We have just seen how channels can be used to pass data between threads. This is the preferred means of thread communication in Fuzion. However, there are situations where mutable shared memory is better suited. This cannot be done in Fuzion using explicit synchronization, which will be explained in this section.

Defining a shared memory data structure

Fuzion does not permit direct mutation of fields or array elements. If it is desired to change data, this can only be done using a mutate effect.

To illustrate this, we define a simple counter feature and use it to count the number of integers divisible by 7 in a given interval:

Single threaded case

The previous example defines my_mutate as a child of mutate, which is the Fuzion base library's standard effect for mutation. This effect does not permit being used by concurrent threads, since it does not ensure exclusive access via locking or similar mechanisms.

Multiple threads using mutate

Now we might want to speed this code up by running 10 parallel threads as follows:

However, this results in an error since mutate permits to be used by a single thread only since it does not do anything to ensure consistency of shared data. We need a more powerful version of mutate in this case.

blocking_mutate

For multi-threaded use, the concur.blocking_mutate effect has been added, so we must use this for out example using threads:

But, unfortunately, this still results in an error since we now have racy accesses to field counter.val. We have to make sure that these accesses are exclusive by the respective threads. This is done using a call to the mutate effect's exclusive operation.

For our small counter example, this means that we must make the code val <- val+1 exclusive since otherwise concurrent threads T1 and T2 may read the same value, e.g., 42 from val, and both write the incremented value 43 back. The situation could even be worse: If T1 was preempted by some unrelated thread T3 just after reading 42 such that T2 can perform many calls to increment before T3 yields the CPU back to T1, which will then write a fully outdated 43 to the counter, undoing all the increments performed by T2 meanwhile.

Wrapping the code in counter.increment with M.env.exlusive makes sure at any time, only one thread may read and modify val, avoiding the described inconsistencies. The disadvantage is that a thread may get blocked waiting for another thread to finish execution of the exclusive code section.

Note that feature counter.read also requires synchronization, but for a more subtle reason: Memory accesses might be re-ordered at different levels (within the CPU, the processor cache hierarchy, or even in the code generated by the compiler) to increase performance. This is permitted as long as there is no visible change in single-thread behavior. Here, this means that read may return an outdated value if changes to the counter were made by other threads only. Making read exclusive introduces fences that prohibit the re-use of such outdated values.

So, here we finally have a working multi-threaded version of our example:

Sharing code for single- and multi-threaded use

The single-threaded mutate that was used in the original, single-threaded example provides features like exclusive as well, even though, as we have seen, multi-threaded accesses are not permitted. The implementation just provides essentially empty implementations of exclusive and related features. This permits the use of data structures that were developed for multi-thread uses with proper exclusive sections to be used in a single-thread using mutate without invoking the synchronization overhead that blocking_mutate entails.

last changed: 2026-06-25