C # Based on the thread pool

Source: Internet
Author: User

A Pool is a common way to improve performance. For example, the thread pool and connection pool have these pools because it is expensive to create and close connections between threads and databases. For such expensive resources, we often consider placing some resources in a pool container, getting them when they are used, adding some points when they are not enough, and returning them when they are used up, in this way, you can avoid constantly creating resources and destroying resources.

If you have performed related experiments, it may take several hundred milliseconds to open 1000 threads. We want to think so. For a highly concurrent environment, assume that there are 100 requests every second, and each request requires 10 threads (ON and OFF, that is to say, one second requires processing the opening and shutting down of 1000 threads. Each thread has an independent stack of 1 M. We can imagine how exaggerated the memory allocation and collection in this second. This overhead cannot be said to be less expensive.

First, we need to understand that thread pool threads are divided into two types of working threads and IO threads. You can set the minimum number of threads and the maximum number of threads separately:

ThreadPool. SetMinThreads (2, 2 );
ThreadPool. SetMaxThreads (4, 4 );

The maximum number of threads is well understood, that is, the thread pool can create up to these threads. If the maximum number of four threads is running, the threads that come in later can only wait in queue. So why is there the minimum thread? In fact, the thread pool is used so that you do not want the thread to understand the collection after it is created. In this way, you need to create a thread when it is used later. We can make the thread pool retain at least a few threads, it is retained even if no thread is working. In the preceding statement, we set the thread pool to keep two working threads and two I/O threads at the beginning, with a maximum of four threads.

The usage of the thread pool is quite simple. First, let's look at a piece of code:

For (int I = 0; I <totalThreads; I ++)
{
ThreadPool. QueueUserWorkItem (o =>
{
Thread. Sleep (1000 );
Int A, B;
Threadpool. getavailablethreads (out a, out B );
Console. writeline (string. format ("({0}/{1}) #{2 }:{ 3}", a, B, thread. currentthread. managedthreadid, datetime. now. tostring ("mm: SS ")));
});
}
Console. writeline ("main thread finished ");
Console. Readline ();

A previously defined static field is used in the Code:

Static readonly int totalthreads = 10;

The code execution result is as follows:

Every thread sleeps for one second and then outputs the available worker threads and IO threads of the current thread pool, as well as the managed ID and time of the current thread. Through this code, we can find several features of the thread pool:

1) All threads in the thread pool are backend threads. If Readline is not used in the main thread, the program will exit immediately.

2) The thread pool occupies 2 threads at the beginning, and 4 threads are occupied in one second. The worker threads are processed by 3-6 threads.

3) The thread pool can contain up to four working threads and zero I/O threads.

So, how do we know that the threads in the thread pool are all finished? We can think of the monitor structure used above:

Stopwatch sw = Stopwatch. StartNew ();
For (int I = 0; I <totalThreads; I ++)
{
ThreadPool. QueueUserWorkItem (o =>
{
Thread. Sleep (1000 );
Int a, B;
ThreadPool. GetAvailableThreads (out a, out B );
Console. writeLine (string. format ("({0}/{1}) #{2 }:{ 3}", a, B, Thread. currentThread. managedThreadId, DateTime. now. toString ("mm: ss ")));
Lock (locker)
{
RunningThreads --;
Monitor. Pulse (locker );
}

});
}

Lock (locker)
{
While (runningThreads> 0)
Monitor. Wait (locker );
}

Console. WriteLine (sw. ElapsedMilliseconds );
Console. ReadLine ();

Two Auxiliary fields are used in the program:

Static object locker = new object ();

Static int runningThreads = totalThreads;

The program running result is as follows:

We can see that all the 10 threads have been executed in 3.5 seconds. What about 20 threads?

It takes 6 seconds. By analyzing these two figures, we can easily find that new threads are not created immediately when they are not enough, but delayed by about 0.5 seconds, this is because the thread pool will wait to see if there is a thread available during this period of time. If there is no thread, create it again. In fact, we can understand that there are only two threads in the first six seconds. In the last four seconds, four threads run 16 and there are only two threads in the last one, so a total of 2 + 4*4 + 2 = 20, 6 seconds processed 20 threads.

Threadpool also has a very useful method to register a semaphore. After we send a signal, all associated threads will execute it. Otherwise, we will wait for it all the time. We can also specify the waiting time:

First, define the semaphores and storage result fields:

Static manualresetevent Mre = new manualresetevent (false );
Static int result = 0;

The procedure is as follows:

Stopwatch Sw = stopwatch. startnew ();
For (INT I = 0; I <totalthreads; I ++)
{
Threadpool. registerwaitforsingleobject (MRE, (State, istimeout) =>
{
Thread. Sleep (1000 );
Int A, B;
Threadpool. getavailablethreads (out a, out B );
Interlocked. increment (ref result );
Console. writeLine (string. format ("({0}/{1}) #{2 }:{ 3}", a, B, Thread. currentThread. managedThreadId, DateTime. now. toString ("mm: ss ")));
Lock (locker)
{
RunningThreads --;
Monitor. Pulse (locker );
}
}, Null, 500, true );
}

Thread. Sleep (1000 );
Result = 10;
Mre. Set ();
Lock (locker)
{
While (runningThreads> 0)
Monitor. Wait (locker );
}
Console. WriteLine (sw. ElapsedMilliseconds );
Console. WriteLine (result );
Console. ReadLine ();

The first parameter is the semaphore, the second parameter is the method body (two parameters are accepted, namely, a state variable passed to the thread and whether the thread times out during execution), and the third parameter is the state variable, for the fourth parameter, the timeout time is set to 500 milliseconds. Because the main thread sends a signal after 1 second, the timeout time is 500 milliseconds, so these threads did not wait 500 milliseconds after the signal is sent. The running result of the program is 30 because even if the thread times out after 500 milliseconds, it takes only one second to accumulate the result. At this time, the main thread has already updated the result to 10, so it starts from 10 instead of 0. The last Boolean parameter is true, indicating that only one thread is executed once after the signal is received.

It was observed that it took 7 seconds for all threads to complete the execution, except that the start wait time was 0.5 seconds, which was 0.5 seconds more than the previous example. Why? Please help with analysis. Another strange problem is that RegisterWaitForSingleObject consumes I/O threads rather than working threads. Does Microsoft think that RegisterWaitForSingleObject is common in IO operations or does not want to waste working threads?

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.