Use thread pool and dedicated thread

Source: Internet
Author: User
Tags terminates

Efficient thread usage

Strictly speaking, the thread system overhead is very high. The system must allocate and initialize a thread kernel object for the thread, and keep 1 MB of address space for each thread (submitted as needed) for the thread's user mode stack, allocate around 12 kb of address space for the kernel mode stack of the thread. Then, after the thread is created, Windows calls a function in each DLL in the process to notify all the DLL Operating Systems in the process to create a new thread. Similarly, the overhead of destroying a thread is not small: Every DLL in the process must receive a notification about the thread's imminent death, and the kernel object and stack must be released.

If a computer has only one CPU, only one thread can run at a certain time. Windows must keep track of every thread object. Windows has to decide which thread the CPU will schedule for execution next time. This additional code has to be executed every 20 ms. Windows causes the CPU to stop executing the code of one thread and starts to execute the code of another thread. This is called context switch ). The overhead of context switching is large because the operating system must perform the following steps:

1. Enter kernel mode.

2. Save the CPU register to the kernel object of the currently executing thread. CPU registers occupy approximately 700 bytes of space on machines in the X86 architecture; CPU registers occupy approximately 1240 bytes of space on machines in the x64 architecture; in the IA64 architecture, CPU registers occupy about 2500 bytes of space.

3. A spin lock is required to determine which thread to schedule next time, and then release the spin lock. If the thread to be scheduled next time belongs to another process, the overhead will be higher because the operating system must switch to the virtual address space.

4. Load the value of the kernel object of the thread to be run to the CPU register.

5. Exit kernel mode.

All of the above content is purely overhead, resulting in a slower execution speed for Windows operating systems and applications than on a single-threaded system.

Based on all the above results, we can conclude that the use of threads should be restricted as much as possible. If more threads are created, the overhead of the operating system is increased, and everything runs slowly. In addition, each thread needs resources (the memory occupied by the kernel object and two stacks), so each thread consumes memory.

The thread has another purpose: scalability. When a computer has multiple CPUs, Windows can schedule multiple threads simultaneously: Each CPU runs one thread.

Introduction to CLR Thread Pool

As mentioned above, the time overhead of creating and destroying a thread is considerable. In addition, too many threads will waste memory resources, and the operating system has to schedule and switch context between runable threads, thus affecting the performance of the operating system and applications. To improve this phenomenon, CLR contains code for managing the CLR thread pool. We can regard the thread pool as a set of threads used by the application itself. Each process has a thread pool, which is shared by all application domains in the process.

When CLR is initialized, there are no threads in the thread pool. In terms of internal implementation, the thread pool maintains a series of Operation requests. When an application wants to perform an asynchronous operation, it can call some methods to add an entry to the queue of the thread pool. The code in the thread pool extracts the entry from the queue and distributes the entry to the thread pool. If there are no threads in the thread pool, create a new thread. Creating a thread may cause performance loss. However, when the thread in the thread pool completes the task, it is not destroyed, but is returned to the thread pool, idle in the thread pool, waiting to respond to another request. Because the thread does not destroy itself, there will be no performance loss.

If the application makes a lot of requests to the thread pool, the thread pool will try to use only one thread to respond to all requests. However, if the requests queued by the application exceed the processing capacity of the thread pool, another thread will be created in the thread pool. In the end, the requests queued by the application reach a balance between the processing capability of the threads in the thread pool. We can use a small number of threads to process all the requests, therefore, more threads are no longer required in the thread pool.

If the application stops the request thread pool, there may be many threads in the thread pool that do nothing. This will waste memory resources. Therefore, when the threads in the thread pool are idle for more than two minutes, the thread will wake itself up and terminate itself to release memory resources. When the thread terminates itself, there will also be a performance loss. However, this performance loss is not very serious, because when the thread terminates itself, the thread is idle, which means that our application has not executed much work currently.

