0

Is there a general way to convert a critical section to one or more semaphores? That is, is there some sort of straightforward transformation of the code that can be done to convert them?

For example, if I have two threads doing protected and unprotected work like below. Can I convert them to Semaphores that can be signaled, cleared and waited on?

void AThread()
{
  lock (this)
  {
    Do Protected Work
  }

  Do Unprotected work.
}

The question came to me after thinking about C#'s lock() statement and if I could implement equivalent functionality with an EventWaitHandle instead.

Martin Ellis
  • 9,603
  • 42
  • 53
Jeff
  • 1,315
  • 2
  • 17
  • 30
  • What do you mean "convert to semaphore?" And, no, you can't reliably implement mutual exclusion using something like an `EventWaitHandle`. Finally, is there a particular reason you want to avoid `lock`? – Jim Mischel May 01 '13 at 10:02
  • A semaphore is **not** equivalent to the *lock* statement. It is not re-entrant, always a good way to invoke deadlock. Like your lock(this) code does as well. Arbitrarily replacing synchronization code that works for no good reason is never a good idea. – Hans Passant May 01 '13 at 13:08
  • I'm wondering if there is any logical equivalent between a critical section and semaphore signaling. I think of it like DeMorgan's theorem; meaning that I can convert (A && B) into (! (!A || !B)). There is no reluctance to use lock. It's more of a theoretical question to me, which is why I put it in cstheory. – Jeff May 01 '13 at 16:23
  • 1
    On the face of it, a critical section looks like a semaphore with a maximum count of 1. Based on that *erroneous and incomplete* view, they look interchangeable. But they are not. A thread cannot release a critical section that it has not first acquired. That's not true of a semaphore; a thread can call release on a semaphore even though it hasn't previously acquired it. That's the most important of several differences. – Jim Mischel May 01 '13 at 22:01

3 Answers3

2

Yes there is a general way to convert a lock section to use a Semaphore, using the same try...finally block that lock is equivalent to, with a Semaphore with a max count of 1, initialised to count 1.

EDIT (May 11th) recent research has shown me that my reference for the try ... finally equivalence is out of date. The code samples below would need to be adjusted accordingly as a result of this. (end edit)

    private readonly Semaphore semLock = new Semaphore(1, 1);
    void AThread()
    {
        semLock.WaitOne();
        try {
            // Protected code
        }
        finally {
            semLock.Release();
        }
        // Unprotected code
    }

However you would never do this. lock:

  • is used to restrict resource access to a single thread at a time,
  • conveys the intent that resources in that section cannot be simultaneously accessed by more than one thread

Conversely Semaphore:

  • is intended to control simultaneous access to a pool of resources with a limit on concurrent access.
  • conveys the intent of either a pool of resources that can be accessed by a maximum number of threads, or of a controlling thread that can release a number of threads to do some work when it is ready.
  • with a max count of 1 will perform slower than lock.
  • can be released by any thread, not just the one that entered the section (added in edit)

Edit: You also mention EventWaitHandle at the end of your question. It is worth noting that Semaphore is a WaitHandle, but not an EventWaitHandle, and also from the MSDN documentation for EventWaitHandle.Set:

There is no guarantee that every call to the Set method will release a thread from an EventWaitHandle whose reset mode is EventResetMode.AutoReset. If two calls are too close together, so that the second call occurs before a thread has been released, only one thread is released. It is as if the second call did not happen.

The Detail

You asked:

Is there a general way to convert a critical section to one or more semaphores? That is, is there some sort of straightforward transformation of the code that can be done to convert them?

Given that:

    lock (this) {
        //  Do protected work
    }
    //Do unprotected work

is equivalent (see below for reference and notes on this) to

**EDIT: (11th May) as per the above comment, this code sample needs adjusting before use as per this link

    Monitor.Enter(this);
    try {
        // Protected code
    }
    finally {
        Monitor.Exit(this);
    }
    // Unprotected code

You can achieve the same using Semaphore by doing:

    private readonly Semaphore semLock = new Semaphore(1, 1);
    void AThread()
    {
        semLock.WaitOne();
        try {
            // Protected code
        }
        finally {
            semLock.Release();
        }
        // Unprotected code
    }

You also asked:

For example, if I have two threads doing protected and unprotected work like below. Can I convert them to Semaphores that can be signaled, cleared and waited on?

