In the. NET multi-threaded programming series, we will discuss all aspects of multi-threaded programming. First, I will Article I will introduce the concept of multithreading and the basic knowledge of multithreading programming. . NET platform multi-threaded programming knowledge, such as the important classes and methods of the system. Threading namespace, and some examples Program .
Introduction
The early computing hardware was very complex, but the functions executed by the operating system were indeed very simple. At that time, the operating system can only execute one task at any time point, that is, only one program can be executed at the same time. The execution of multiple tasks must take turns and wait in queue in the system. Due to the development of computers, the system requires more and more powerful functions. At this time, the concept of time-sharing operations emerged: each running program occupies a certain processor time, the next program waiting for the queue to wait for the processor resources starts to run. Note that the program is not running after a certain processor time, and the processor time may need to be allocated again or multiple times. We can see from this that this execution method is obviously parallel execution of multiple programs, but at the macro level, we feel that multiple tasks are executed at the same time, therefore, the concept of multi-task was born. Each running program has its own memory space, its own stack and environment variable settings. Each program corresponds to a process and represents executing a large task. A process can start another process, which is called a sub-process. The execution of a parent process and a child process has only a logical relationship, and there is no other relationship, that is, their execution is independent. However, a large program (representing a large task) can be divided into many small tasks. For functional purposes or to speed up the operation, you may need to execute multiple tasks at the same time (each task is assigned a multi-thread to execute the corresponding tasks ). For example, you are viewing some wonderful articles through your web browser. You need to download some good articles. You may need to add some wonderful articles to your favorites, you can use your printer to print these online articles. Here, the browser downloads an article in HTML format and prints the article. This means that a program executes multiple tasks at the same time, and each task is assigned a thread to complete. Therefore, we can see that a program can execute multiple tasks simultaneously through multiple threads.
Multithreading vs multitasking
As mentioned above, multitasking refers to the ability to execute multiple programs at the same time, relative to the operating system, but in fact, it is impossible to execute more than two programs at the same time with only one CPU. High-speed switching between programs allows all programs to get a shorter CPU time in a short period of time. From the user's perspective, it seems that multiple programs are being executed at the same time. Multithreading refers to the ability to execute different parts of the same program at the same time, and each part of execution becomes a thread. Therefore, when writing applications, we must be well designed to avoid interference between different threads during execution. This helps us design robust programs so that we can add threads whenever needed.
Thread Concept
A thread can be described as a microprocess, which has the starting point, execution sequence, and end point. It maintains its own stacks, which are used for exception handling, priority scheduling, and other information required when the system resumes thread execution. From this concept, it seems that there is no difference between a thread and a process. In fact, there must be a difference between a thread and a process:
A complete process has its own independent memory space and data, but threads in the same process share the memory space and data. A process corresponds to a program, which is composed of threads that run independently and simultaneously in the same program. A thread is also called a lightweight process that runs in parallel in a program. A thread is called a lightweight process because it runs dependent on the context environment provided by the process and uses process resources.
In a process, thread scheduling can be preemptible or non-preemptible.
In preemption mode, the operating system allocates CPU time to each process. Once the current process is used up, the operating system determines which thread occupies the CPU time. Therefore, the operating system regularly interrupts the threads currently being executed and allocates the CPU to the next thread waiting for the queue. Therefore, no thread can exclusively occupy the CPU. The CPU usage time of each thread depends on the process and operating system. The process is allocated to every thread for a short time, so that we feel that all threads are executed at the same time. In fact, the system runs each process for 2 milliseconds and then Schedules other threads. It also maintains all threads and loops and allocates a small amount of CPU time to threads. The thread switching and scheduling are so fast that it feels that all threads are executed synchronously.
What does scheduling mean? Scheduling means that the processor stores the status of the process whose CPU time is to be executed and the status of the process loaded at a certain time in the future to resume its operation. However, this method also has some shortcomings. A thread can interrupt the execution of another thread at any given time. Assume that a thread is writing data to a file, and the other thread interrupts its operation and writes data to the same file. Windows 95/NT, Unix uses this thread scheduling method.
In non-preemptible scheduling mode, each thread takes up the CPU time as much as the CPU time required. In this scheduling mode, a thread with a long execution time may cause all other threads that require CPU to starve to death ". When the processor is idle, that is, when the process does not use the CPU, the system can allow other processes to temporarily use the CPU. The CPU-consuming thread has control over the CPU. Other threads can use the CPU only when it actively releases the CPU. Some I/O and Windows 3. X uses this scheduling policy.
In some operating systems, both scheduling policies are used. Non-preemptible scheduling policies are generally used when the thread running priority is normal, while high-priority thread scheduling uses preemptible scheduling policies. If you are not sure that the system uses the scheduling policy, it is safer to assume that the preemptible scheduling policy is unavailable. When designing an application, we think that the threads that occupy more CPU time will release the control of CPU at a certain interval, at this time, the system will check the threads in the waiting queue with the same priority or higher priority as the currently running threads, so that these threads can use the CPU. If the system finds such a thread, it immediately suspends the current thread and activates the thread that meets the conditions. If no thread with the same priority or higher level is found, the current thread continues to occupy the CPU. When the executing thread wants to release control of CPU to a low-priority thread, the current thread will be transferred to sleep state, so that the low-priority thread will occupy the CPU.
In a multi-processor system, the operating system will allocate these independent threads to different processors for execution, which will greatly speed up program running. The thread execution efficiency will be greatly improved, because the time-sharing single processor of the thread is converted into distributed multi-processor execution. This type of multi-processor is very useful in 3D modeling and graphic processing.
Need multithreading?
We issued a print command to ask the printer to print the task. Suppose the computer stops responding at this time and the printer is still working, isn't it because we are waiting for the printer to print at a slow speed? Fortunately, this does not happen. We can also listen to music or draw pictures while working on the printer. Because we use independent multithreading to execute these tasks. You may be surprised when multiple users access the database or web server at the same time. How do they work? This is because an independent thread is set up for each user connected to the database or web server to maintain the user's status. If a program runs in a certain order, this method may cause problems or even cause the entire program to crash. If the program can be divided into different independent tasks and multithreading is used, even if a part of the task fails, it will not affect other tasks and will not cause the whole program to crash.
There is no doubt that writing a multi-threaded program gives you a powerful tool to drive a non-multi-threaded program, but multithreading may also become a burden or a huge cost. Improper use may cause more disadvantages. If a program has many threads, the threads of other programs must only occupy less CPU time, and a large amount of CPU time is used for thread scheduling; the operating system also needs enough memory space to maintain the context information of each thread. Therefore, a large number of threads will reduce the operating efficiency of the system. Therefore, if multithreading is used, the program multithreading must be well designed; otherwise, the benefits will be far less than the disadvantages. Therefore, we must be careful when using multithreading to create, schedule, and release these threads.
Multithreading Program Design tips
There are multiple ways to design multi-threaded applications. As shown in the following article, I will provide detailed programming examples through which you can better understand multithreading. Threads can have different priorities. For example, in our applications, drawing graphics or performing a large number of operations must also accept user input, obviously, the user's input needs to be responded immediately, while the drawing or operation takes a lot of time. The pause is not a problem, so the user's input thread will need a high level of leisure, graphics Rendering or low-priority operations. These threads are independent of each other and do not affect each other.
In the above example, drawing a graph or a large number of operations obviously requires a lot of CPU time. During this time, you do not have to wait for them to complete execution before entering information, therefore, we design the program into two independent threads, one responsible for user input and the other responsible for processing those time-consuming tasks. This will make the program more flexible and responsive. At the same time, the user may cancel the task at any time during running. In this example, the program should always be responsible for receiving messages from the system. If the program is busy with a task, the screen may become blank, which obviously requires our program to handle such events. So I have to have a thread responsible for processing these messages, as I mentioned earlier, it should trigger the re-painting of the screen.
We should grasp a principle that we should give higher priority to tasks that require time to be obtained immediately, while other threads should have lower priority than her. The thread that listens to client requests should always have a high priority. For a task on the user interface that interacts with the user, it needs to get the first response because of this high priority.
Mao's nest
In the next article, I will introduce the thread API in. net, how to use C # To create a thread, start and stop the thread, and set the priority and status.
Programs Written in. net will be automatically assigned a thread. Let's take a look at how to create a thread in C # programming language and continue to learn the thread knowledge. We all know. the main thread of the net runtime environment is started by the main () method, and. net compilation language has an automatic garbage collection function. This garbage collection occurs in another thread. All of this happens in the background, so that we cannot feel what happened. by default, only one thread is used to complete all program tasks, but as we discussed in the first article, we may add more threads as needed to better coordinate the program. For example, in our example, a program with user input that needs to draw graphics or complete a large number of operations, we must add a thread so that user input can be responded in a timely manner, because the input requires time and response, and the other thread is responsible for drawing graphics or a large number of operations.
The system. Threading namespace of the. net base class library provides a large number of classes and interfaces that support multithreading. There are many classes in this namespace. Here we will focus on the Thread class.
The system. Threading. Thread class is the most common class used to create and control a thread, set its priority, and obtain its status. He has many methods. Here we will introduce more common and important methods:
Thread. Start (): the execution of the startup thread;
Thread. Suspend (): suspends a thread, or does not work if the thread has been suspended;
Thread. Resume (): continue the suspended thread;
Thread. Interrupt (): Stop a thread in the wait, sleep, or join thread state;
Thread. Join (): blocks the calling thread until the end of a thread.
Thread. Sleep (): the specified number of milliseconds that will block the current thread;
Thread. Abort (): starts the process of terminating this thread. If the thread is already terminated, the thread cannot be started through thread. Start.
By calling thread. Sleep, thread. Suspend or thread. Join, you can pause or block a thread. Calling the sleep () and suspend () methods means that the thread no longer gets the CPU time. There is a difference between the two methods to suspend a thread. Sleep () causes the thread to stop execution immediately, but before calling the suspend () method, the common language runtime must reach a security point. A thread cannot call the sleep () method for another thread, but can call the suspend () method to suspend the execution of another thread. The thread. Resume () method is called for a suspended thread to continue execution. No matter how many times the suspend () method is used to block a thread, you only need to call the resume () method once to continue the thread execution. Threads that have been terminated and have not started execution cannot be suspended. Thread. Sleep (int x) blocks the thread for X milliseconds. Only when this thread is awakened by other threads by calling the thread. Interrupt () or thread. Abort () method. If a thread is called for a thread in the blocking status. the interrupt () method will change the thread state, but it will throw a threadinterupptedexception exception. You can catch this exception and handle it, or ignore this exception and cause the thread to terminate at runtime. Within a certain period of waiting time, both thread. Interrupt () and thread. Abort () can immediately wake up a thread.
Next we will explain how to abort another thread from one thread. In this case, we can use the thread. Abort () method to permanently destroy a thread and throw a threadabortexception exception. So that the terminated thread can catch exceptions, but it is difficult to control the recovery. The only way is to call the thread. resetabort () to cancel the call, and only when this exception is caused by the call thread. Therefore, thread a can correctly use the thread. Abort () method to act on thread B, but thread B cannot call thread. resetabort () to cancel the thread. Abort () operation. The thread. Abort () method allows the system to quietly destroy threads without notifying users. Once the thread. Abort () operation is implemented, the thread cannot be restarted. The call to this method does not mean that the thread is destroyed immediately. Therefore, to determine whether the thread is destroyed, we can call the thread. join () to determine its destruction, thread. join () is a blocking call and is returned only when the thread is terminated. However, a thread may call the thread. Interrupt () method to stop another thread, which is waiting for the return of the thread. Join () call.
Try not to use the suspend () method to suspend the blocking thread, because it is easy to cause a deadlock. Assume that you have suspended a thread and its resources are required by other threads. What are the consequences. Therefore, we try to assign different priorities to threads of different importance. Use the thread. Priority () method instead of the thread. Suspend () method.
The thread class has many attributes, which must be mastered by multithreaded programming.
Thread. isalive attribute: gets a value that indicates the execution status of the current thread. True if the thread has been started and has not been terminated or aborted; otherwise, false.
Thread. Name attribute: gets or sets the thread name.
Thread. Priority attribute: gets or sets a value that indicates the scheduling priority of the thread.
Thread. threadstate attribute: gets a value that contains the status of the current thread.
In the following example, we will see how to set these attributes. In the following example, we will discuss these attributes in detail.
To create a thread, you must first instantiate a Thread class and call threadstart in the class constructor. This delegate contains where the thread starts execution. After the thread starts, start () starts a new thread. The following is an example program.
Using system;
Using system. Threading;
Namespace learnthreads
{
Class thread_app
{
Public static void first_thread ()
{
Console. writeline ("first thread created ");
Thread current_thread = thread. currentthread;
String thread_details = "thread name:" + current_thread.name +
"\ R \ nthread state:" + current_thread.threadstate.tostring () +
"\ R \ n thread priority level:" + current_thread.priority.tostring ();
Console. writeline ("the details of the thread are:" + thread_details );
Console. writeline ("first thread terminated ");
}
Public static void main ()
{
Threadstart thr_start_func = new threadstart (first_thread );
Console. writeline ("creating the first thread ");
Thread fthread = new thread (thr_start_func );
Fthread. Name = "first_thread ";
Fthread. Start (); // starting the thread
}
}
}
In this example, a fthread thread object is created, which is responsible for executing tasks in the first_thread () method. When the thread start () method is called, the proxy containing the first_thread () Address threadstart will be executed.
Thread status
The system. Threading. thread. threadstate attribute defines the State of the thread during execution. A thread must be in one of the statuses from creation to termination. When a thread is created, it is in the unstarted state. The start () method of the thread class changes the thread state to the running state, and the thread will remain in this state, unless we call a method to suspend, block, destroy, or terminate it. If the thread is suspended, it will be in the susponded state, unless we call the resume () method to re-execute it, then the thread will change to the running state again. Once the thread is destroyed or terminated, the thread is in the stopped state. A thread in this state will no longer exist. Just as the thread starts to start, the thread will not be able to return to the unstarted state. The thread also has a background status, which indicates whether the thread runs on the foreground or background. At a specified time, the thread may be in multiple States. For example, if a thread is blocked when sleep is called, and another thread calls the abort method, the thread is in both waitsleepjoin and abortrequested states. Once the thread response is converted into a blocking or abort event, a threadabortexception exception is thrown when the thread is destroyed.
Thread priority
System. Threading. thread. Priority enumerates the priority of a thread, which determines how much CPU time the thread can obtain. High-priority threads usually get more CPU time than general-priority threads. If there are more than one high-priority thread, the operating system will allocate CPU time cyclically between these threads. Low-priority threads get less CPU time. When there is no high-priority thread, the operating system selects the next low-priority thread for execution. Once a low-priority thread encounters a high-priority thread during execution, it will give the CPU to the high-priority thread. The priority of the newly created thread is normal. We can set the priority value of the thread, as shown below:
Highest
Abovenormal
Normal
Belownormal
Lowest
Conclusion: In this section, we discuss the priority of the thread to be created. The system. Threading namespace also contains advanced features such as thread lock, thread synchronization, multi-thread management, and deadlock resolution. We will continue to discuss these features in the following sections.
Mao's nest