A value from one thread influencing the path of another thread - c#

I have a program which uses two client threads and a server. There is a point in my program where I want a value in one thread to influence the path of another thread.
More specifically, I have this code in the server:
class Handler
{
public void clientInteraction(Socket connection, bool isFirstThread, Barrier barrier)
{
string pAnswer = string.Empty;
string endGameTrigger = string.Empty;
//setup streamReaders and streamWriters
while(true) //infinite game loop
{
//read in a question and send to both threads.
pAnswer = sr.ReadLine();
Console.WriteLine(pAnswer);
awardPoints();
writeToConsole("Press ENTER to ask another question or enter 0 to end the game", isFirstThread);
if(isFirstThread == true)
{
endGameTrigger = Console.ReadLine(); //this is only assigning to one thread...
}
barrier.SignalAndWait();
if(endGameTrigger == "0")//...meaning this is never satisfied in one thread
{
endGame();
}
}
}
}
The boolean value isFirstThread is a value set up in the constructor of the thread to which I can detect which thread was connected first.
Is there some way, or perhaps a threading method, that will allow the second connected thread to detect that the endGameTrigger in the first thread has been set and therefore both threads execute the endGame() method properly.

It's best to be concerned with multithreading
If it's absolutely necessary to start a separate thread for performance/UI reasons
If your code may be running in a multithreaded environment (like a web site) and you need to know that it won't break when multiple threads operate on the same class or same values.
But exercise extreme caution. Incorrect use/handling of multiple threads can cause your code to behave unpredictably and inconsistently. Something will work most of the time and then not work for no apparent reason. Bugs will be difficult to reproduce and identify.
That being said, one of the essential concepts of handling multithreading is to ensure that two threads don't try to update the same value at the same time. They can corrupt or partially modify values in ways that would be impossible for a single thread.
One way to accomplish this is with locking.
private object _lockObject = new Object();
private string _myString;
void SetStringValue(string newValue)
{
lock(_lockObject)
{
_myString = newValue;
}
}
You generally have an object that exists only to serve as a lock. When one thread enters that lock block it acquires a lock on the object. If another thread already has a lock on that object then the next thread just waits for the previous thread to release the lock. That ensures that two threads can't update the value at the same time.
You want to keep the amount of code inside the lock as small as possible so that the lock is released as soon as possible. And be aware that if it gets complicated with multiple locks then two threads can permanently block each other.
For incrementing and updating numbers there are also interlocked operations that handle the locking for you, ensuring that those operations are executed by one thread at a time.
Just for fun I wrote this console app. It takes a sentence, breaks it into words, and then adds each word back onto a new string using multiple threads and outputs the string.
using System;
using System.Threading.Tasks;
namespace FunWithThreading
{
class Program
{
static void Main(string[] args)
{
var sentence =
"I am going to add each of these words to a string "
+ "using multiple threads just to see what happens.";
var words = sentence.Split(' ');
var output = "";
Parallel.ForEach(words, word => output = output + " " + word);
Console.WriteLine(output);
Console.ReadLine();
}
}
}
The first two times I ran it, the output string was exactly what I started with. Great, it works perfectly! Then I got this:
I am going to add of these words to a string using multiple threads just to see what happens. each
Then I ran it 20 more times and couldn't repeat the error. Just imagine the frustration if this was a real application and something unpredictable like this happened even though I tested over and over and over, and then I couldn't get it to happen again.
So the point isn't that multithreading is evil, but just to understand the risks, only introduce it if you need to, and then carefully consider how to prevent threads from interfering with each other.

In response to Luaan's comment. I have put the endGameTrigger as private static string endGameTrigger in the Handler class. Making it a static field instead of a local method variable allows all instances of the handler class (each thread) access to this variable's most recent assignation. Many thanks.

Related

C# shared variable access from different threads

