C # multithreading practices-synchronization system,

Source: Internet
Author: User

C # multithreading practices-synchronization system,

Lock Statement (that is, Monitor. enter/Monitor. exit) is usually used when a thread synchronizes exclusive access to a piece of code or resources, however, it is complicated to implement synchronization in complex application scenarios such as sending signals to the waiting worker thread to start task execution. The. NET framework provides EventWaitHandle, Mutex, and Semaphore classes for building a wide range of synchronization systems. For example, when MutexEventWaitHandle provides a unique signal function, it will multiply the lock efficiency. These three classes depend on the WaitHandle class. Although they have different functions, they can all bypass the operating system process, rather than bypassing the thread in the current process.

EventWaitHandle has two subclasses:AutoResetEventAndManualResetEvent(Events or delegation in C # are not involved ). The difference between the two classes is that the base class constructor is called with different parameters. In terms of performance, the use of Wait Handles consumes system overhead in microseconds and does not significantly affect the use of them in the context. AutoResetEvent is the most useful class in WaitHandle. it, together with the lock statement, is a major synchronization structure.

AutoResetEvent

  AutoResetEvent is like a revolving door passed by a ticket: Insert a ticket so that the right person can pass the ticket. In the class name, "auto" is actually a revolving door that is automatically closed or "rescheduled" and later people let it pass. A thread waits for or prevents the WaitOne method from being called on the door (it is not opened until this "one" method is called). The insertion of the ticket is called by the Set method. If WaitOne is called by many threads, a queue is formed in front of the door. A ticket may come from any thread-in other words, any (not blocked) the thread needs to call the Set method through the AutoResetEvent object to release a blocked thread.

That is, all threads that call the WaitOne method will block a waiting queue, and other non-blocking threads will release a blocking by calling the Set method. Then AutoResetEvent continues to block the subsequent threads.

If no thread is waiting for the Set call, the handle remains open until a thread calls WaitOne. This behavior avoids getting up at the thread to rotate the door and insert a ticket to the thread (Oh, inserting a ticket is a very short matter of microseconds. Unfortunately, you will have to wait uncertain !) Competition. But when no one waits, repeatedly calling the Set Method on the door won't allow a team to pass. When they arrive, only one person can pass, all the extra tickets are "wasted ".

WaitOne accepts an optional timeout parameter-This method returns false when the wait period ends with a timeout period, and WaitOne also notifies you to leave the current synchronization content while waiting for the entire period of time, in order to avoid excessive blocking.

The Reset function is to close the revolving door, that is, whether or not it has been set at this time, it will block the next WaitOne-it should be open.

AutoResetEvent can be created in two ways. The first is through the constructor:

EventWaitHandle wh = new AutoResetEvent (false);

If the Boolean parameter is true, the Set method is automatically called immediately after construction. That is to say, the first WaitOne will be released and will not be blocked. The other method is to use its base class EventWaitHandle:

EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto);

The constructor of EventWaitHandle can also create ManualResetEvent (defined by EventResetMode. Manual ).

When Wait Handle is not needed, you should call the Close method to release operating system resources. However, if a Wait Handle will be used in the life cycle of a program (as in most cases in this section), you can leave this step to lazy, it will be automatically destroyed when the program domain is destroyed.

In this example, one thread starts to wait until another thread sends a signal.

