David Sherret
David Sherret

Reputation: 106820

Semaphore Block Main Process Instead of Multiple Threads

So according to MSDN, and many other places I've read, they use a semaphore and block within the individual threads, like so:

private static Semaphore _pool;
public static void Main()
{
    _pool = new Semaphore(0, 3);
    for(int i = 1; i <= 1000; i++)
    {
        Thread t = new Thread(new ParameterizedThreadStart(Worker));
        t.Start(i);
    }
}

private static void Worker(object num)
{
    try
    {
        _pool.WaitOne();
        // do a long process here
    }
    finally
    {
        _pool.Release();
    }
}

Wouldn't it make more sense to block the process so that you don't create potentially 1000s of threads all at once depending on the number of iterations in Main()? For example:

private static Semaphore _pool;
public static void Main()
{
    _pool = new Semaphore(0, 3);
    for(int i = 1; i <= 1000; i++)
    {
        _pool.WaitOne(); // wait for semaphore release here

        Thread t = new Thread(new ParameterizedThreadStart(Worker));
        t.Start(i);
    }
}

private static void Worker(object num)
{
    try
    {
        // do a long process here
    }
    finally
    {
        _pool.Release();
    }
} 

Maybe both ways are not wrong and it depends on the situation? Or there is a better way to do this once there are a lot of iterations?

Edit: This is a windows service, so I'm not blocking the UI thread.

Upvotes: 0

Views: 796

Answers (1)

Scott Chamberlain
Scott Chamberlain

Reputation: 127603

The reason you would normally do it inside the thread is you want to make that exclusive section as small as possible. You don't need the entire thread synchronized, only where that thread accesses the shared resource.

So a more realistic version of Worker is

private static void Worker(object num)
{
    //Do a bunch of work that can happen in parallel
    try
    {
        _pool.WaitOne();
        // do a small amount of work that can only happen in 3 threads at once
    }
    finally
    {
        _pool.Release();
    }
    //Do a bunch more work that can happen in parallel
}

(P.S. If you are doing something that uses 1000 threads, you are doing something wrong. You should likely rather be using a ThreadPool or Tasks for many short-lived workloads or make each thread do more work.)


Here is how to do it with Parallel.ForEach

private static BlockingCollection<int> _pool;
public static void Main()
{
    _pool = new BlockingCollection<int>();
    Task.Run(() => //This is run in another thread so it shows data is being taken out and put in at the same time
    {
        for(int i = 1; i <= 1000; i++)
        {
            _pool.Add(i);
        }
        _pool.CompleteAdding(); //Lets the foreach know no new items will be showing up.
    });

    //This will work on the items in _pool, if there is no items in the collection it will block till CompleteAdding() is called.
    Parallel.ForEach(_pool.GetConsumingEnumerable(), new ParallelOptions {MaxDegreeOfParallelism = 3}, Worker);
}

private static void Worker(int num)
{
        // do a long process here
} 

Upvotes: 2

Related Questions