I am using a static variables to get access between threads, but is taking so long to get their values.
Context: I have a static class Results.cs, where I store the result variables of two running Process.cs instances.
public static int ResultsStation0 { get; set; }
public static int ResultsStation1 { get; set; }
Then, a function of the two process instances is called at the same time, with initial value of ResultsStation0/1 = -1.
Because the result will be provided not at the same time, the function is checking that both results are available. The fast instance will set the result and await for the result of the slower instance.
void StationResult(){
Stopwatch sw = new Stopwatch();
sw.Restart();
switch (stationIndex) //Set the result of the station thread
{
case 0: Results.ResultsStation0 = 1; break;
case 1: Results.ResultsStation1 = 1; break;
}
//Waits to get the results of both threads
while (true)
{
if (Results.ResultsStation0 != -1 && Results.ResultsStation1 != -1)
{
break;
}
}
Trace_Info("GOT RESULTS " + stationIndex + "Time: " + sw.ElapsedMilliseconds.ToString() + "ms");
if (Results.ResultsStation0 == 1 && Results.ResultsStation1 == 1)
{
//set OK if both results are OK
Device.profinet.WritePorts(new Enum[] { NOK, OK },
new int[] { 0, 1 });
}
}
It works, but the problem is that the value of sw of the thread that awaits, should be 1ms more or less. I am getting 1ms sometimes, but most of the times I have values up to 80ms.
My question is: why it takes that much if they are sharing the same memory (I guess)?
Is this the right way to access to a variable between threads?
Don't use this method. Global mutable state is bad enough. Mixing in multiple threads sounds like a recipe for unmaintainable code. Since there is no synchronization at all in sight there is no real guarantee that your program may ever finish. On a single CPU system your loop will prevent any real work from actually being done until the scheduler picks one of the worker threads to run, an even on multi core system you will waste a ton of CPU cycles.
If you really want global variables, these should be something that can signal the completion of the operation, i.e. a Task, or ManualResetEvent. That way you can get rid of your horrible spin-wait, and actually wait for each task to complete.
But I would highly recommend to get rid of the global variables and just use standard task based programming:
var result1 = Task.Run(MyMethod1);
var result2 = Task.Run(MyMethod2);
await Task.WhenAll(new []{result1, result2});
Such code is much easier to reason about and understand.
Multi threaded programming is difficult. There are a bunch of new ways your program can break, and the compiler will not help you. You are lucky if you even get an exception, in many cases you will just get an incorrect result. If you are unlucky you will only get incorrect results in production, not in development or testing. So you should read a fair amount about the topic so that you are at least familiar with the common dangers and the ways to mitigate them.
You are using flags as signaling for this you have a class called AutoResetEvent.
There's a difference between safe access and synchronization.
For safe access (atomic) purpose you can use the class Interlocked
For synchronization you use mutex based solutions - either spinlocks, barriers, etc...
What it looks like is you need a synchronization mechanism because you relay on an atomic behavior to signal a process that it is done.
Further more,
For C# there's the async way to do things and that is to call await.
It is Task based so in case you can redesign your flow to use Tasks instead of Threads it will suit you more.
Just to be clear - atomicity means you perform the call in one go.
So for example this is not atomic
int a = 0;
int b = a; //not atomic - read 'a' and then assign to 'b'.
I won't teach you everything to know about threading in C# in one post answer - so my advice is to read the MSDN articles about threading and tasks.

Legitimate use cases for UpgradeableReadLock

