Exploring the multi-thread mechanism of c #

Source: Internet
Author: User
As you can see, in the above routine, synchronization is completed by waiting for monitor. Pulse. First, the producer produces a value, while the consumer is in the waiting state at the same time until it receives the "pulse" notification from the producer that production has been completed, and then the consumer enters the consumption state, the producer will call monitor after waiting for the consumer to complete the operation. the "pulse" emitted by pulese ". The execution result is simple:

Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20

In fact, this simple example has helped us solve multithreading applications.ProgramAs long as you understand the basic method for solving inter-thread conflicts, it is easy to apply it to complicated programs.

4. Thread Pool and timer-Automatic Management of Multithreading
In multi-threaded programs, there are often two situations. In one case, the threads in the application spend most of the time waiting for the status, waiting for an event to happen before responding; in another case, the thread is usually in sleep state, but is periodically awakened. In the. NET Framework, we use threadpool to deal with the first case, and timer to deal with the second case.

The threadpool class provides a thread pool maintained by the system-a container that can be considered as a thread. This container must be supported by Windows 2000 and later versions, some of these methods call APIs that are only available in later versions of Windows. You can use the threadpool. queueuserworkitem () method to place the thread in the thread pool. The prototype of this method is as follows:

// Put a thread into the thread pool. The start () method of the thread calls the Function Represented by the waitcallback proxy object.
Public static bool queueuserworkitem (waitcallback );
// The reload method is as follows. The parameter object is passed to the method represented by waitcallback.
Public static bool queueuserworkitem (waitcallback, object );

It should be noted that the threadpool class is also a static class, and you cannot and do not need to generate its objects. Once you use this method to add a project to the thread pool, this project cannot be canceled. Here, you don't need to create a thread on your own. You just need to write the work you want to do as a function and pass it to threadpool as a parameter. the queueuserworkitem () method is used. The passed method relies on the waitcallback proxy object, and the thread creation, management, and running tasks are automatically completed by the system, you don't need to consider the complicated details. The advantages of the thread pool are shown here, as if you are a company boss-you only need to arrange work, instead of doing it yourself.
The following routine demonstrates the usage of threadpool. First, the program creates a manualresetevent object, which is like a signal lamp and can be used to notify other threads. In this example, when all the threads in the thread pool are finished, the manualresetevent object will be set as a signal to notify the main thread to continue running. It has several important methods: reset (), set (), waitone (). When initializing this object, you can specify its default state (with signal/without signal). After initialization, this object will remain unchanged until its reset () or the Set () method is called. The reset () method sets it to the signal-less state, and the Set () method sets it to the signal state. The waitone () method suspends the current thread until the manualresetevent object is in a signal state and the thread is activated. The program then adds work items to the thread pool. The work items provided in the form of functions are used by the system to initialize automatically created threads. Manualresetevent. the Set () method is called because manualresetevent is called. the main thread waiting for the waitone () method will receive this signal, so it will continue to execute and complete the subsequent work.

Using system;
Using system. collections;
Using system. Threading;

// This is the data structure used to save information and will be passed as a parameter
Public class somestate
{
Public int cookie;
Public somestate (INT icookie)
{
Cookie = icookie;
}
}

Public Class Alpha
{
Public hashtable hashcount;
Public manualresetevent eventx;
Public static int icount = 0;
Public static int imaxcount = 0;
Public alpha (INT maxcount)
{
Hashcount = new hashtable (maxcount );
Imaxcount = maxcount;
}

file: // the thread in the thread pool calls the beta () method.
Public void beta (object state)
{< br> // output the hash code value and cookie value of the current thread
console. writeline ("{0} {1}:", thread. currentthread. gethashcode (),
(somestate) State ). cookie);
console. writeline ("hashcount. count = {0}, thread. currentthread. gethashcode () = {1} ", hashcount. count, thread. currentthread. gethashcode ();
lock (hashcount)
{< br> file: // if the current hash table does not have the current line If (! Hashcount. containskey (thread. currentthread. gethashcode ()
hashcount. add (thread. currentthread. gethashcode (), 0);
hashcount [thread. currentthread. gethashcode ()] =
(INT) hashcount [thread. currentthread. gethashcode ()]) + 1;
}

Int IX = 2000;
Thread. Sleep (IX );
// Interlocked. increment () is an atomic operation. For details, refer to the following description.
Interlocked. increment (ref icount );
If (icount = imaxcount)
{
Console. writeline ();
Console. writeline ("setting eventx ");
Eventx. Set ();
}
}
}