Class BasicWaitHandle {static EventWaitHandle wh = new AutoResetEvent (false); static void Main () {new Thread (Waiter ). start (); Thread. sleep (1000); // wait for a while... wh. set (); // OK -- wake it up} static void Waiter () {Console. writeLine ("Waiting... "); wh. waitOne (); // wait for the notification Console. writeLine ("Notified") ;}} Waiting... (pause) Notified.

 

Create an EventWaitHandle across processes

The EventWaitHandle constructor allows you to create an EventWaitHandle by name. It has the ability to span multiple processes. The name is a simple string and may conflict with others unintentionally! If the name is used, you will reference the same potential EventWaitHandle unless the operating system creates a new one. See this example:

EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto,"MyCompany.MyApp.SomeName");

If two programs run this code, they can send signals to each other, waiting for the handle to span all threads in the two processes.

 

Task confirmation

Imagine we want to complete the task in the background, but we don't create another thread every time we get the task. We can do this through a polling thread: Wait for a task, execute it, and then wait for the next task. This is a general multi-threaded solution. That is, internal operations are split on the Creation thread, task execution is serialized, and potential unwanted operations are excluded between multiple working threads and excessive resource consumption. We have to decide what to do. However, if a new task comes, the worker thread is already busy with the previous task, in this case, we need to choose to block callers until the previous task is completed. A system like this can be implemented using two AutoResetEvent objects: A "ready" AutoResetEvent, which is called by the worker thread when the system is ready; and a "go" AutoResetEvent, when a new task exists, it is called by the thread to call the Set method. In the following example, a simple string field is used to determine the task (the volatile keyword declaration is used to ensure that both threads can see the same version ):

class AcknowledgedWaitHandle {  static EventWaitHandle ready = new AutoResetEvent (false);  static EventWaitHandle go = new AutoResetEvent (false);  static volatile string task;    static void Main() {    new Thread (Work).Start();      // Signal the worker 5 times    for (int i = 1; i <= 5; i++) {      ready.WaitOne();                // First wait until worker is ready      task = "a".PadRight (i, 'h');   // Assign a task      go.Set();                       // Tell worker to go!    }      // Tell the worker to end using a null-task    ready.WaitOne(); task = null; go.Set();  }    static void Work() {    while (true) {      ready.Set();                          // Indicate that we're ready      go.WaitOne();                         // Wait to be kicked off...      if (task == null) return;             // Gracefully exit      Console.WriteLine (task);    }  }}  ahahhahhhahhhh

Note: Assign null to the task to notify the worker thread to exit. Calling Interrupt or Abort on the worker thread has the same effect, if we call ready. WaitOne first. Because after calling ready. WaitOne, we know the exact location of the working thread, not just before the go. WaitOne statement, thus avoiding the complexity of interrupting arbitrary code. To call Interrupt or Abort, We need to capture exceptions in the work thread.

 

Producer/consumer queue

Another common thread solution is to allocate tasks from the queue in the background worker process. This is called producer/consumer queue: In the Work thread, the producer columns the task and the consumer columns the task. This is similar to the previous example, except that the caller is not blocked when the worker thread is busy with a task.

Producer/consumer queues can be scaled because multiple consumers may be created-each of them serves the same queue, but a separate thread is enabled. This is a good way to use a multi-processor system to limit the number of working threads, which has always avoided the defects of a great number of concurrent threads (excessive Content Switching and resource connections ).

In the following example, a separate AutoResetEvent is used to notify the worker thread that it waits only when the task is used up (the queue is empty. A general collection class is used in the queue and must pass the lock

Control its access to ensure thread security. The worker thread ends when the queue is null:

using System;using System.Threading;using System.Collections.Generic;  class ProducerConsumerQueue : IDisposable {  EventWaitHandle wh = new AutoResetEvent (false);  Thread worker;  object locker = new object();  Queue<string> tasks = new Queue<string>();    public ProducerConsumerQueue() {    worker = new Thread (Work);    worker.Start();  }    public void EnqueueTask (string task) {    lock (locker) tasks.Enqueue (task);    wh.Set();  }    public void Dispose()  {        EnqueueTask (null);     // Signal the consumer to exit.        worker.Join();          // Wait for the consumer's thread to finish.       wh.Close();             // Release any OS resources.  }    void Work() {    while (true) {      string task = null;      lock (locker)        if (tasks.Count > 0) {          task = tasks.Dequeue();          if (task == null) return;        }      if (task != null) {        Console.WriteLine ("Performing task: " + task);        Thread.Sleep (1000);  // simulate work...      }      else        wh.WaitOne();         // No more tasks - wait for a signal    }  }}//Here's a main method to test the queue: class Test {  static void Main() {    using (ProducerConsumerQueue q = new ProducerConsumerQueue()) {      q.EnqueueTask ("Hello");      for (int i = 0; i < 10; i++) q.EnqueueTask ("Say " + i);      q.EnqueueTask ("Goodbye!");    }    // Exiting the using statement calls q's Dispose method, which    // enqueues a null task and waits until the consumer finishes.  }}

Performing task: Hello 
Performing task: Say 1 
Performing task: Say 2 
Performing task: Say 3 
... 
... 
Performing task: Say 9 
Goodbye!

Note that we explicitly disable Wait Handle when ProducerConsumerQueue is destroyed, because in the life cycle of the program, we may potentially create and destroy many instances of this class.

 

ManualResetEvent

ManualResetEvent is a form of AutoResetEvent change. The difference is that it does not automatically reset when the thread is passed by the WaitOne call, this process is like the door-call Set to open the door, allowing any number of WaitOne threads to pass through; call Reset to close the door, it may cause a series of "Waiting" until the next door opens.

You can use a Boolean field "publish open" (declared with the volatile keyword) and "spin-sleeping"-to combine -- repeat the flag and then let the thread sleep for a period of time, to simulate this process.

ManualResetEvent is sometimes used to send signals to a completed operation, or to a thread that has been initialized and is preparing for execution.

 

Mutex)

Mutex provides the same functions as the lock Statement of C #, which makes it redundant most of the time. Its advantage is that it can work across processes-providing a computer-wide lock over a program-wide lock.

Mutex is quite fast, and lock is hundreds of times faster than it. It takes several microseconds to get Mutex, and it takes several dozen nanoseconds to get lock (if not blocked ).

For a Mutex class, WaitOne acquires the Mutex lock and blocks it when it is preemptible. Mutex is released after ReleaseMutex is executed, just like the lock Statement of C #, Mutex only

Can be released from the thread that gets the mutex lock.

Mutex is widely used across processes to ensure that only one program instance is running at the same time. The following shows how to use it:

class OneAtATimePlease {  // Use a name unique to the application (eg include your company URL)  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");     static void Main() {    // Wait 5 seconds if contended – in case another instance    // of the program is in the process of shutting down.      if (!mutex.WaitOne (TimeSpan.FromSeconds (5), false)) {      Console.WriteLine ("Another instance of the app is running. Bye!");      return;    }    try   {      Console.WriteLine ("Running - press Enter to exit");      Console.ReadLine();    }    finally { mutex.ReleaseMutex(); }  }}

Mutex has a good feature: If the Mutex lock fails to pass ReleaseMutex when the program ends, the CLR will automatically release the Mutex.

 

Semaphore

Semaphore is like a nightclub: it has a fixed capacity, which is guaranteed by the guard. Once it is full, no one can enter the nightclub and a queue will be formed outside it. Then, when a person leaves, the person in the queue can enter. The constructor requires at least two parameters: the activity space of the nightclub and the size of the nightclub.

The features of Semaphore are similar to those of Mutex and lock. Except for Semaphore, there is no "owner"-it is an unknown thread. Any thread in Semaphore can call Release, mutex and lock can be released only by the threads that have obtained the resource.

In the following example, 10 threads execute a loop and use the Sleep statement in the middle. Semaphore ensures that only three threads can execute the Sleep statement at a time:

class SemaphoreTest {  static Semaphore s = new Semaphore (3, 3);  // Available=3; Capacity=3    static void Main() {    for (int i = 0; i < 10; i++) new Thread (Go).Start();  }    static void Go() {    while (true) {      s.WaitOne();      Thread.Sleep (100);   // Only 3 threads can get here at once      s.Release();    }  }}

WaitAny, WaitAllAnd SignalAndWait

 

In addition to the Set and WaitOne methods, there are also some static methods used to create complex synchronization procedures in the WaitHandle class.

WaitAny, WaitAll, and SignalAndWait make it easy to span multiple wait handles that may be of different types.

SignalAndWait is probably the most useful: it calls WaitOne on a WaitHandle and automatically calls Set on another WaitHandle. You can assemble two threads on a pair of EventWaitHandle to make them "encounter" at a certain point in time. AutoResetEvent or ManualResetEvent cannot be used. The first thread is like this:

WaitHandle. SignalAndWait (wh1, wh2 );

At the same time, the second thread does the opposite:

WaitHandle. SignalAndWait (wh2, wh1 );

WaitHandle. WaitAny waits for any signal from a group of pending handles, and WaitHandle. WaitAll waits for signals from all given handles. Similar to the bill revolving door example, these methods may simultaneously wait for all revolving doors-by the first open (WaitAny case ), or wait until all of them are opened (WaitAll ).

WaitAll is actually an uncertain value, because it has a strange connection with the issues left over from the COM system in the unit mode thread. WaitAll requires the caller to be a multi-threaded unit. Coincidentally, the Unit mode is the most suitable. Especially in Windows Forms programs, it is as vulgar to execute tasks as they are combined with clipboard!

Fortunately, when waiting for a handle to be difficult to use or unsuitable, the. NET framework provides a more advanced signal structure-Monitor. Wait and Monitor. Pulse.

Related Article

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.