0

I'm trying to understand how to use semaphores while working with threads.

I have 2 threads that uses the same resource - an ArrayList. One method adds a random temperature to the list, the other method calculate the average temperature of the list.

How do I use semaphore's methods Wait and Release in this context? and how can I control that my thread that calculate the average temperature starts after something is added to my list.

This is some of my code:

class Temperature
{
    private static Random random = new Random();
    private static ArrayList buffer = new ArrayList();
    static SemaphoreSlim e, b;

    public static void Main (string[] args)
    {
        e = new SemaphoreSlim(6); //how will this work?
        b = new SemaphoreSlim(1);
        Thread t1 = new Thread (Add);
        t1.Start ();
        Thread t2 = new Thread (Average);
        t2.Start ();
    }

    public static void Add()
    {
        int temperature;
        for (int i=0; i<50; i++)
        {
            temperature = random.Next (36, 42);
            Console.WriteLine ("Temperature added to buffer: " + temperature);
            b.Wait ();
            e.Wait ();
            buffer.Add(temperature);
            b.Release ();
            Thread.Sleep (50);
        }
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Andes
  • 11
  • 3

5 Answers5

3

As others have pointed out, a straightforward lock is more appropriate than a semaphore here.

The reason for this is because you have only one resource (buffer) that you are protecting from concurrent access. With only a single resource that is either busy or not busy, a lock is most appropriate.

A semaphore is useful when there is a finite capacity for a resource. An example:

A venue like a bar or nightclub with a fire limit. People (threads) Wait until the bouncer (the Semaphore) sees the venue (the Resource) has capacity. The first clients won't have to Wait long, in fact they'll be let in immediately.

As they go in the bouncer (Semaphore) clicks his counter to record how much capacity is consumed. Once the venue reaches capacity, a queue will build up - the people will have to Wait until someone inside leaves.

As people leave (they Release) and the bouncer (Semaphore) decreases his counter - and a corresponding number of people can now enter.

So - you set capacity on the Semaphore, consumers of the resource Wait for it to be available (they will block if capacity is not available) and consumers Release when they are done with the resource.

A common feature (but not a necessary one) is that a semaphore is often used when you don't know (or care) what particular part of the resource is being consumed, just that some available portion of it is being used.

Semaphore versus Mutex

A Mutex is similar to a lock, but it is a named system-wide resource. A Mutex must be released by the Thread that acquired it, whereas a Semaphore does not have thread identity. This can explain why you sometimes see a Semaphore with a count of 1 being used in preference to a Mutex.

Community
  • 1
  • 1
James World
  • 29,019
  • 9
  • 86
  • 120
2

Only use one semaphore:

var b = new SemaphoreSlim(1); // 1 -> allow 1 thread to enter the critical section at one time.

Then use it to guard your buffer (= critical section)

b.Wait ();
buffer.Add(temperature);
b.Release ();

If you don't mind using a lock, use this instead:

private readonly object _locker_ = new object();

Then use it with the lock statement:

lock(_locker_)
{
    buffer.Add(temperature);
}

Don't forget to do the same in your Average() method.

helb
  • 7,609
  • 8
  • 36
  • 58
  • Yes usually a lock is better when your critical section is not part of several function or dependent on the object's state. I just want to add that if you make a locker object, always make it `readonly` so that it can't be changed (which would interfere with any ongoing synchronization). I.e. `private readonly object _locker_ = new Object();` – David S. Nov 04 '13 at 10:05
2

You need two things here.

  1. To prevent the threads from interfering with each other when updating the list, you need a lock or a semaphore.
  2. To make the averaging thread update the average whenever an item is added, you need it to wait on an event, and have the adding thread set that event whenever it adds an item.

You can do the one with a semaphore, although a lock or Monitor is more appropriate. The other really requires an event, probably AutoResetEvent.

private static Random random = new Random();
private static ArrayList buffer = new ArrayList();
static SemaphoreSlim BufferLock = new SemaphoreSlim(1);
static AutoResetEvent ItemAdded = new AutoResetEvent(false);

public static void Main (string[] args)
{
    BufferLock.Wait();  // initially acquire the semaphore.
    Thread t1 = new Thread (Add);
    t1.Start ();
    Thread t2 = new Thread (Average);
    t2.Start ();

    // wait for adding thread to finish
    t1.Wait();
}

public static void Add()
{
    int temperature;
    for (int i=0; i<50; i++)
    {
        temperature = random.Next (36, 42);
        BufferLock.Wait();
        buffer.Add(temperature);
        ItemAdded.Set();
        BufferLock.Release();
        Console.WriteLine ("Temperature added to buffer: " + temperature);
        Thread.Sleep (50);
    }
}

public static void Average()
{
    while (true)
    {
        ItemAdded.Wait();  // wait for item to be added
        BufferLock.Wait();
        ComputeAverage();  // however you want to do this
        BufferLock.Release();
    }
}

If you want to make sure that the last Average is computed, you'll have to wait on t2. Of course, you'll need a way to tell the thread to exit. Look into Cancellation.

You really shouldn't be using ArrayList. List< int > would be a much better choice. The only reason ever to use ArrayList is when you're writing code for .NET versions prior to 2.0, or when you're supporting very old code.

You could replace the AutoResetEvent with a Semaphore to simulate the event notification, but doing so is a bit of a kludge and will require very careful coding to get right. I certainly wouldn't want to do it that way.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • This was the solution I ended up using for my project. I tried some of the other suggestions (like using locks etc.) but this example worked best for my project :) – Andes Nov 13 '13 at 17:56
0

I guess you want to have your Average method kicks in as soon as Add method does it's work and be thread safe around your ArrayList object. So...

var s = new SemaphoreSlim(0);
private object myLock = new object();

Add()
{
  Calculate();
  lock(myLock)
  {
      buffer.Add(temperature);
      SemaphoreSlim.Release();
  }
}

Average()
{
  SemaphoreSlim.Wait();
  lock(myLock)
  {
     CalcAvg();
  }
}
Klark
  • 8,162
  • 3
  • 37
  • 61
0

While you can use an initially-zero Semaphore to implement the desired signaling, you might be better of using an AutoResetEvent instead. Make your calculating thread wait on the event handle and fire the event after adding to the list. Keep the locks as in the @klark's solution though.

For more information on choosing the right syncronization primitive for the job read http://msdn.microsoft.com/en-us/library/ms228964(v=vs.110).aspx

Jonas Bötel
  • 4,452
  • 1
  • 19
  • 28