Public class simplepool
{
Public static int main (string [] ARGs)
{
Console. writeline ("Thread Pool sample :");
Bool w2k = false;
Int maxcount = 10; // allows a maximum of 10 threads to run in the thread pool
// Create a manualresetevent object and initialize it to a stateless state.
Manualresetevent eventx = new manualresetevent (false );
Console. writeline ("Queuing {0} items to thread pool", maxcount );
Alpha oalpha = new alpha (maxcount); file: // create a work item
// Initialize the eventx attribute of the oalpha object
Oalpha. eventx = eventx;
Console. writeline ("queue to thread pool 0 ");
Try
{
File: // load the work item into the thread pool
File: // only APIs of Windows 2000 or later are used here, so the notsupportexception exception may occur.
Threadpool. queueuserworkitem (New waitcallback (oalpha. Beta ),
New somestate (0 ));
W2k = true;
}
Catch (notsupportedexception)
{
Console. writeline ("These API's may fail when called on a non-Windows 2000 system .");
W2k = false;
}
If (w2k) // if the current system supports the threadpool method.
{
For (INT iItem = 1; iItem <maxcount; iItem ++)
{
// Insert queue Elements
Console. writeline ("queue to thread pool {0}", iItem );
Threadpool. queueuserworkitem (New waitcallback (oalpha. Beta), new somestate (Iitem ));
}
Console. writeline ("waiting for thread pool to drain ");
File: // wait for the event to complete, that is, the thread calls the manualresetevent. Set () method.
Eventx. waitone (timeout. Infinite, true );
File: // The waitone () method causes the thread to wait until the eventx. Set () method is called.
Console. writeline ("thread pool has been drained (event fired )");
Console. writeline ();
Console. writeline ("load wait SS Threads ");
Foreach (Object o in oalpha. hashcount. Keys)
Console. writeline ("{0} {1}", O, oalpha. hashcount [O]);
}
Console. Readline ();
Return 0;

}
}

Some small points in the program should attract our attention. The somestate class is a data structure that stores information. In the above program, it is passed to every thread as a parameter, which you can easily understand, because you need to encapsulate some useful information and provide it to the thread, This method is very effective. The interlocked class of a program exists for multi-threaded programs. It provides some useful atomic operations. The so-called Atomic operations are in multi-threaded programs, if this thread calls this operation to modify a variable, other threads cannot modify the variable, which is essentially the same as the lock keyword.

We should thoroughly analyze the above program, grasp the essence of the thread pool, and understand its meaning so that we can use it with ease. The output result of the program is as follows:

Thread Pool sample:
Queuing 10 items to thread pool
Queue to thread pool 0
Queue to thread pool 1
...
...
Queue to thread pool 9
Waiting for thread pool to drain
98 0:
Hashcount. Count = 0, thread. currentthread. gethashcode () = 98
100 1:
Hashcount. Count = 1, thread. currentthread. gethashcode () = 100
98 2:
...
...
Setting eventx
Thread Pool has been drained (event fired)
Load Balance SS threads
101 2
100 3
98 4
102 1

Unlike the threadpool class, the Timer class is used to set a timer to regularly execute the user-specified function. The function is passed by another Proxy object timercallback, it must be specified when the timer object is created and cannot be changed. After the timer starts, the system automatically creates a new thread and executes the user-specified function in this thread. The following statement initializes a timer object:

Timer timer = new timer (timerdelegate, S, 1000,100 0 );

The first parameter specifies the timercallback proxy object. The second parameter indicates the same as the waitcallback proxy object mentioned above, and is passed to the method to be called as a data transmission object; the third parameter is the delay time-the time from which the timer starts, in milliseconds; the fourth parameter is the timer interval-after the timer starts, every so long period of time, timercallback indicates that the method will be called once in milliseconds. This statement sets the timer delay time and interval to one second.

The timer settings can be changed. You only need to call the timer. Change () method. This is a parameter type overload method. The general prototype is as follows:

Public bool change (Long, long );

The following sectionCodeModify the timer set in the front:

Timer. Change (timer, 2000 );

Obviously, the time interval of the timer is reset to 2 seconds. The timer takes effect after 10 seconds.

The following section demonstrates the usage of the Timer class.

Using system;
Using system. Threading;
Class timerexamplestate
{
Public int counter = 0;
Public timer TMR;
}

Class app
{
Public static void main ()
{
Timerexamplestate S = new timerexamplestate ();

// Create the proxy object timercallback, which will be called regularly
Timercallback timerdelegate = new timercallback (checkstatus );

// Create a timer with an interval of 1 s
Timer timer = new timer (timerdelegate, S, 1000,100 0 );
S. TMR = timer;

// The main thread stops and waits for the termination of the timer object
While (S. TMR! = NULL)
Thread. Sleep (0 );
Console. writeline ("timer example done .");
Console. Readline ();
}
File: // The following method is called regularly.

Static void checkstatus (object state)
{
Timerexamplestate S = (timerexamplestate) State;
S. Counter ++;
Console. writeline ("{0} checking status {1}.", datetime. Now. timeofday, S. Counter );
If (S. Counter = 5)
{
File: // The change method is used to change the time interval.
(S. TMR). Change (random, 2000 );
Console. writeline ("changed ...");
}
If (S. Counter = 10)
{
Console. writeline ("disposing of timer ...");
S. TMr. Dispose ();
S. TMR = NULL;
}
}
}

The program first creates a timer, which will start to call the checkstatus () method once every one second after the creation of 1 second. After five calls () the time interval is modified to 2 seconds, and the task starts again after 10 seconds. When the Count reaches 10 times, the timer object is deleted by calling the timer. Dispose () method, and the main thread jumps out of the loop and terminates the program. The execution result of the program is as follows:

The above is a brief introduction to the threadpool and timer classes. taking full advantage of the functions provided by the system, we can save a lot of time and effort-especially for error-prone multi-threaded programs. At the same time, we can also see the powerful built-in objects of. NET Framework, which will bring great convenience to our programming.
V. mutex objects-more flexible Synchronization Methods

Sometimes you may feel that the method described above is not enough. Right, we solve the synchronization problem between code and resources, and solve the problem of multi-thread automatic management and timed triggering, but how can we control the connections between multiple threads? For example, if I want to go to a restaurant for dinner, I have to wait for the cook to finish the meal, and then I start to eat. After dinner, I have to pay. The payment method can be cash or credit card, I can leave after payment. After analyzing this process, I can think of my meal as the main thread, the cook as a thread, and the waiter can use a credit card to collect money and receive cash as two other threads, you can clearly see the relationship between them-I have to wait for the cook to cook, and then wait for any one of the two receiving threads to complete, and then I can run this step to exit the thread, that's why I ended my meal. In fact, there are more complex connections in reality. How can we control them well without conflict or repetition?

In this case, we need to use mutex objects, namely the mutex class in the system. Threading namespace. Everyone must have taken a taxi. In fact, we can regard mutex as a taxi, so a passenger is a thread. A passenger must first wait for the car, then get on the bus, and finally get off the bus. When a passenger is in the car, other passengers can get on the bus only after they get off the bus. This is also the relationship between the thread and the mutex object. The thread uses mutex. the waitone () method waits for the mutex object to be released. If the mutex object it waits for is released, it automatically owns the object until it calls mutex. the releasemutex () method releases this object. During this period, other threads that want to obtain this mutex object only have to wait.

The following example uses a mutex object to synchronize four threads. The main thread waits for the end of the four threads, and the running of these four threads is associated with two mutex objects. The autoresetevent class object is also used. Just like the manualresetevent object mentioned above, you can simply consider it as a signal lamp and use autoresetevent. the Set () method can be used to set it to a signal state, while autoresetevent is used. the reset () method sets it to stateless. Here, its signal state is used to indicate the end of a thread.

// Mutex. CS
Using system;
Using system. Threading;

