How do I protect shared data with MutexLock in EK9?
← Design Patterns and Idioms · Ref: Q213
MutexLock of T wraps a value and controls access through MutexKey callback functions. The canonical EK9 pattern is to hold the lock as a FIELD on a wrapping class and expose the protected operations as class methods. The lock never escapes the wrapping class.
FIELD-ON-CLASS PATTERN
Declare the MutexLock as a field with an inline initialiser:
Inventory
lockedStock as MutexLock of List of String: MutexLock(List() of String)
The field initialiser is the only legal MutexLock construction site (E08254/E08256 reject locals and collections of locks).
MUTEXKEY CALLBACK
Inside each method on the wrapping class, declare a dynamic MutexKey function. The function body is the critical section; lockedItem is the protected value:
add()
-> item as String
accessKey <- (item) is MutexKey of List of String as function
lockedItem += item
require lockedStock.enter(accessKey)
ENTER THE LOCK
Call enter() (blocking) or tryEnter() (non-blocking) inside the class's own method. The lock releases automatically when the callback returns.
COPY DATA OUT
To expose a snapshot of the protected value, copy it out with :=: into a return-target captured in the callback:
read()
<- rtn as List of String: List() of String
accessKey <- (rtn) is MutexKey of List of String as function
rtn :=: lockedItem
require lockedStock.enter(accessKey)
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 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 Q158 for MutexLock basics. See Q159 for parallel processing. See Q52 for dynamic functions. See Q267 for anti-patterns including mutable shared state.
Example
defines module qa.patterns.mutex defines class //Canonical pattern: MutexLock as a field on a wrapping class. //The class methods are the only API to the protected stock list; //the lock itself never escapes. Inventory lockedStock as MutexLock of List of String: MutexLock(List() of String) add() -> item as String accessKey <- (item) is MutexKey of List of String as function lockedItem += item require lockedStock.enter(accessKey) read() <- rtn as List of String: List() of String accessKey <- (rtn) is MutexKey of List of String as function rtn :=: lockedItem require lockedStock.enter(accessKey) default operator ? defines program MutexPatternDemo() stdout <- Stdout() //Construct the wrapper — the MutexLock is created by the //class's field initialiser, not here in user code. inventory <- Inventory() inventory.add("widgets") inventory.add("sprockets") snapshot <- inventory.read() stdout.println(`Inventory snapshot: ${$snapshot}`)
Common mistakes
E08256 — MutexLock must be declared as a field on a class with an inline initialiser. The field-rooted home is required for compile-time lock-identity tracking. See ek9 -h E08256 for details.
Incorrect:
lock <- MutexLock(List() of String) //local — rejected
Correct:
Inventory
lockedStock as MutexLock of List of String: MutexLock(List() of String)
Other ways to ask this
- How do I use MutexLock with a callback in EK9?
- What is the MutexKey pattern in EK9?
- How do I safely update locked data in EK9?
Coming from another language?
Java: synchronized blocks or ReentrantLock with try/finally. Python: threading.Lock with context manager. Rust: Mutex<T>::lock() returns MutexGuard. Go: sync.Mutex with Lock()/Unlock(). EK9: MutexLock as a class field, accessed through MutexKey callbacks inside class methods. The field-rooted home enables compile-time data-race and deadlock detection.
Keywords: enter, pattern, shared, lock, class, protect, safety, field, thread, idiom, key, mutex, concurrent, design