Вопрос

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?

Это было полезно?

Решение

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()

Другие советы

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top