12

There seems to be a lot to learn about multithreaded programming and it's all a bit intimidating.

For my current needs, I just want to protect against a method being called again from another thread before it finishes, and my question is:

Is this an adequate (safe) way to make a method thread-safe?

class Foo
{
    bool doingWork;
    void DoWork()
    {
        if (doingWork)  // <- sophistocated thread-safety
            return;     // <-

        doingWork = true;

        try
        {
            [do work here]
        }
        finally
        {
            doingWork = false;
        }
    }
}

If that isn't sufficient, what is the simplest way to achieve this?


EDIT: More info about the scenario:

  • There is only one instance of Foo

  • Foo.DoWork() will be called from a ThreadPool thread on the Elapsed event of a System.Timers.Timer.

  • Normally Foo.DoWork() will finish eons before the next time it's called, but I want to code for the slim chance that it will run long, and get called again before finishing.


(I'm also not smart enough to be sure if this question could be tagged language-agnostic, so I haven't. Enlightened readers, feel free to do so if applicable.)

Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
  • Is an object of type Foo instantiated for each thread, or is it shared across multiple threads? – NotMe Jul 29 '11 at 22:04
  • Give more details regardingcode which calling dowork method,are there multiple threads? – sll Jul 29 '11 at 22:08
  • Either support re-entrancy or design your code so that it can't happen. Just bailing out when it happens is not likely to be the right solution. But your edit makes it clear that thread safety is actually the issue rather that re-entrancy. – David Heffernan Jul 29 '11 at 22:11

4 Answers4

14

Your code is not thread safe. You should use the lock keyword instead.

In your current code:

  if (doingWork)
        return;

  // A thread having entered the function was suspended here by the scheduler.

  doingWork = true;

When the next thread comes through, it will also enter the function.

This is why the lock construct should be used. It basically does the same as your code, but without the risk for a thread being interrupted in the middle:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        lock(lockObject)
        {
            [do work here]
        }
    }
}

Note that this code has somewhat different semantics than your original. This code will cause the second thread entering to wait and then do the work. Your original code made the second thread just abort. To come closer to your original code, the C# lock statement cannot be used. The underlying Monitor construct has to be used directly:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        if(Monitor.TryEnter(lockObject))
        {
            try
            {
                [do work here]
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    }
}
Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
Anders Abel
  • 67,989
  • 17
  • 150
  • 217
  • I don't really need to abort, so the lock method should be fine. Sweet - that's simple! Thanks. – Igby Largeman Jul 29 '11 at 22:35
  • [Valentin](http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698) has a way of doing the abort with just lock(). Interested in your comments on it. – Igby Largeman Jul 29 '11 at 23:01
3

Re-entrancy has nothing to do with multi-threading.

A re-entrant method is a method that can end up being called from within itself, on the same thread.
For example, if a method raises an event, and the client code that handles that event calls the method again inside the event handler, that method is re-entrant.
Protecting that method from re-entrancy means making sure that if you call it from inside itself, it will either not do anyhting or throw an exception.

Your code is protected from re-entrancy within the same object instance, as long as everything is on the same thread.

Unless [do work here] is capable of running external code (eg, by raising an event, or by calling a delegate or method from something else), it isn't re-entrant in the first place.

Your edited question indicates that this entire section is irrelevant to you.
You should probably read it anyway.


You may be (EDIT: are) looking for exclusivity – ensuring that the method will not run twice at once if called by multiple threads simultaneously.
Your code is not exclusive. If two threads run the method at once, and they both run the if statement at once, they will both get past the if, then both set the doingWork flag, and will both run the entire method.

To do that, use the lock keyword.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Well it did say multithreading in the tags so one can only assume. – Jesus Ramos Jul 29 '11 at 22:04
  • 1
    One can only assume _what_? The question has nothing to do with multi-threading. **EDIT**: Now, it does. – SLaks Jul 29 '11 at 22:06
  • Admittedly I was only thinking about the method being called again from another thread. Thanks for pointing this out - I have edited the question to be clear on that. However I believe the term can be applied to a multhithreading scenario too. If not, what would you call it when a method needs to be able able cope with being called from another thread while it's already executing? – Igby Largeman Jul 29 '11 at 22:12
  • Point taken. My confusion came from reading in MSDN that my method should be "reentrant" if I don't prevent the scenario I described. However, I understand the distinction now. Thanks! – Igby Largeman Jul 29 '11 at 22:24
  • - Quesion edited to correct lingo. Hope it's more correct now. – Igby Largeman Jul 29 '11 at 22:29
2

if you want easy code and dont care about performance too much it can be as easy as

class Foo
{
    bool doingWork;
object m_lock=new object();
    void DoWork()
    {
        lock(m_lock) // <- not sophistocated multithread protection
{
        if (doingWork)  
            return;     
         doingWork = true;
}


        try
        {
            [do work here]
        }
        finally
        {
lock(m_lock) //<- not sophistocated multithread protection
{
            doingWork = false;
}
        }
    }

}

If you want to incapsulate locking a little you can create a property that is thread safe like this:

public bool DoingWork
{
get{ lock(m_Lock){ return doingWork;}}
set{lock(m_lock){doingWork=value;}}
}

Now you can use it instead field , however it will result in more time spent for locking cause number of lock uses increases.

Or you can use full fence approach ( from great threading book Joseph Albahari online threading )

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

He states that full fence is 2x faster than lock statement. In some cases you can improve performance by removing unneeded calls to MemoryBarrier(), but using lock is simple, more clear and less error prone.

I believe this can also be done using Interlocked class around int based doingWork field.

Valentin Kuzub
  • 11,703
  • 7
  • 56
  • 93
  • Is there any notable difference between your lock() (locking access to doingWork), and Anders' lock() method (locking access to the code doing the work)? – Igby Largeman Jul 29 '11 at 22:47
  • there is logical difference. As i can see from his answer method is reentrant, and further calls are queued up. In my code I assume that you dont want to allow other threads to queue up this method, if it is running on one thread. In my experience my approach is often preferred, for example when you dont want to allow user to launch several server requests by double click on a button. PS: where he says that it cannot be done using lock, I do it using lock. Monitor.TryEnter can be better performance , but a little more advanced for daily needs I guess, id leave it for critical performance code. – Valentin Kuzub Jul 29 '11 at 22:53
  • This solution is simpler than my second code snippet using `Monitor.TryEnter`. It has worse performance as it requires two locks, but unless your app is very threading intensive it won't matter. – Anders Abel Jul 29 '11 at 23:05
  • barriers and fences are for real experts, the rest of us use locks and are confident in the correctness – David Heffernan Jul 29 '11 at 23:09
  • knowing the toolset can be important if task is performance critical, but its hard to disagree. Even solution as slow as thread safe property above can be fine for daily use. – Valentin Kuzub Jul 29 '11 at 23:11
  • Fortunately performance is not an issue in this case, but the non-aborting method fits best anyway. But I have another scenario where I really do need to bail out if a flag is true, so I think Ill adapt this method (top example) for that case. – Igby Largeman Jul 29 '11 at 23:33
0

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

Might want to look into using barrier instead, it does all of the work for you. It's the standard way of controlling reentrant code. Also allows you to control the amount of threads doing that work at once (if you allow more than 1).

Jesus Ramos
  • 22,940
  • 10
  • 58
  • 88