This is a question I struggled to understand, so I apologise. In your example you name your method AThread. To me, it's not really AThread, it's AMethodToBeRunByManyThreads !!

    private readonly Semaphore semLock = new Semaphore(1, 1);
    void MainMethod() {
        Thread t1 = new Thread(AMethodToBeRunByManyThreads);
        Thread t2 = new Thread(AMethodToBeRunByManyThreads);
        t1.Start();
        t2.Start();
        //  Now wait for them to finish - but how?
    }
    void AMethodToBeRunByManyThreads() { ... }

So semLock = new Semaphore(1, 1); will protect your "protected code", but lock is more appropriate for that use. The difference is that a Semaphore would allow a third thread to get involved:

    private readonly Semaphore semLock = new Semaphore(0, 2);
    private readonly object _lockObject = new object();
    private int counter = 0;
    void MainMethod()
    {
        Thread t1 = new Thread(AMethodToBeRunByManyThreads);
        Thread t2 = new Thread(AMethodToBeRunByManyThreads);
        t1.Start();
        t2.Start();
        //  Now wait for them to finish
        semLock.WaitOne();
        semLock.WaitOne();
        lock (_lockObject)
        {
            // uses lock to enforce a memory barrier to ensure we read the right value of counter
            Console.WriteLine("done: {0}", counter);  
        }
    }

    void AMethodToBeRunByManyThreads()
    {
        lock (_lockObject) {
            counter++;
            Console.WriteLine("one");
            Thread.Sleep(1000);
        }
        semLock.Release();
    }

However, in .NET 4.5 you would use Tasks to do this and control your main thread synchronisation.


Here are a few thoughts:

lock(x) and Monitor.Enter - equivalence

The above statement about equivalence is not quite accurate. In fact:

"[lock] is precisely equivalent [to Monitor.Enter try ... finally] except x is only evaluated once [by lock]" (ref: C# Language Specification)

This is minor, and probably doesn't matter to us.

You may have to be careful of memory barriers, and incrementing counter-like fields, so if you are using Semaphore you may still need lock, or Interlocked if you are confident of using it.

Beware of lock(this) and deadlocks

My original source for this would be Jeffrey Richter's article "Safe Thread Synchronization". That, and general best practice:

  • Don't lock this, instead create an object field within your class on class instantiation (don't use a value type, as it will be boxed anyway)
  • Make the object field readonly (personal preference - but it not only conveys intent, it also prevents your locking object being changed by other code contributors etc.)

The implications are many, but to make team working easier, follow best practice for encapsulation and to avoid nasty edge case errors that are hard for tests to detect, it is better to follow the above rules.

Your original code would therefore become:

    private readonly object m_lockObject = new object();
    void AThread()
    {
        lock (m_lockObject) {
            //  Do protected work
        }
        //Do unprotected work
    }

(Note: generally Visual Studio helps you in its snippets by using SyncRoot as your lock object name)

Semaphore and lock are intended for different use

