Created
February 19, 2019 17:17
-
-
Save duanenewman/6bf027fae3aab5aa85342eb34c94d9d4 to your computer and use it in GitHub Desktop.
Concurrent Reads and Locked Writes with In-Memory Objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static Random rnd = new Random(); | |
void Main() | |
{ | |
NoLocking(); | |
UsingReadUpdateLocker(); | |
UsingInstanceReadUpdateLocker(); | |
UsingInstanceReaderWriterLock(); | |
} | |
void NoLocking() | |
{ | |
try | |
{ | |
var datas = BuildList(); | |
var tasks = CreateTasks( | |
() => GetCount(datas, true), | |
() => Update(datas, true)); | |
Task.WaitAll(tasks.Where(t => { t.Start(); return true; }).ToArray()); | |
$"NoLocking passed without exception".Dump(); | |
} | |
catch (AggregateException ex) | |
{ | |
$"NoLocking exception: {Environment.NewLine}{string.Join(Environment.NewLine, ex.InnerExceptions.Select(ie => ie.Message))}".Dump(); | |
} | |
} | |
void UsingReadUpdateLocker() | |
{ | |
var locker = new ReadUpdateLocker(); | |
var datas = BuildList(); | |
var tasks = CreateTasks( | |
() => locker.DoRead(() => GetCount(datas, true)), | |
() => locker.DoUpdate(() => Update(datas, true))); | |
Task.WaitAll(tasks.Where(t => { t.Start(); return true; }).ToArray()); | |
} | |
void UsingInstanceReadUpdateLocker() | |
{ | |
var locker = new InstanceReadUpdateLocker<List<Data>>(BuildList()); | |
var tasks = CreateTasks( | |
() => locker.DoRead(d => GetCount(d, true)), | |
() => locker.DoUpdate(d => Update(d, true))); | |
Task.WaitAll(tasks.Where(t => { t.Start(); return true; }).ToArray()); | |
} | |
void UsingInstanceReaderWriterLock() | |
{ | |
var locker = new InstanceReaderWriterLock<List<Data>>(BuildList()); | |
var tasks = CreateTasks( | |
() => locker.DoRead(d => GetCount(d, true)), | |
() => locker.DoUpdate(d => Update(d, true))); | |
Task.WaitAll(tasks.Where(t => { t.Start(); return true; }).ToArray()); | |
} | |
List<Task> CreateTasks(Action readAction, Action updateAction) | |
{ | |
var tasks = new List<Task>(); | |
for (int i = 0; i < 100; i++) | |
{ | |
Task task; | |
if (rnd.NextDouble() < 0.8) | |
{ | |
task = new Task(readAction); | |
} | |
else | |
{ | |
task = new Task(updateAction); | |
} | |
tasks.Add(task); | |
} | |
return tasks; | |
} | |
List<Data> BuildList() | |
{ | |
var datas = new List<Data>(); | |
for (int i = 0; i < 100; i++) | |
{ | |
Update(datas, true); | |
} | |
return datas; | |
} | |
List<Data> GetCount(List<Data> datas, bool silent = false) | |
{ | |
Thread.Sleep(1); | |
var value = rnd.Next(10); | |
var matchingDatas = datas.Where(d => d.Value == value); | |
if (!silent) Console.WriteLine($"{matchingDatas.Count()} with value {value}"); | |
return matchingDatas.ToList(); | |
} | |
void Update(List<Data> datas, bool silent = false) | |
{ | |
Thread.Sleep(1); | |
var data =new Data() { Value = rnd.Next(10) }; | |
datas.Add(data); | |
if (!silent) Console.WriteLine($"Added {data.Value}"); | |
} | |
class Data | |
{ | |
public int Value { get; set; } | |
} | |
public class ReadUpdateLocker | |
{ | |
private readonly object UpdateLock = new object(); | |
private readonly object CounterLock = new object(); | |
private int ReadCount = 0; | |
private void IncrementReadCounter() | |
{ | |
lock (UpdateLock) | |
{ | |
Interlocked.Increment(ref ReadCount); | |
} | |
} | |
private void DecrementReadCounter() | |
{ | |
Interlocked.Decrement(ref ReadCount); | |
} | |
public void DoRead(Action readAction) | |
{ | |
try | |
{ | |
IncrementReadCounter(); | |
readAction.Invoke(); | |
} | |
finally | |
{ | |
DecrementReadCounter(); | |
} | |
} | |
public void DoUpdate(Action updateAction) | |
{ | |
lock (UpdateLock) | |
{ | |
//wait for counter to hit 0; | |
while (ReadCount > 0) | |
{ | |
Thread.Sleep(0); | |
} | |
updateAction.Invoke(); | |
} | |
} | |
} | |
public class InstanceReadUpdateLocker<T> | |
{ | |
private ReadUpdateLocker locker = new ReadUpdateLocker(); | |
private T Data { get; set; } | |
public InstanceReadUpdateLocker(T data) | |
{ | |
Data = data; | |
} | |
public T DoRead(Func<T, T> readAction) | |
{ | |
var result = default(T); | |
locker.DoRead (() => result = readAction(Data)); | |
return result; | |
} | |
public void DoUpdate(Action<T> updateAction) | |
{ | |
locker.DoUpdate (() => updateAction(Data)); | |
} | |
} | |
public class InstanceReaderWriterLock<T> | |
{ | |
private ReaderWriterLock locker = new ReaderWriterLock(); | |
private T Data { get; set; } | |
private int timeout = 500; | |
public InstanceReaderWriterLock(T data) | |
{ | |
Data = data; | |
} | |
public T DoRead(Func<T, T> readAction) | |
{ | |
try | |
{ | |
locker.AcquireReaderLock(timeout); | |
return readAction.Invoke(Data); | |
} | |
finally | |
{ | |
locker.ReleaseReaderLock(); | |
} | |
} | |
public void DoUpdate(Action<T> updateAction) | |
{ | |
try | |
{ | |
locker.AcquireWriterLock(timeout); | |
updateAction.Invoke(Data); | |
} | |
finally | |
{ | |
locker.ReleaseWriterLock(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment