0

Need suggestion for best approach for Multi-threading in c# 3.0 (No Parallel or Task)

The situation is, I have a Queue with 500 items. At a particular time I can run only 10 threads (Max). Below is my code.

While (queue.Count > 0)
{
Thread[] threads = new Thread[no_of_threads];
for (int j = 0; j < no_of_threads; j++)
 {
   threads[j] = new Thread(StartProcessing);//StartProcessing Dequeue one item each time //for a single thread
   threads[j].Start();
 }

 foreach (Thread objThread in threads)
 {
   objThread.Join();
 }
}

Problem in this approach is, for an instance, if no_of_threads = 10 and out of them 9 threads are done with processing, and 1 thread is still working, I cannot come out of loop and delegate work to the free threads until all 10 threads are done.

I need at all the time 10 threads should work till the queue count > 0.

Dustin Kingen
  • 20,677
  • 7
  • 52
  • 92
SAM
  • 159
  • 1
  • 2
  • 12
  • TPL is available in .NET 3.5. [Task Parallel Library for .NET 3.5](http://nuget.org/packages/TaskParallelLibrary/) – Dustin Kingen May 06 '13 at 16:47
  • Creating 1 thread to dequeue 1 item is not very efficient anyway. There is no need to Join() here, think of a better control flow. – H H May 06 '13 at 16:53

5 Answers5

4

This is easily done with a Semaphore.

The idea is to create a semaphore with a maximum count of N, where N is the number of threads you allow. The loop waits on the semaphore and queues tasks as it acquires the semaphore.

Semaphore ThreadsAvailable = new Semaphore(10, 10);
while (Queue.Count > 0)
{
    ThreadsAvailable.WaitOne();
    // Must dequeue item here, otherwise you could run off the end of the queue
    ThreadPool.QueueUserWorkItem(DoStuff, Queue.Dequeue());
}

// Wait for remaining threads to finish
int threadCount = 10;
while (threadCount != 0)
{
    ThreadsAvailable.WaitOne();
    --threadCount;
}


void DoStuff(object item)
{
    ItemType theItem = (ItemType)item;
    // process the item
    StartProcessing(item);
    // And then release the semaphore so another thread can run
    ThreadsAvailable.Release();
}

The item is dequeued in the main loop because that avoids a race condition that otherwise is rather messy to handle. If you let the thread dequeue the item, then the thread has to do this:

lock (queue)
{
    if (queue.Count > 0)
        item = queue.Dequeue();
    else
        // There wasn't an item to dequeue
        return;
}

Otherwise, the following sequence of events is likely to occur when there is only one item left in the queue.

main loop checks Queue.Count, which returns 1
main loop calls QueueUserWorkItem
main loop checks Queue.Count again, which returns 1 because the thread hasn't started yet
new thread starts and dequeues an item
main loop tries to dequeue an item and throws an exception because queue.Count == 0

If you're willing to handle things that way, then you're okay. The key is making sure that the thread calls Release on the semaphore before the thread exits. You can do that with explicitly managed threads, or with the ThreadPool approach that I posted. I just used ThreadPool because I find it easier than explicitly managing threads.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Thanks for the approach, few more doubts. I need to pick the item from Queue for passing to DoStuff. For that purpose I need to LOCK the Queue to ensure the same items is not being picked up from queue by multiple threads, how can that be achieved in case of ThreadPool? – SAM May 06 '13 at 17:58
  • @SAM In this code the thread is only ever access from one thread; the main thread, which is effectively a scheduler, and as such there is no need to synchronize access to the queue. – Servy May 06 '13 at 18:24
1

You should use ThreadPool which manages and optimizes threads for you

Once a thread in the pool completes its task, it is returned to a queue of waiting threads, where it can be reused. This reuse enables applications to avoid the cost of creating a new thread for each task.

Thread pools typically have a maximum number of threads. If all the threads are busy, additional tasks are put in queue until they can be serviced as threads become available.

It's better not to interfere into ThreadPool since it's enough smart to manage and allocate threads. But if you really need to do this, you can set the constraint of the maximum number of threads by using SetMaxThreads method

Community
  • 1
  • 1
cuongle
  • 74,024
  • 28
  • 151
  • 206
  • Ok, but how do you constrain it to ten threads, and how do you add new threads as each one completes? Without additional guidance, I think the OP already has this with his Thread array. – Robert Harvey May 06 '13 at 16:33
  • @RobertHarvey - I'm guessing the thought here is that the OP is limiting to 10 threads at a time for performance reasons. The ThreadPool would do this work and optimize everything instead of the OP having to manage that on his own. – Justin Niessner May 06 '13 at 16:35
  • 1
    @JustinNiessner While sometimes the constraint on number of threads is just the OP asking for what they think is best, and the thread pool would be smarter, sometimes it really is better to have a specified number of threads, sometimes because you know more than the thread pool does, and sometimes because the thread pool is being utilized by another aspect of the program and you need to separate the pools. – Servy May 06 '13 at 16:36
  • Note that your program might not be the only thing that is using the ThreadPool. Any libraries that you use might also be affected by limiting the number of threads, if they also use the ThreadPool. – Robert Harvey May 06 '13 at 16:38
  • I tried using ThreadPool, but the number of threads created is more than what is set through SetMaxThreads. And I need to limit it to max 10 threads only, as my third-party software is licensed for 10 threads only. Any clues why the threadpool is not limiting to this count? or any other suggestion? – SAM May 06 '13 at 17:28
1

So all you need to handle this is a queue that is designed to be accessed from multilpe threads. Were you using .NET 4.0 I'd say use BlockingCollection. Not only will it work perfectly, but it's very efficient. You can rather trivially make your own class that is just a Queue with lock calls around all of the methods. It will work about as well, but it won't be as efficient. (It will likely be efficient enough for your purposes though, and a re-writing BlockingCollection "properly" would be quite hard.)

Once you have that queue each worker can just grab an item from that queue, process it, then ask the queue for another. When there are no more you don't need to worry about ending that thread; there's no more work it could do.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Actually the queue only holds some batch_id, based on that ID, i need to do some process (say generating documents from images). I am using C# 3.0, I believe this feature is not available.. Anyways thanks for help, I will definitely explore BlockingCollection. – SAM May 06 '13 at 20:44
  • @SAM As I said in my post, you don't have access to `BlockingCollection`, but you can emulate the effect through just using `Queue` and some locks, which you *do* have access to. It won't be quite as efficient, but it will be efficient enough, and it will work. – Servy May 06 '13 at 20:46
  • And work quite well, actually. You start N threads and let them go until the queue is empty. – Jim Mischel May 06 '13 at 22:13
0

This is a simple producer-consumer scenario. You need a thread-safe queue like this one: Creating a blocking Queue<T> in .NET? - 10 threads can read and process job by job in a loop until the queue is empty. Depending on how you fill the queue (prior to starting processing it or while processing it) you can end those threads as soon as the queue becomes empty or when you signal it to stop by means of a stop flag. In the latter case you probably need to wake the threads (eg with dummy jobs).

Community
  • 1
  • 1
JeffRSon
  • 10,404
  • 4
  • 26
  • 51
  • BlockingCollection isn't available in c# 3.0. It was added 4.0. Thankfully so, it's makes this problem so much easier to deal with. – cgotberg May 06 '13 at 16:33
  • `BlockingCollection` doesn't exist in 3.0 – Servy May 06 '13 at 16:33
  • How would you dequeue a thread early? In other words, if the last thread placed into the queue completes first, wouldn't you want to consume the result first and free the slot for a new thread? – Robert Harvey May 06 '13 at 16:43
  • Each threads reads the queue in a loop, so as soon as it finishs a job it is ready for the next. The loop runs as long as a certain stop condition is false. Such you can end the threads by setting the stop condition. Of course you have to wake the threads by queuing dummy jobs. Simple producer-consumer scenario with lots of samples around. The point is to find or write the queue. – JeffRSon May 06 '13 at 16:46
0

Instead of controlling the threads from the outside, let each thread consume data itself.

Pseudocode:

create 10 threads

thread code:
    while elements in queue
    get element from queue
    process element
Femaref
  • 60,705
  • 7
  • 138
  • 176
  • note you must ensure that you properly synchronize access to the queue as there is no `ConcurrentQueue` in .NET 3.0 – Servy May 06 '13 at 16:34