I was recently reminded of the UpgradeableReadLock construct C# provides and I'm trying to discern when it really makes sense to use it.
Say, for example, I have a cache of settings that are heavily read by many classes, but periodically need to be updated with a very low frequency based on a set of conditions that aren't necessarily deterministic...
would it make more sense to simply lock like so:
List<Setting> cachedSettings = this.GetCachedSettings( sessionId );
lock(cachedSettings)
{
bool requiresRefresh = cachedSettings.RequiresUpdate();
if(requiresRefresh)
{
// a potentially long operation
UpdateSettings( cachedSettings, sessionId );
}
return cachedSettings;
}
or use an UpgradeableReadLock:
public class SomeRepitory {
private ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
public List<Setting> GetCachedSettings( string sessionId )
{
_rw.EnterUpgradeableReadLock();
List<Setting> cachedSettings = this.GetCachedSettings( sessionId );
bool requiresRefresh = cachedSettings.RequiresUpdate();
if(requiresRefresh)
{
_rw.EnterWriteLock();
UpdateSettings( cachedSettings, sessionId );
_rw.ExitWriteLock();
}
_rw.ExitUpgradeableReadLock();
return cachedSettings;
}
perhaps what confuses me the most is how we can get away with checking if an update is required outside of the write block. In my example above I am referring to when I check for where a refresh is required, but to simplify I'll use an example from "C# 5.0 In A Nutshell":
while (true)
{
int newNumber = GetRandNum (100);
_rw.EnterUpgradeableReadLock();
if (!_items.Contains (newNumber))
{
_rw.EnterWriteLock();
_items.Add (newNumber);
_rw.ExitWriteLock();
Console.WriteLine ("Thread " + threadID + " added " + newNumber);
}
_rw.ExitUpgradeableReadLock();
Thread.Sleep (100);
}
my understanding is that this allows concurrent reads unless a thread needs to write, but what if two or more threads end up with the same random number and determine !_items.Contains(newNumber)? Given my understanding that this should allow concurrent reads (and correct me if I have misunderstood, of course).. it seems that, as soon as a write lock is obtained, any threads that were concurrently reading would need to be suspended and forced back to the start of _rw.EnterUpgradeableReadLock(); ?
Of course your second approach is better in case of many simultaneous readers and relatively rare write operations. When read lock is acquired (using _rw.EnterUpgradeableReadLock()) by a thread - other threads can also acquire it and read the value simultaneously. When some thread then enters write lock, it waits all reads to complete and then acquires exclusive access to lock object (all other threads trying to execute EnterXXX() operations wait) to update the value. When it releases the lock, other threads can do their job.
First example lock(cachedSettings) blocks all other threads so that only one thread can read the value at a time.
I would recommend in addition use the following pattern:
_rw.EnterUpgradeableReadLock();
try
{
//Do your job
}
finally
{
_rw.ExitUpgradeableReadLock();
}
for all Enter/Exit lock operations. It ensures (with high probability) that if exception happened inside your synchronized code, lock won't remain locked forever.
EDIT:
Answering Martin's comment. If you don't want multiple threads updating the value simultaneously, you need to change your logic to achieve that. For example, using a double-checked lock construct:
if(cachedSettings.RequiresUpdate())
{
_rw.EnterWriteLock();
try
{
if(cachedSettings.RequiresUpdate())
{
UpdateSettings( cachedSettings, sessionId );
}
}
finally
{
_rw.ExitWriteLock();
}
}
This will check if while we were waiting for write lock other thread haven't refreshed that value already. And if value doesn't require refresh anymore - just release the lock.
IMPORTANT: it's very bad to take exclusive lock for long time. So it the UpdateSettings function is long-running, you better execute it outside the lock and implement some additional logic to allow readers read expired value while some thread is refreshing it. I used to implement cache once and it's really complex to make it fast and thread-safe. You better use one of the existing implementations (for example System.Runtime.MemoryCache).

Under what conditions can a thread enter a lock (Monitor) region more than once concurrently?

(question revised): So far, the answers all include a single thread re-entering the lock region linearly, through things like recursion, where you can trace the steps of a single thread entering the lock twice. But is it possible somehow, for a single thread (perhaps from the ThreadPool, perhaps as a result of timer events or async events or a thread going to sleep and being awaken/reused in some other chunk of code separately) to somehow be spawned in two different places independently of each other, and hence, run into the lock re-entrance problem when the developer didn't expect it by simply reading their own code?
In the ThreadPool Class Remarks (click here) the Remarks seem to suggest that sleeping threads should be reused when they're not in use, or otherwise wasted by sleeping.
But on the Monitor.Enter reference page (click here) they say "It is legal for the same thread to invoke Enter more than once without it blocking." So I figure there must be something I'm supposed to be careful to avoid. What is it? How is it even possible for a single thread to enter the same lock region twice?
Suppose you have some lock region that takes an unfortunately long time. This might be realistic, for example, if you access some memory that has been paged out (or whatever.) The thread in the locked region might go to sleep or something. Does the same thread become eligible to run more code, which might accidentally step into the same lock region? The following does NOT, in my testing, get multiple instances of the same thread to run into the same lock region.
So how does one produce the problem? What exactly do you need to be careful to avoid?
class myClass
{
private object myLockObject;
public myClass()
{
this.myLockObject = new object();
int[] myIntArray = new int[100]; // Just create a bunch of things so I may easily launch a bunch of Parallel things
Array.Clear(myIntArray, 0, myIntArray.Length); // Just create a bunch of things so I may easily launch a bunch of Parallel things
Parallel.ForEach<int>(myIntArray, i => MyParallelMethod());
}
private void MyParallelMethod()
{
lock (this.myLockObject)
{
Console.Error.WriteLine("ThreadId " + Thread.CurrentThread.ManagedThreadId.ToString() + " starting...");
Thread.Sleep(100);
Console.Error.WriteLine("ThreadId " + Thread.CurrentThread.ManagedThreadId.ToString() + " finished.");
}
}
}
Suppose you have a queue that contains actions:
public static Queue<Action> q = whatever;
Suppose Queue<T> has a method Dequeue that returns a bool indicating whether the queue could be successfully dequeued.
And suppose you have a loop:
static void Main()
{
q.Add(M);
q.Add(M);
Action action;
while(q.Dequeue(out action))
action();
}
static object lockObject = new object();
static void M()
{
Action action;
lock(lockObject)
{
if (q.Dequeue(out action))
action();
}
}
Clearly the main thread enters the lock in M twice; this code is re-entrant. That is, it enters itself, through an indirect recursion.
Does this code look implausible to you? It should not. This is how Windows works. Every window has a message queue, and when a message queue is "pumped", methods are called corresponding to those messages. When you click a button, a message goes in the message queue; when the queue is pumped, the click handler corresponding to that message gets invoked.
It is therefore extremely common, and extremely dangerous, to write Windows programs where a lock contains a call to a method which pumps a message loop. If you got into that lock as a result of handling a message in the first place, and if the message is in the queue twice, then the code will enter itself indirectly, and that can cause all manner of craziness.
The way to eliminate this is (1) never do anything even slightly complicated inside a lock, and (2) when you are handling a message, disable the handler until the message is handled.
Re-Entrance is possible if you have a structure like so:
Object lockObject = new Object();
void Foo(bool recurse)
{
lock(lockObject)
{
Console.WriteLine("In Lock");
if (recurse) { foo(false); }
}
}
While this is a pretty simplistic example, it's possible in many scenarios where you have interdependent or recursive behaviour.
For example:
ComponentA.Add(): locks a common 'ComponentA' object, adds new item to ComponentB.
ComponentB.OnNewItem(): new item triggers data-validation on each item in list.
ComponentA.ValidateItem(): locks a common 'ComponentA' object to validate the item.
Same-thread re-entry on the same lock is needed to ensure you don't get deadlocks occurring with your own code.
One of the more subtle ways you can recurse into a lock block is in GUI frameworks. For example, you can asynchronously invoke code on a single UI thread (a Form class)
private object locker = new Object();
public void Method(int a)
{
lock (locker)
{
this.BeginInvoke((MethodInvoker) (() => Method(a)));
}
}
Of course, this also puts in an infinite loop; you'd likely have a condition by which you'd want to recurse at which point you wouldn't have an infinite loop.
Using lock is not a good way to sleep/awaken threads. I would simply use existing frameworks like Task Parallel Library (TPL) to simply create abstract tasks (see Task) to creates and the underlying framework handles creating new threads and sleeping them when needed.
IMHO, Re-entering a lock is not something you need to take care to avoid (given many people's mental model of locking this is, at best, dangerous, see Edit below). The point of the documentation is to explain that a thread cannot block itself using Monitor.Enter. This is not always the case with all synchronization mechanisms, frameworks, and languages. Some have non-reentrant synchronization in which case you have to be careful that a thread doesn't block itself. What you do need to be careful about is always calling Monitor.Exit for every Monitor.Enter call. The lock keyword does this for you automatically.
A trivial example with re-entrance:
private object locker = new object();
public void Method()
{
lock(locker)
{
lock(locker) { Console.WriteLine("Re-entered the lock."); }
}
}
The thread has entered the lock on the same object twice so it must be released twice. Usually it is not so obvious and there are various methods calling each other that synchronize on the same object. The point is that you don't have to worry about a thread blocking itself.
That said you should generally try to minimize the amount the time you need to hold a lock. Acquiring a lock is not computationally expensive, contrary to what you may hear (it is on the order of a few nanoseconds). Lock contention is what is expensive.
Edit
Please read Eric's comments below for additional details, but the summary is that when you see a lock your interpretation of it should be that "all activations of this code block are associated with a single thread", and not, as it is commonly interpreted, "all activations of this code block execute as a single atomic unit".
For example:
public static void Main()
{
Method();
}
private static int i = 0;
private static object locker = new object();
public static void Method()
{
lock(locker)
{
int j = ++i;
if (i < 2)
{
Method();
}
if (i != j)
{
throw new Exception("Boom!");
}
}
}
Obviously, this program blows up. Without the lock, it is the same result. The danger is that the lock leads you into a false sense of security that nothing could modify state on you between initializing j and evaluating the if. The problem is that you (perhaps unintentionally) have Method recursing into itself and the lock won't stop that. As Eric points out in his answer, you might not realize the problem until one day someone queues up too many actions simultaneously.
ThreadPool threads cannot be reused elsewhere just because they went to sleep; they need to finish before they're reused. A thread that is taking a long time in a lock region does not become eligible to run more code at some other independent point of control. The only way to experience lock re-entry is by recursion or executing methods or delegates inside a lock that re-enter the lock.
Let's think about something other than recursion.
In some of business logics, they would like to control the behaviors of synchronization.
One of these patterns, they invoke Monitor.Enter somewhere and would like to invoke Monitor.Exit elsewhere later. Here is the code to get the idea about that:
public partial class Infinity: IEnumerable<int> {
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public IEnumerator<int> GetEnumerator() {
for(; ; )
yield return ~0;
}
public static readonly Infinity Enumerable=new Infinity();
}
public partial class YourClass {
void ReleaseLock() {
for(; lockCount-->0; Monitor.Exit(yourLockObject))
;
}
void GetLocked() {
Monitor.Enter(yourLockObject);
++lockCount;
}
void YourParallelMethod(int x) {
GetLocked();
Debug.Print("lockCount={0}", lockCount);
}
public static void PeformTest() {
new Thread(
() => {
var threadCurrent=Thread.CurrentThread;
Debug.Print("ThreadId {0} starting...", threadCurrent.ManagedThreadId);
var intanceOfYourClass=new YourClass();
// Parallel.ForEach(Infinity.Enumerable, intanceOfYourClass.YourParallelMethod);
foreach(var i in Enumerable.Range(0, 123))
intanceOfYourClass.YourParallelMethod(i);
intanceOfYourClass.ReleaseLock();
Monitor.Exit(intanceOfYourClass.yourLockObject); // here SynchronizationLockException thrown
Debug.Print("ThreadId {0} finished. ", threadCurrent.ManagedThreadId);
}
).Start();
}
object yourLockObject=new object();
int lockCount;
}
If you invoke YourClass.PeformTest(), and get a lockCount greater than 1, you've reentered; not necessarily be concurrent.
If it was not safe for reentrancy, you will get stuck in the foreach loop.
In the code block where Monitor.Exit(intanceOfYourClass.yourLockObject) will throw you a SynchronizationLockException, it is because we are trying to invoke Exit more than the times it have entered. If you are about to use the lock keyword, you possibly would not encounter this situation except directly or indirectly of recursive calls. I guess that's why the lock keyword was provided: it prevents the Monitor.Exit to be omitted in a careless manner.
I remarked the calling of Parallel.ForEach, if you are interested then you can test it for fun.
To test the code, .Net Framework 4.0 is the least requirement, and following additional name spaces are required, too:
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
using System.Collections;
Have fun.

Thread racing, why do threads work so?

I have two different result from exchanging two lines of code ( done = true with Console.Write() one )
If I put done = true, firstly, the result will be:
True
Else If I put Console.WriteLine() firstly, the result will be:
False
False
Why? ( see carefully, that bool variable is static! )
using System;
using System.Threading;
class Program
{
static bool done;
static void Main(string[] args)
{
new Thread(test).Start();
test();
}
static void test()
{
if (!done)
{
done = true;
Console.WriteLine(done);
}
}
}
My bet is that the Console.WriteLine will be enough work to keep the thread busy while the second call to test() has a chance to execute.
So basically the call to WriteLine delays the setting of done long enough for the second call to test to be able to test done and find it is still set as false.
If you leave it as shown, with done = true; before the write to the console then this will be set almost instantly and thus the second call to test will find done set to true and will therefore not perform the Console.WriteLine.
Hope that all makes sense.
I just found this which contains code very much like your question. If you didn't get your question from this page already, then I would suggest having a read as it explains in much more detail the cause of this effect.
With the follow key extract:
On a single-processor computer, a thread scheduler performs
time-slicing — rapidly switching execution between each of the active
threads. Under Windows, a time-slice is typically in the
tens-of-milliseconds region — much larger than the CPU overhead in
actually switching context between one thread and another (which is
typically in the few-microseconds region).
So essentially the call to Console.WriteLine is taking long enough for the processor to decide that it is time for the main thread to have another go before your extra thread is permitted to continue (and ultimate set the done flag)
Your code isn't thread safe, and the results will be unpredictable.
You need to lock access when reading / writing to the static boolean, like so:
static bool done;
static readonly object _mylock = new object();
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
new Thread(test).Start();
test();
Console.ReadKey();
}
static void test()
{
lock (_mylock)
{
if (!done)
{
Console.WriteLine(done);
done = true;
}
}
}
Edit : readonly thanks #d4wn
Looks like the scheduler just cut the CPU time from one thread after it's call of Console.Writeline and then gave it to the other thread, all before done was set to true.
Are you certain that it always prints False\nFalse when you call Console.Writeline before assigning done = true;? To my understanding, this should be quite random.
Each time a shared variable is accessed by one of the sharing threads must be protected by one of the syncronization techniques explicitly. The environment (clr..) doesn't do it for us, cause in the whole possible complexity of multithreading it would be impossible. So this definetely responsible and not easy task must be done by the developer, writing multithreading code.
I guess there you can find a great deal of necessary information:
Thread Synchronization (C# Programming Guide)

C# manual lock/unlock

I have a function in C# that can be called multiple times from multiple threads and I want it to be done only once so I thought about this:
class MyClass
{
bool done = false;
public void DoSomething()
{
lock(this)
if(!done)
{
done = true;
_DoSomething();
}
}
}
The problem is _DoSomething takes a long time and I don't want many threads to wait on it when they can just see that done is true.
Something like this can be a workaround:
class MyClass
{
bool done = false;
public void DoSomething()
{
bool doIt = false;
lock(this)
if(!done)
doIt = done = true;
if(doIt)
_DoSomething();
}
}
But just doing the locking and unlocking manually will be much better.
How can I manually lock and unlock just like the lock(object) does? I need it to use same interface as lock so that this manual way and lock will block each other (for more complex cases).
The lock keyword is just syntactic sugar for Monitor.Enter and Monitor.Exit:
Monitor.Enter(o);
try
{
//put your code here
}
finally
{
Monitor.Exit(o);
}
is the same as
lock(o)
{
//put your code here
}
Thomas suggests double-checked locking in his answer. This is problematic. First off, you should not use low-lock techniques unless you have demonstrated that you have a real performance problem that is solved by the low-lock technique. Low-lock techniques are insanely difficult to get right.
Second, it is problematic because we don't know what "_DoSomething" does or what consequences of its actions we are going to rely on.
Third, as I pointed out in a comment above, it seems crazy to return that the _DoSomething is "done" when another thread is in fact still in the process of doing it. I don't understand why you have that requirement, and I'm going to assume that it is a mistake. The problems with this pattern still exist even if we set "done" after "_DoSomething" does its thing.
Consider the following:
class MyClass
{
readonly object locker = new object();
bool done = false;
public void DoSomething()
{
if (!done)
{
lock(locker)
{
if(!done)
{
ReallyDoSomething();
done = true;
}
}
}
}
int x;
void ReallyDoSomething()
{
x = 123;
}
void DoIt()
{
DoSomething();
int y = x;
Debug.Assert(y == 123); // Can this fire?
}
Is this threadsafe in all possible implementations of C#? I don't think it is. Remember, non-volatile reads may be moved around in time by the processor cache. The C# language guarantees that volatile reads are consistently ordered with respect to critical execution points like locks, and it guarantees that non-volatile reads are consistent within a single thread of execution, but it does not guarantee that non-volatile reads are consistent in any way across threads of execution.
Let's look at an example.
Suppose there are two threads, Alpha and Bravo. Both call DoIt on a fresh instance of MyClass. What happens?
On thread Bravo, the processor cache happens to do a (non-volatile!) fetch of the memory location for x, which contains zero. "done" happens to be on a different page of memory which is not fetched into the cache quite yet.
On thread Alpha at the "same time" on a different processor DoIt calls DoSomething. Thread Alpha now runs everything in there. When thread Alpha is done its work, done is true and x is 123 on Alpha's processor. Thread Alpha's processor flushes those facts back out to main memory.
Thread bravo now runs DoSomething. It reads the page of main memory containing "done" into the processor cache and sees that it is true.
So now "done" is true, but "x" is still zero in the processor cache for thread Bravo. Thread Bravo is not required to invalidate the portion of the cache that contains "x" being zero because on thread Bravo neither the read of "done" nor the read of "x" were volatile reads.
The proposed version of double-checked locking is not actually double-checked locking at all. When you change the double-checked locking pattern you need to start over again from scratch and re-analyze everything.
The way to make this version of the pattern correct is to make at least the first read of "done" into a volatile read. Then the read of "x" will not be permitted to move "ahead" of the volatile read to "done".
You can check the value of done before and after the lock:
if (!done)
{
lock(this)
{
if(!done)
{
done = true;
_DoSomething();
}
}
}
This way you won't enter the lock if done is true. The second check inside the lock is to cope with race conditions if two threads enter the first if at the same time.
BTW, you shouldn't lock on this, because it can cause deadlocks. Lock on a private field instead (like private readonly object _syncLock = new object())
The lock keyword is just syntactic sugar for the Monitor class. Also you could call Monitor.Enter(), Monitor.Exit().
But the Monitor class itself has also the functions TryEnter() and Wait() which could help in your situation.
I know this answer comes several years late, but none of the current answers seem to address your actual scenario, which only became apparent after your comment:
The other threads don't need to use any information generated by ReallyDoSomething.
If the other threads don't need to wait for the operation to complete, the second code snippet in your question would work fine. You can optimize it further by eliminating your lock entirely and using an atomic operation instead:
private int done = 0;
public void DoSomething()
{
if (Interlocked.Exchange(ref done, 1) == 0) // only evaluates to true ONCE
_DoSomething();
}
Furthermore, if your _DoSomething() is a fire-and-forget operation, then you might not even need the first thread to wait for it, allowing it to run asynchronously in a task on the thread pool:
int done = 0;
public void DoSomething()
{
if (Interlocked.Exchange(ref done, 1) == 0)
Task.Factory.StartNew(_DoSomething);
}

Categories

Resources