'Signal and Wait in C#
In the below application there are two parties that are calling ChannelReservationCache to fetch or add information.
I want to use the "signal and wait" thing in my ChannelReservationCache class so that in case ChannelReservationCache.AddChannelState() is adding the cache, parallelly if the WebApi call hits the ChannelReservationCache.GetChannel() then the GetChannel() should wait for the execution of AddChannelState() and vice-versa.
How this can be done in ChannelReservationCache class?
Will there be any deadlock?
public class ChannelReservationCache
{
private readonly IDictionary<int, string> channelStates = new Dictionary<int, string>>();
private readonly object lockObject = new object();
private static readonly object lock = new object();
private static ChannelReservationCache instance = null;
private ChannelReservationCache() {}
public static ChannelReservationCache Instance
{
get
{
lock(lock) {
if (instance == null) {
instance = new ChannelReservationCache();
}
return instance;
}
}
}
public void AddChannelState(int level, string channel)
{
lock (this.lockObject)
{
//other code that makes the function take long time.
this.AddChannel(level, channel);
}
}
public Channel GetChannel(int level)
{
//other code that makes the function take long time.
Channel c = new Channel()
channelStates.TryGetValue(destinationId, out var c);
return c;
}
private void AddChannel(int level, string channel)
{
Channel c = new Channel();
c.ChannelName = channel;
c.IsActive = true;
channelStates.Add(level, resourceState)
}
}
public class Channel
{
public string ChannelName {get; set;}
public bool IsActive {get; set;}
}
public class RMQRequestHandler
{
public Task HandleChannelRequest(int level, Channel messages)
{
ChannelReservationCache.Instance.AddChannelState(level, messages)
}
}
[Route("api/v1")]
public class ChannnelController: ControllerBase
{
[HttpGet]
[Route("ChannelResource")]
public IActionResult GetChannelResource([FromQuery] int id)
{
ChannelReservationCache crc = ChannelReservationCache.Instance.GetChannel(id);
return this.Ok(crc);
}
}
Solution 1:[1]
First off there is a far simpler solution for you: Simply change
private readonly IDictionary<int, string> channelStates = new Dictionary<int, string>();
To:
private readonly IDictionary<int, string> channelStates = new System.Collections.Concurrent.ConcurrentDictionary<int, string>();
//using ConcurrentDictionary instead of Dictionary
And forget about the thread concurrency locking etc... In reality it is pretty hard to beat the performance of ConcurrentDictionary by writing our own locking structures to wrap a normal Dictionary. It is possible using ReaderWriterLockSlim to lock the dictionary and Interlocked to maintain a custom implementation for its count property. But this is a micro optimization that would only pay out over millions of itterations.
Now to answer your question:
One issue here:
- AddChannelState is threadsafe but GetChannel is not Think of it this way. Your writer is using a threadsafe lock but your reader is not. GetChannel also needs a lock in it.
Suggestion
- If you are Not using Lazy < T > in your singleton then it is probably best to make the following change
Code Example below where the cost of thread synchronization is avoided after it has been initialized. Bearing in mind that Lock (or Monitor.Enter / Exit) is one of the most expensive operations to perform. Sure there will be minimum locking contentions once it is initialized however the memory barrier is enforced each and every time plus the Monitor is being checked.
Following reference link is discussing Interlocked but the context of the memory barrier cost is the same: https://docs.microsoft.com/en-us/archive/msdn-magazine/2005/october/understanding-low-lock-techniques-in-multithreaded-apps
private static ChannelReservationCache instance;
private static readonly object lockInstance = new object();
//lock (lockInstance) enforces a memory barrier plus the monitor which is a cost you do not need to bear once the instance has been initialized
public static ChannelReservationCache Instance
{
get
{
if (instance == null)
{
lock (lockInstance)
{
if (instance == null)
{
instance = new ChannelReservationCache();
}
}
}
return instance;
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
