Introduction
This article describes using a Semaphore for thread pooling.
Background
A semaphore is an enforcement of size limitation to a thread pool. Once it’s full, no more threads can enter the semaphore until one or more threads complete and hence get terminated. A queue builds up outside for other threads. Then, for each thread that leaves the semaphore, another thread enters from the queue.
A semaphore has no owner. Any thread can release any other thread on a semaphore.
There are two functionally similar versions of this class: Semaphore and SemaphoreSlim. The latter was introduced in Framework 4.0 and has been optimized to meet the low-latency demands of parallel programming. It’s also useful in traditional multithreading because it lets you specify a cancellation token when waiting. It cannot, however, be used for interprocess signaling.
To use a semaphore in C#, you first need to instantiate an instance of a Semaphore object. The basic constructor takes two parameters. The first is the number of resource slots initially available when the object is instantiated. The second parameter is the maximum number of slots available. If you want to reserve some slots for the calling thread, you can do so by making the first parameter smaller than the second. To reserve all slots for new threads, you should make both parameters the same.
Use the Semaphore class to control access to a pool of resources. Threads enter the semaphore by calling the WaitOne method, which is inherited from the WaitHandle class, and release the semaphore by calling the Release method.
The count on a semaphore is decremented each time a thread enters the semaphore, and incremented when a thread releases the semaphore. When the count is zero, subsequent requests block until other threads release the semaphore. When all threads have released the semaphore, the count is at the maximum value specified when the semaphore was created.
Using the Code
In our example code, we are creating a semaphore with an initial size of 3 and a max size of 5, which means initially two slots will be vacant.
using System;
using System.Threading;
namespace Threading
{
public class SemaphoreImpl
{
private static Semaphore semaphore = new Semaphore(3,5);
public SemaphoreImpl()
{
}
private void ThreadExcecute(object num)
{
Console.WriteLine("Thread {0} begins " +
"and waits for the semaphore.", num);
semaphore.WaitOne();
Console.WriteLine("Thread {0} enters the semaphore.", num);
Thread.Sleep(1000);
Console.WriteLine("Thread {0} got released from the semaphore.", num);
semaphore.Release();
}
public void ExcecuteSemaphore()
{
for (int i = 1; i <= 10; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(ThreadExcecute));
t.Start(i);
}
Console.WriteLine("Main thread exits.");
Thread.Sleep(500);
}
}
}
Points of Interest
Thread pooling now becomes efficient.