Reputation: 8435
Let's say I have a list of integers in the system:
let mutable data: int list = [1; 2; 3; 4; 5]
which is updated (by adding an element) by relatively few producers and consumed by lots of consumers.
Note: it's ok if consumers receive slightly outdated data.
What would be a proper way to synchronize access to this variable?
A) The safe approach would be to wrap this variable into agent and access it via serialized messages, we wouldn't even need mutable
modifier here. But this approach seems to be sub-optimal, because it would unnecessarily make all read accesses synchronized.
B) AFAIK reference assignment is atomic in .NET, so between one publisher and all consumers a simple assignment should suffice:
Publisher: data <- newItem :: data
Consumer: data |> process
So just simple locking between publishers would be enough to wrap up this workflow?
let monitor = object()
Publisher: lock monitor (fun () -> data <- newItem::data)
Am I right with my assumptions? Which approach is preferred and would be more idiomatic for F#? Any better options?
Upvotes: 5
Views: 623
Reputation: 91
If your shared data is immutable you can do this safely:
let monitor = new System.Object()
let data : int list ref = ref List.empty
let modData (modFun : int list -> int list) =
lock (monitor) (fun _ -> data := modFun !data)
You can read the data when ever you like without locking. Data is immutable so it cannot be "between states". If you need to read and write atomically, you can do it in the "modFun".
Usage example:
modData (function |(head::tail) -> tail |_ -> List.Empty) //Remove one
modData (fun thelist -> 1::thelist) //Add number 1 to head
Upvotes: 0
Reputation: 564631
You could use Interlocked.CompareExchange to handle publishing without explicitly locking:
let mutable data = [1;2;3;4;5]
let newValue = 0
// To publish:
let mutable tmp = data;
while not(tmp.Equals(Interlocked.CompareExchange(&data, newValue::data, tmp))) do
tmp <- data
This would likely provide a small benefit if you have synchronized writers.
If you decide you do want consumers to always have the latest data, a ReaderWriterLockSlim
would allow you to completely synchronize the data without forcing reads to block on each call.
That could look something like:
let mutable data = [1;2;3;4;5]
let rwl = ReaderWriterLockSlim()
let newValue = 0
// To publish:
let publish newValue =
rwl.EnterWriteLock()
try
data <- newValue :: data
finally
rwl.ExitWriteLock()
// To read:
let readCurrent =
rwl.EnterReadLock()
try
data
finally
rwl.ExitReadLock()
Upvotes: 9