Deep multithreading: deep analysis of producer and consumer queues

Source: Internet
Author: User

The last time we used AutoResetEvent to implement a production/consumer queue. This time we will use the Wait and Pulse methods to implement a more powerful version, which allows multiple consumers, each running in their own thread.

We use arrays to track threads.

Thread [] _ workers;

Through the tracking thread, we can end our queue tasks after all threads are finished.

Every consumer thread executes a method called Consume. In a for loop, we can create and start a thread. For example:

Copy codeThe Code is as follows: public PCQueue (int workerCount)
{
_ Workers = new Thread [workerCount];
For (int I = 0; I <workerCount; I ++)
(_ Workers [I] = new Thread (Consume). Start ();
}

The last time we used a string to represent the task, this time we used the Action delegate. Its definition is as follows:

Public delegate void Action ();

To represent a series of tasks, we use the Queue <T> set, for example:

Queue <Action >_itemq = new Queue <Action> ();

Before calling the production (EnqueueItem) and consumption (Consume) methods, let's take a complete look at the Code:

Copy codeThe Code is as follows: class PCQueue
{
Readonly object _ locker = new object ();
Thread [] _ workers;
Queue <Action> _ itemQ = new Queue <Action> (); // Save the Queue of the task
Public PCQueue (int workerCount)
{
_ Workers = new Thread [workerCount];
For (int I = 0; I <workerCount; I ++)
(_ Workers [I] = new Thread (Consume). Start ();
}

Public void Shutdown (bool waitForWorkers)
{
// Insert a null item for each thread. Every worker can exit.
Foreach (Thread worker in _ workers)
EnqueueItem (null );

// Wait until all threads exit.
If (waitForWorkers)
Foreach (Thread worker in _ workers)
Worker. Join ();
}

Public void EnqueueItem (Action item)
{
Lock (_ locker)
{
_ ItemQ. Enqueue (item );
Monitor. Pulse (_ locker); // notify the threads in the waiting queue
}
}

Void Consume ()
{
While (true)
{
Action item;
Lock (_ locker)
{
While (_ itemQ. Count = 0)
{
Monitor. Wait (_ locker); // release the lock and stop the current thread until other threads send the pulse signal. }
Item = _ itemQ. Dequeue ();
}

If (item = null) return; // exit Signal
Item ();
}
}
}

We can have an exit policy, insert a null item as the signal for the consumer to exit. If we want to exit quickly, we can use an independent "cancel" mark. Because we support multiple consumers, we must insert a null item for each consumer.

The following is the Main method. Use two consumer threads, and then let the two consumers execute 10 delegates.

Copy codeThe Code is as follows: public static void Main ()
{
PCQueue q = new PCQueue (2 );
Console. WriteLine ("Enqueuing 10 items ...");

For (int I = 0; I <10; I ++)
{
Int itemNumber = I;
Q. EnqueueItem () =>
{
Thread. Sleep (1000); // simulate time-consuming work
Console. WriteLine ("Task" + itemNumber );
});
}

Q. Shutdown (true); // wait for Shutdown
Console. WriteLine ();
Console. WriteLine ("Workers complete! ");
}

Let's take a closer look at the EnqueueItem method:

Copy codeThe Code is as follows: public void EnqueueItem (Action item)
{
Lock (_ locker)
{
_ ItemQ. Enqueue (item );
Monitor. Pulse (_ locker); // notify the threads in the waiting queue
}
}

Because our queue _ itemQ is used in multi-threaded environments, we need to lock the _ itemQ when reading it.

Because we have inserted a new task, we must modify the blocking condition, that is, call the pulse method to wake up the thread that calls the wait method.

For efficiency consideration, the Pulse method is used to replace the PulseAll method when inserting an Item, because in most cases, only one consumer is required for each Item. If you have an ice cream, you cannot ask 30 sleep children to get up and eat it. Similarly, it is no good for an item to wake up 30 consumers at the same time.

Let's take a look at the Consumer method.

We hope that when there is nothing to do, the thread can be blocked. In other words, when there is no item in the queue, the thread should be blocked. Therefore, our blocking condition is _ itemQ. Count = 0;

Copy codeThe Code is as follows: Action item;
Lock (_ locker)
{
While (_ itemQ. Count = 0)
{
Monitor. Wait (_ locker); // release the lock and stop the current thread until other threads send the pulse signal. }
Item = _ itemQ. Dequeue ();
}

If (item = null) return; // exit Signal
Item ();

When the while LOOP exits, it also means that _ itemQ has at least one item. You must call the dequeue method to obtain the item before releasing the lock. Consider the following code:Copy codeThe Code is as follows: lock (_ locker)
{
While (_ itemQ. Count = 0)
{
Monitor. Wait (_ locker); // release the lock and stop the current thread until other threads send the pulse signal. }
}
// It may be preemptible now, And _ itemQ may be modified
Lock (_ locker)
{
Item = _ itemQ. Dequeue ();
}

After the item is Dequeued, we should release the lock immediately. If we keep holding the lock while executing the task, there is no need to block other threads to obtain the task.

Wait Timeouts

When you call the Wait method, you can pass a millisecond or Timespan time to set the timeout. If Wait times out, the Wait method returns false.

The main steps of the Wait method with the timeout function are as follows:

Release the lock.
Blocking until pulsed or timeout.
Obtain the lock again.

Timeout is like when the CLR times out, the pulse method is automatically called.

The following is the main code for timeout Wait:

Lock (_ locker)

While (<blocking condition>)

Monitor. Wait (_ locker, <timeout> );

The Monitor. Wait method returns a bool value to indicate whether the pulse is called or has timed out.

True indicates that pulse is called.

If it is false, it indicates that the request has timed out.

This is useful for logging.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.