lock grants threads a spot on the "ready queue" on a FIFO basis (ref. Threading in C# - Joseph Albahari, part 2: Basic Synchronization, Section: Locking). When anyone sees lock, they know that usually inside that section is a shared resource, such as a class field, that should only be altered by a single thread at a time.

The Semaphore is a non-FIFO control for a section of code. It is great for publisher-subscriber (inter-thread communication) scenarios. The freedom around different threads being able to release the Semaphore to the ones that acquired it is very powerful. Semantically it does not necessarily say "only one thread accesses the resources inside this section", unlike lock.

Example: to increment a counter on a class, you might use lock, but not Semaphore

    lock (_lockObject) {
        counter++;
    }

But to only increment that once another thread said it was ok to do so, you could use a Semaphore, not a lock, where Thread A does the increment once it has the Semaphore section:.

    semLock.WaitOne();
    counter++;
    return;

And thread B releases the Semaphore when it is ready to allow the increment:

    // when I'm ready in thread B
    semLock.Release();

(Note that this is forced, a WaitHandle such as ManualResetEvent might be more appropriate in that example).

Performance

From a performance perspective, running the simple program below on a small multi thread VM, lock wins over Semaphore by a long way, although the timescales are still very fast and would be sufficient for all but high throughput software. Note that this ranking was broadly the same when running the test with two parallel threads accessing the lock.

Time for 100 iterations in ticks on a small VM (smaller is better):

  • 291.334 (Semaphore)
  • 44.075 (SemaphoreSlim)
  • 4.510 (Monitor.Enter)
  • 6.991 (Lock)

Ticks per millisecond: 10000

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        Console.WriteLine("100 iterations in ticks");
        p.TimeMethod("Semaphore", p.AThreadSemaphore);
        p.TimeMethod("SemaphoreSlim", p.AThreadSemaphoreSlim);
        p.TimeMethod("Monitor.Enter", p.AThreadMonitorEnter);
        p.TimeMethod("Lock", p.AThreadLock);
        Console.WriteLine("Ticks per millisecond: {0}", TimeSpan.TicksPerMillisecond);
    }

    private readonly Semaphore semLock = new Semaphore(1, 1);
    private readonly SemaphoreSlim semSlimLock = new SemaphoreSlim(1, 1);
    private readonly object _lockObject = new object();
    const int Iterations = (int)1E6;
    int sharedResource = 0;

    void TimeMethod(string description, Action a)
    {
        sharedResource = 0;
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < Iterations; i++)
        {
            a();
        }
        sw.Stop();
        Console.WriteLine("{0:0.000} ({1})", (double)sw.ElapsedTicks * 100d / (double)Iterations, description);
    }

    void TimeMethod2Threads(string description, Action a)
    {
        sharedResource = 0;
        Stopwatch sw = new Stopwatch();
        using (Task t1 = new Task(() => IterateAction(a, Iterations / 2)))
        using (Task t2 = new Task(() => IterateAction(a, Iterations / 2)))
        {
            sw.Start();
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2);
            sw.Stop();
        }
        Console.WriteLine("{0:0.000} ({1})", (double)sw.ElapsedTicks * (double)100 / (double)Iterations, description);
    }

    private static void IterateAction(Action a, int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            a();
        }
    }

    void AThreadSemaphore()
    {
        semLock.WaitOne();
        try {
            sharedResource++;
        }
        finally {
            semLock.Release();
        }
    }
    void AThreadSemaphoreSlim()
    {
        semSlimLock.Wait();
        try
        {
            sharedResource++;
        }
        finally
        {
            semSlimLock.Release();
        }
    }
    void AThreadMonitorEnter()
    {
        Monitor.Enter(_lockObject);
        try
        {
            sharedResource++;
        }
        finally
        {
            Monitor.Exit(_lockObject);
        }
    }
    void AThreadLock()
    {
        lock (_lockObject)
        {
            sharedResource++;
        }
    }
}
Andy Brown
  • 18,961
  • 3
  • 52
  • 62
1

It's difficult to determine what you're asking for here.

If you just want something you can wait on, you can use a Monitor, which is what lock uses under the hood. That is, your lock sequence above is expanded to something like:

void AThread()
{
    Monitor.Enter(this);
    try
    {
        // Do protected work
    }
    finally
    {
        Monitor.Exit(this);
    }
    // Do unprotected work
}

By the way, lock (this) is generally not a good idea. You're better off creating a lock object:

private object _lockObject = new object();

Now, if you want to conditionally obtain the lock, you can use `Monitor.TryEnter:

if (Monitor.TryEnter(_lockObject))
{
    try
    {
        // Do protected work
    }
    finally
    {
        Monitor.Exit(_lockObject);
    }
 }

If you want to wait with a timeout, use the TryEnter overload:

if (Monitor.TryEnter(_lockObject, 5000))  // waits for up to 5 seconds

The return value is true if the lock was obtained.

A mutex is fundamentally different from an EventWaitHandle or Semaphore in that only the thread that acquires the mutex can release it. Any thread can set or clear a WaitHandle, and any thread can release a Semaphore.

I hope that answers your question. If not, edit your question to give us more detail about what you're asking for.

Community
  • 1
  • 1
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • JimMischel. For interest, I just learned that the lock equivalance is different in .NET 4, so your above code should, potentially, be updated. See [Locks and exceptions do not mix" - Eric Lippert](http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx) – Andy Brown May 11 '13 at 20:57
0

You should consider taking a look a the Wintellect Power Threading libraries: https://github.com/Wintellect/PowerThreading

One of the things these libraries do is create generic abstractions that allow threading primitives to be swapped out.

This means on a 1 or 2 processor machine where you see very little contention, you may use a standard lock. One a 4 or 8 processor machine where contention is common, perhaps a reader/writer lock is more correct. If you use the primitives such as ResourceLock you can swap out:

  • Spin Lock
  • Monitor
  • Mutex
  • Reader Writer
  • Optex
  • Semaphore
  • ... and others

I've written code that dynamically, based on the number of processors, chose specific locks based on the amount of contention likely to be present. With the structure found in that library, this is practical to do.

Chris M.
  • 1,731
  • 14
  • 15