In terms of internal implementation, the thread pool classifies threads in the thread pool into worker threads and I/O threads ). When the application requests the thread pool to execute an asynchronous operation restricted by computing (including initializing asynchronous operations restricted by I/O, the I/O thread is used to notify the code when asynchronous operations restricted by I/O are completed. Specifically, this means that we need to use an asynchronous programming model for I/O requests.

Limit the number of threads in the thread pool

The CLR thread pool allows developers to set the maximum number of working threads and I/O threads. CLR ensures that the number of created threads does not exceed this value. But never set an upper limit on the number of threads in the thread pool, because hunger and deadlock may occur. In the default version of CLR 2.0, the maximum number of worker threads is 25 CPUs and the maximum number of I/O threads is 1000.

System. threading. the threadpool class provides several static methods to operate on the number of threads in the thread pool: getmaxthreads (maximum number of threads in the query thread pool) and setmax-threads (maximum number of threads) getminthreads, setminthreads, and getavailable-threads.

We strongly recommend that you do not call the setmaxthreads method to modify the number of threads in the thread pool, because this will damage the application's execution performance.

The CLR thread pool tries to avoid creating additional threads too quickly. Specifically, the thread pool tries to avoid creating a new thread every Ms. This raises a problem for some developers because tasks in the queue cannot be processed in a timely manner. To solve this problem, call the setminthreads method to set the minimum number of threads in the thread pool. After this method is called, the thread pool will quickly create so many threads, and when the number of tasks in the queue continues to increase, after all the threads created are used, the thread pool will continue to create additional threads at intervals of Ms. By default, the minimum number of worker threads and I/O threads in the thread pool is set to 2. This value can be obtained by calling the getminthreads method.

Finally, you can call the getavailablethreads method to obtain the number of additional threads that can be added to the thread pool. The return value of this method is the maximum number of threads in the thread pool minus the number of threads in the thread pool. This value is only useful at the returned moment, because after the method is returned, many threads may have been added to the thread pool, or some threads may have been destroyed.

Use the thread pool to execute asynchronous operations restricted by computing

Operations restricted by computing are computing operations. For example, cells that can be computed in a workbook application. Ideally, operations restricted by computing will not execute any asynchronous I/O operations, because all asynchronous I/O operations will suspend the calling thread when the underlying hardware is running. Try to run the thread as much as possible, because the suspended thread does not continue to run but still uses system resources.

To add a computing-restricted Asynchronous Operation to the queue of the thread pool, you can use the following method defined in the threadpool class:

static bool QueueUserWorkItem(WaitCallback callback);
static bool QueueUserWorkItem(WaitCallback callback, object state);
static bool UnsafeQueueUserWorkItem(WaitCallback callback, object state);

The above method adds a "work item" (and optional State data) to the queue of the thread pool, and then these methods will return immediately. A work item is only a method identified by the callback parameter. The thread in the thread pool calls this method. This method can only pass a single parameter specified by the State (State data) parameter. The queueuserworkitem method without the state parameter passes NULL for the callback function. Eventually, some threads in the thread pool will execute work items, resulting in our method being called. The callback method we write must match the system. Threading. waitcallback delegate type. Its definition method is as follows:

delegate void WaitCallback(object state);

The following code demonstrates how a thread in the thread pool asynchronously calls a method:

Using system;
Using system. Threading;

Public static class Program
{
Public static void main ()
{
Console. writeline ("main thread: queuing An Asynchronous Operation ");
Threadpool. queueuserworkitem (computeboundop, 5 );
Console. writeline ("main thread: doing other work here ...");
Thread. Sleep (10000); // simulate other jobs for 10 seconds
Console. writeline ("hit <enter> to end this program ...");
Console. Readline ();
}

// The signature of this method must match the waitcallback delegate type
Private Static void computeboundop (object state)
{
// This method is executed by the thread in the thread pool
Console. writeline ("in computeboundop: State = {0}", State );
Thread. Sleep (1000); // simulate other operations for 1 second

// After this method is returned, the thread returns to the thread pool and waits for another task to be executed.
}
}

If the exception thrown by the callback method is not handled, the CLR terminates the process.

The threadpool class has an unsafequeueuserworkitem method. This method is very similar to the queueuserworkitem method that is usually called. The following describes the differences between the two methods: when attempting to access a restricted resource (such as opening a file), CLR will execute a code access security (CAS) Check. That is to say, CLR checks whether all the assembly in the call stack of the call thread has permission to access resources. If some assemblies do not have the required permission, CLR throws a securityexception. If the assembly of the thread where the code is being executed does not have the permission to open the file, CLR throws a securityexception when the thread tries to open the file.

To allow the thread to continue running, the thread can add a work item to the queue of the thread pool, so that the thread in the thread pool can execute the code to open the file. Of course, this must be done in a program with the appropriate permission. This workspace allows malicious code to seriously damage restricted resources. To prevent this security permission, the queueuserworkitem method traverses the stack of the calling thread internally and captures all the security permissions granted. Then, when the thread in the thread pool starts to execute, these permissions are combined with the thread. Therefore, the threads in the thread pool run with the same permission set as the threads that call the queueuserworkitem method.

Traversing the stack of a thread and capturing all security permissions is closely related to performance. To improve the queuing performance of asynchronous operations restricted by computing, you can call the unsafequeueuserworkitem method. This method only adds work items to the queue of the thread pool, without traversing the stack of the calling thread. The final result is that this method is executed faster than the queueuserworkitem method, but it opens a potential security vulnerability in the application. The unsafequeueuserwork-item method can be called only when you can confirm that the Code executed by the thread in the thread pool does not touch restricted resources, or if you are sure that there is no problem with accessing this part of resources. Similarly, you must call this method to enable the controlpolicy and controlevision tags of securitypermission to prevent untrusted code from occasionally or intentionally increasing its permission.

Use a dedicated thread to execute asynchronous operations restricted by computing

We strongly recommend that you use thread pools whenever possible to execute asynchronous operations restricted by computing. However, in some cases, we may want to explicitly create a thread to execute specific computing-restricted asynchronous operations. Generally, if the code to be executed needs to be in a specific State (different from the normal state of the thread in the thread pool), you want to create a dedicated thread. For example, you want a thread to run with a special priority (All threads in the thread pool run with a normal priority, and we should not modify the thread priority in the thread pool ), you need to create a dedicated thread. Another example: If you want to make a thread a foreground thread (All threads are background threads), you can also consider creating and using your own threads to Prevent the application from being "killed ", until the thread completes the task. If a task restricted by computing runs for a very long time, you should also use a dedicated thread. In this way, we do not have to make the logic of the thread pool effort to determine whether additional threads need to be created. Finally, if we want to start a thread and interrupt the thread by calling the abort method of the thread, we should use a dedicated thread.

To create a dedicated thread, we can build an instance of the system. Threading. Thread class (using the method name as the parameter of the constructor ). The following is the prototype of the constructor:

public sealed class Thread : CriticalFinalizerObject, ...
{
public Thread(ParameterizedThreadStart start);
}

The start parameter indicates that the method of the dedicated thread is about to be executed. This method must match the signature of the commissioned parameterizedthreadstart:

delegate void ParameterizedThreadStart(Object obj);

It can be seen that the signature of the parameterizedthreadstart delegate is the same as that of the waitcallback delegate. This means that the same method can be called using a thread in the thread pool or using a dedicated thread.

Creating a thread object does not create an operating system thread. To create an operating system thread and run the callback method, we must call the start method of the thread. As follows:

Using system;
Using system. Threading;

Public static class Program
{
Public static void main ()
{
Console. writeline ("main thread: starting a dedicated thread" + "to do an Asynchronous Operation ");
Thread dedicatedthread = new thread (computeboundop );
Dedicatedthread. Start (5 );

Console. writeline ("main thread: doing other work here ...");
Thread. Sleep (10000); // simulate other operations for 10 seconds

Dedicatedthread. Join (); // wait for the thread to terminate
Console. writeline ("hit <enter> to end this program ...");
Console. Readline ();
}

// The signature of this method must match the parameterizedthreadstart delegate.
Private Static void computeboundop (object state)
{
// This method is executed by a dedicated thread
Console. writeline ("in computeboundop: State = {0}", State );
Thread. Sleep (1000); // simulate other operations for 1 second
}
}

Note that the main method calls the join method, which causes the calling thread to stop executing any code until the thread identified by dedicatedthread destroys itself or is terminated. When the queueuserworkitem method of threadpool is used to queue asynchronous operations, CLR does not provide a built-in method to determine whether the operation is complete. The join method provides this capability when we use a dedicated thread. However, if you need to know when the operation is completed, you should not use a dedicated thread to replace the queueuserworkitem method, but use APM.

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.