How do I protect shared data from concurrent access in EK9?
← Concurrency · Ref: Q158
EK9 uses MutexLock of T to protect shared data from concurrent access. The canonical pattern is to hold the MutexLock as a FIELD on a wrapping class, and expose access through methods that internally use MutexKey callbacks. The lock itself never escapes the wrapping class — it has a stable field-rooted home that the compiler tracks for data-race and deadlock detection.
FIELD-ON-CLASS PATTERN
Declare the MutexLock as a field with an inline initialiser:
Counter
lockedCount as MutexLock of Integer: MutexLock(0)
The field initialiser is the only legal MutexLock construction site (E08254/E08256 reject locals and collections of locks).
MUTEXKEY CALLBACK PATTERN
Inside each method that needs to read or update the protected value, declare a dynamic MutexKey function. The function body is the critical section; lockedItem is the protected value while the lock is held:
read()
<- rtn as Integer: Integer()
accessKey <- (rtn) is MutexKey of Integer as function
rtn :=: lockedItem
require lockedCount.enter(accessKey)
When the callback returns, the lock is released automatically. This prevents forgetting to unlock.
ENTER VS TRYENTER
enter() blocks until the lock is available. tryEnter() attempts to acquire the lock and returns immediately with a Boolean indicating success:
blocking <- lockedCount.enter(keyFunction) attempt <- lockedCount.tryEnter(keyFunction)
COPY NOT REASSIGN
Inside a MutexKey callback, use the copy operator :=: to update lockedItem, not regular assignment :=. Reassignment would leave an outside reference to the original value, bypassing the lock.
WHY FIELD-ON-CLASS
EK9's compile-time data-race detection needs every lock to have a stable field-rooted home. Locks declared as locals, returned from functions, or held inside collections defeat the static analysis. Wrapping the lock in a class also keeps the API surface small — callers see only the protected operations.
See Q117 for singleton and DI for shared components. See Q159 for parallel processing. See Q160 for the concurrency model. See Q213 for MutexLock design pattern.
Example
defines module qa.concurrency.mutex defines class //Canonical pattern: MutexLock as a field on a wrapping class. //The class exposes protected operations as methods; the lock //never escapes. Counter lockedCount as MutexLock of Integer: MutexLock(0) read() <- rtn as Integer: Integer() accessKey <- (rtn) is MutexKey of Integer as function rtn :=: lockedItem require lockedCount.enter(accessKey) increment() -> amount as Integer accessKey <- (amount) is MutexKey of Integer as function lockedItem :=: lockedItem + amount require lockedCount.enter(accessKey) default operator ? defines program MutexLockDemo() stdout <- Stdout() //Construct the wrapper — the MutexLock is created by the //class's field initialiser, not here in user code. counter <- Counter() stdout.println(`Initial count: ${counter.read()}`) counter.increment(5) stdout.println(`After +5: ${counter.read()}`) counter.increment(10) stdout.println(`After +10: ${counter.read()}`)
Common mistakes
E08256 — MutexLock must be declared as a field on a class with an inline initialiser. Constructing one as a local (here `strayLock <- MutexLock(0)`) is rejected at SYMBOL_DEFINITION (E08256). The lock must have a stable field-rooted home so the compiler can track its identity for data-race and deadlock analysis.
Incorrect:
counter <- Counter() strayLock <- MutexLock(0) assert strayLock?
Correct:
counter <- Counter()
E08254 — MutexLock cannot appear as a type argument to another generic (here `List of MutexLock`). Use one MutexLock per shared payload, held as a field, rather than a collection of locks. See ek9 -h E08254 for details.
Incorrect:
counter <- Counter() badLocks <- List() of MutexLock of Integer assert badLocks?
Correct:
counter <- Counter()
Other ways to ask this
- How do I use MutexLock in EK9?
- What is thread-safe data access in EK9?
- How does MutexLock protect shared state in EK9?
Coming from another language?
Java: synchronized blocks or ReentrantLock with try/finally. Python: threading.Lock with context manager. Rust: Mutex<T> with lock() returning a guard. Go: sync.Mutex with Lock()/Unlock(). EK9: MutexLock of T held as a class field, accessed via MutexKey callback inside class methods. The field-rooted home enables compile-time data-race and deadlock detection that other languages lack.
Keywords: mutexlock, mutex, critical, thread, lock, protect, class, concurrent, section, safety, parallel, field, shared