c# - What are the pros and cons of using closures instead of locks for shared state? -


i'm trying assess fastest solution sharing state across single-writer, single-reader scenario, reader consumes latest value of state variable assigned writer. shared state can of managed type (i.e. reference or value types).

ideally synchronization solution work fast naive non-synchronized solution possible, since method used both single-threaded , multi-threaded scenarios potentially thousands of times.

the order of read/writes shouldn't matter, long reader receives latest value within time frame (i.e. reader ever read, never modify, update timing doesn't matter long doesn't receive future value before older value...)

naive solution, no locking:

var memory = default(int); var reader = task.run(() => {     while (true)     {         func(memory);     } });  var writer = task.run(() => {     while (true)     {         memory = datetime.now.ticks;     } }); 

what issues naive solution? i've come these far:

  1. no guarantee reader sees latest value (no memory barrier/volatile)
  2. values consumed reader may invalid if type of shared variable not primitive type or reference type (e.g. composite value types).

the straightforward solution locking:

var gate = new object(); var memory = default(int); var reader = task.run(() => {     while (true)     {         int local;         lock(gate) { local = memory; }         func(local);     } });  var writer = task.run(() => {     while (true)     {         lock(gate)         {             memory = datetime.now.ticks;         }     } }); 

this of course works, incurs price of locking (~50ns) in single-threaded case, , of course price of context-switching/contention in multi-threaded case.

this totally negligible scenarios, significant in case since method used across board on potentially thousands of loops need timely possible running tens of thousands of times per second.

the last solution thought using immutable state closures read shared state:

func<int> memory = () => default(int); var reader = task.run(() => {     while (true)     {         func(memory());     } });  var writer = task.run(() => {     while (true)     {         var state = datetime.now.ticks;         memory = () => state;     } }); 

now potential issues this? own performance benchmarks report ~10ns solution vs locking in single-threaded case. seems nice gain, considerations include:

  1. still no memory barrier/volatile reader not guaranteed see latest closure (how common actually? know...)
  2. the atomicity problem solved: since closure reference type, reading/writing has guaranteed atomicity according standard
  3. boxing costs: using closure implies allocating memory on heap somehow, happening on every iteration. unclear costs of seems faster locking...

is there else i'm missing? consider use closures instead of locks in programs? great know other possible fast solutions single-reader/single-writer shared state.

both first , third examples, have pointed out, fail ensure reader task sees recent value assigned writer task. mitigating factor on x86 hardware, memory access volatile, there's nothing question restricts context x86 hardware, , in case assumes write or read wasn't optimized out jit compiler.

marc gravell has an excellent demonstration of hazards of unprotected write/read, in written value never observed reading thread. bottom line if don't explicitly synchronize access, code broken.

so, go second example, 1 that's correct.


way, far using closures wrap value goes, there's no point in that. can wrap collection of values in object directly instead of having compiler generate class you, , use object's reference shared value reader , writer. using thread.volatilewrite() , thread.volatileread() on object reference addresses cross-thread visibility issue (i'm assuming you're using captured local here…of course if shared variable field, can mark volatile).

the values can in tuple, or can write own custom class (which want make immutable, tuple, ensure against accidental bugs).

and of course, in first example, if did use volatile semantics, addresses visibility issue type int, write , read can done atomically.


Comments

Popular posts from this blog

c++ - Delete matches in OpenCV (Keypoints and descriptors) -

java - Could not locate OpenAL library -

sorting - opencl Bitonic sort with 64 bits keys -