Concurrent builds upon the Fantom's core concurrent library and provides a collection of utility classes for sharing data in and between threads.
Install Concurrent
with the Fantom Pod Manager ( FPM ):
C:\> fpm install afConcurrent
Or install Concurrent
with fanr:
C:\> fanr install -r http://eggbox.fantomfactory.org/fanr/ afConcurrent
To use in a Fantom project, add a dependency to build.fan
:
depends = ["sys 1.0", ..., "afConcurrent 1.0"]
Full API & fandocs are available on the Eggbox - the Fantom Pod Repository.
The Concurrent
library provides strategies for sharing data between threads:
The afConcurrent::Synchronized class provides synchronized serial access to a block of code, akin to Java's synchronized
keyword. It can be used as a mechanism for exclusive locking. For example:
class Example {
Synchronized lock := Synchronized(ActorPool())
Void main() {
lock.synchronized |->| {
// ...
// put important thread safe code here
// ...
}
}
}
Synchronized
works by calling the thread safe function from within the receive()
method of an Actor, which has some important implications:
- The passed in function needs to be an immutable func.
- Any object returned also has to be immutable.
Alien-Factory's Concurrent library uses this synchronized construct to supply the following useful classes:
- afConcurrent::SynchronizedState
- afConcurrent::SynchronizedList
- afConcurrent::SynchronizedMap
- afConcurrent::SynchronizedFileMap
- afConcurrent::SynchronizedBuf
All Synchronized classes are const
, mutable, and may be shared between threads.
See the individual class documentation for more details.
Atomic Lists and Maps are backed by an object held in an AtomicRef
. They do not perform any processing in a separate thread, hence are more lightweight than their synchronized counterparts.
But write operations make a copy the backing object before appling changes, and are not synchronized. This means they are susceptible to data-loss during race conditions between multiple threads. If used for caching situations where values may be calcuated on the fly, then this may be acceptable.
See:
The atomic classes are also available in Javascript.
Local Refs, Lists and Maps do not share data between threads, in fact, quite the opposite!
They wrap data stored in Actor.locals()
thereby constraining it to only be accessed by the executing thread. The data is said to be local to that thread.
The problem is that data held in Actor.locals()
is susceptible to being overwritten due to name clashes. Consider:
class Drink {
Str beer {
get { Actor.locals["beer"] }
set { Actor.locals["beer"] = it }
}
}
man := Drink()
man.beer = "Ale"
kid := Drink()
kid.beer = "Ginger Ale"
echo(man.beer) // --> Ginger Ale (WRONG!)
echo(kid.beer) // --> Ginger Ale
To prevent this, afConcurrent::LocalRef creates a unique qualified name to store the data under:
class Drink {
LocalRef beer := LocalRef("beer")
}
man := Drink()
man.beer.val = "Ale"
kid := Drink()
kid.beer.val = "Ginger Ale"
echo(man.beer.val) // --> Ale
echo(kid.beer.val) // --> Ginger Ale
echo(man.beer.qname) // --> 0001.beer
echo(kid.beer.qname) // --> 0002.beer
While LocalRefs
are not too exciting on their own, BedSheet and IoC use them to keep track of data to be cleaned up at the end of HTTP web requests.
See:
LocalRef
is also available in Javascript (as from Fantom 1.0.68) but LocalList
and LocalMap
are blocked on js: Func.toImmutable not implemented.
When Concurrent is added as a dependency to an IoC enabled application, such as BedSheet or Reflux, then the following services are automatically made available to IoC:
- afConcurrent::ActorPools - takes contributions of
Str:ActorPool
- afConcurrent::LocalRefManager
A DependencyProvider
then allows you to inject instances of:
-
LocalRefs
-
LocalLists
-
LocalMaps
@Inject { type=[Str:Slot?]# } const LocalMap localMap
Another provider allows you to name an ActorPool
and inject instances of:
-
ActorPool
-
Synchronized
-
SynchronizedList
-
SynchronizedMap
-
SynchronizedState
@Inject { id="<actorPool.id>" type=[Str:Slot?]# } const SynchronizedMap syncMap
All the above makes use of the non-invasive module feature of IoC 3.