Public class mutexsample
{
Static mutex GM1;
Static mutex gm2;
Const int iters = 100;
Static autoresetevent event1 = new autoresetevent (false );
Static autoresetevent event2 = new autoresetevent (false );
Static autoresetevent event3 = new autoresetevent (false );
Static autoresetevent event4 = new autoresetevent (false );

Public static void main (string [] ARGs)
{
Console. writeline ("mutex sample ...");
// Create a mutex object and name it mymutex
GM1 = new mutex (true, "mymutex ");
// Create an unnamed mutex object.
Gm2 = new mutex (true );
Console. writeline ("-Main owns GM1 and gm2 ");

Autoresetevent [] EVS = new autoresetevent [4];
EVS [0] = event1; file: // defines the autoresetevent object for the subsequent threads T1, T2, T3, and T4.
EVS [1] = event2;
EVS [2] = event3;
EVS [3] = event4;

Mutexsample TM = new mutexsample ();
Thread T1 = new thread (New threadstart (TM. t1start ));
Thread t2 = new thread (New threadstart (TM. t2start ));
Thread T3 = new thread (New threadstart (TM. t3start ));
Thread t4 = new thread (New threadstart (TM. t4start ));
T1.start (); // use the mutex. waitall () method to wait for all objects in a mutex array to be released
T2.start (); // use the mutex. waitone () method to wait for the release of GM1
T3.start (); // use the mutex. waitany () method to wait for any object in a mutex array to be released
T4.start (); // use the mutex. waitone () method to wait for the release of gm2

Thread. Sleep (2000 );
Console. writeline ("-main releases GM1 ");
Gm1.releasemutex (); file: // thread T2. The T3 end condition is met.

Thread. Sleep (1000 );
Console. writeline ("-main releases gm2 ");
Gm2.releasemutex (); file: // The end conditions of thread T1 and T4 are met.

// Wait until all four threads end
Waithandle. waitall (EVS );
Console. writeline ("... mutex sample ");
Console. Readline ();
}

Public void t1start ()
{
Console. writeline ("t1start started, mutex. waitall (mutex [])");
Mutex [] GMS = new mutex [2];
GMS [0] = GM1; // create a mutex array as a parameter of the mutex. waitall () method.
GMS [1] = gm2;
Mutex. waitall (GMS); // wait until both GM1 and gm2 are released
Thread. Sleep (2000 );
Console. writeline ("t1start finished, mutex. waitall (mutex []) satisfied ");
Event1.set (); file: // The End Of The thread. Set event1 to a signal state.
}

Public void t2start ()
{
Console. writeline ("t2start started, gm1.waitone ()");
Gm1.waitone (); // wait for the release of GM1
Console. writeline ("t2start finished, gm1.waitone () satisfied ");
Event2.set (); // when the thread ends, set event2 to a signal state.
}

Public void t3start ()
{
Console. writeline ("t3start started, mutex. waitany (mutex [])");
Mutex [] GMS = new mutex [2];
GMS [0] = GM1; // create a mutex array as a parameter of the mutex. waitany () method.
GMS [1] = gm2;
Mutex. waitany (GMS); // wait for any mutex object in the array to be released
Console. writeline ("t3start finished, mutex. waitany (mutex [])");
Event3.set (); // when the thread ends, set event3 to a signal state.
}

Public void t4start ()
{
Console. writeline ("t4start started, gm2.waitone ()");
Gm2.waitone (); // wait for gm2 to be released
Console. writeline ("t4start finished, gm2.waitone ()");
Event4.set (); // when the thread ends, set event4 to a signal state.
}
}

The execution result of the program is as follows:

From the execution results, we can clearly see that the running of thread T2 and T3 is conditional on the release of GM1, and T4 is executed after the release of gm2, t1 is executed only after both GM1 and gm2 are released. At the end of the main () function, waithandle is used to wait for the signals of all autoresetevent objects. The signals of these objects represent the end of the corresponding thread.

Vi. Summary

Multi-threaded program design is a huge topic. This article attempts to use the latest C # language to describe the appearance of multi-threaded programs in the. NET Framework environment. I hope this article will help you understand the concept of thread, its usage, its C # implementation method, and its benefits and troubles. C # Is a new language, so its thread mechanism also has many unique features. I hope you can see these clearly through this article, in this way, the thread can be further understood and explored.

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.