Wait signal and trigger signal-signaling with wait and Puls
Before discussing the event wait handle-a simple signaling mechanism, one thread keeps blocking until it receives a notification from another thread.
A more powerful signaling mechanism is provided by the Monitor class via static function wait and Pluse (and PulseAll). You write your own notification logic, use custom tags and fields (plus locks), and then introduce the wait and Pluse commands to block spin. With the lock statement and these functions, you can complete the functions of Autoresetevent,manualreseteven and Semaplore, as well as the functions of WaitHandle static functions WaitAll and WaitAny. As a result, wait and pluse can be used where wait handles are used.
Wait and pluse signals are less than waiting event handles:
- Wait/pluse cannot span application domains or applications.
- You are more protective of all variables associated with the signal logic
- Wait/pluse programming may be confusing to developers who rely on Microsoft documentation.
There is no obvious way to tell you how to use wait and pluse in a document, even if you've read how they work. Wait and pluse are especially annoying for amateurs. Fortunately, there is a very simple pattern to tame wait and pluse.
The pluse consumes about 100ns, which is One-third of the call set function. It's up to you to decide when you're not competing for a signal-because you've made your own logic.
How to use Wait and Pluse
- Defines a familiar use for synchronizing objects, such as: readonly object _locker = new Object ();
- Define some fields for custom blocking conditions, such as: BOOL _go; or int _semaphorecount;
- Use the following code when you want to block: Lock (_locker) while (<blocking-condition>) monitor.wait (_locker);
- Use the following code to change the blocking condition: Lock (_locker) {< alter the field (s) or data that might impact the blocking condition (s) >; Monitor.pluse (_locker);/*or:monitor.pluseall (_locker); */}
This way allows you to allow any thread to wait any time for any condition. Here is a simple example of a worker thread waiting until the _go field is true:
classsimplewaitpluse{Static Object_locker =New Object(); Static BOOL_go; Static voidMain () {NewThread (work). Start (); Console.ReadLine (); Lock(_locker) {_go=true; Monitor.pluse (_locker); } } Static voidWork () {Lock(_locker) { while(!go) Monitor.Wait (_locker); } Console.WriteLine ("woken!"); }}
For thread safety, you must ensure that all shared fields are locked. Therefore, a row of lock statements is added around the read/update _go tag. This is critical (unless you want to use the non-blocking synchronization principle).
The work function blocks until _go is true. Monitor.Wait did the following things, in order:
- Releases the lock on the _locker object.
- Block until _lcoker is triggered.
- Re-acquires the lock on the _locker object. If it is competitive, it will block until it is ready to use.
Monitor.Wait requirements are used within the lock statement, otherwise an exception will still be in place. The same is true for Monitor.pluse.
In main, the worker thread is notified by setting _go to True and calling Pluse. As soon as a release lock is released, the worker thread executes immediately.
Pluse and Pluseall release the threads that are blocking on wait. Pluse releases a maximum of one thread; Pluse releases all. In this example, only one thread is blocked. If more than one thread calls Pluseall with our recommendations, it is more secure.
In order for wait to communicate with Pluse or Pluseall, the synchronization object must be the same (this example is _locker).
In our mode, the trigger (Pluse) indicates that something has changed, and the waiting thread must recheck the blocking condition. The work worker thread is checked through a while loop. The waiting person then decides whether to continue.
Remove the while loop to get an example of a skinny bone:
classsimplewaitpluse{Static Object_locker =New Object(); Static BOOL_go; Static voidMain () {NewThread (work). Start (); Console.ReadLine (); Lock(_locker) {_go=true; Monitor.pluse (_locker); } } Static voidWork () {Lock(_locker) {monitor.wait (_locker); } Console.WriteLine ("woken!"); }}
Its output is indeterminate. If wait executes first, it works fine. If the Pluse line executes, then this notification will be lost and the worker thread will always be stuck. This is different from AutoResetEvent, where the SET statement will have a block of memory and lock the effect, so even calling it before WaitOne is valid.
Pluse does not lock itself, so it must be tagged with go. This is the talent of wait and pluse: we use a bool tag that allows him to work like AutoResetEvent; With an integer field, you can write a countdownevent seamphore. With more data structures, a production/consumer queue can be written.
Production/Consumer Queue
The concept of the production/consumer queue has been described earlier, and how to write using AutoResetEvent. Now use wait and pluse to write more powerful production/consumer queues. This time allows any number of work items, each with its own thread. Use arrays to track threads, which allows you to use the Join option when you close a column.
Each worker thread executes a consume function. Create and start these threads in a loop. A more flexible approach, rather than a string, will be used to describe a task. Using the System.Action delegate, it can match any parameter method, unlike a ThreadStart delegate. A method with parameters is still called to represent the task by encapsulating it in an anonymous function or lambda expression. Use the Queue<t> collection to represent the queue for the task.
The following is the complete code:
usingSystem;usingSystem.Threading;usingSystem.Collections.Generic; Public classPcqueue {ReadOnly Object_locker =New Object(); Thread[] _workers; Queue<Action> _ITEMQ =NewQueue<action>(); PublicPcqueue (intWorkercount) {_workers=NewThread [Workercount]; //Create and start a separate thread for each worker for(inti =0; i < Workercount; i++) (_workers [i]=NewThread (consume)). Start (); } Public voidShutdown (BOOLwaitforworkers) { //Enqueue One null item per worker to do each exit. foreach(Thread workerinch_workers) Enqueueitem (NULL); //Wait for workers to finish if(waitforworkers)foreach(Thread workerinch_workers) worker. Join (); } Public voidEnqueueitem (Action item) {Lock(_locker) {_itemq.enqueue (item); //We must pulse because we ' reMonitor.pulse (_locker);//changing a blocking condition. } } voidconsume () { while(true)//Keep consuming until{//told otherwise.Action Item; Lock(_locker) { while(_itemq.count = =0) monitor.wait (_locker); Item=_itemq.dequeue (); } if(Item = =NULL)return;//This is signals our exit.Item ();//Execute Item. } } }
Thread of the C # Learning Note-Advanced topic: wait and trigger signal