Although kernel developers have to consider multithreading from the very beginning, user-mode developers once had a wonderful life: they only need to care about one thread (mostly UI threads) and you don't have to worry too much about performance: Even if you're nested with countless layers of loops in the main logic, the damn Moore's Law will fix all the problems for you. After entering the multi-core era, user-mode developers finally found that they have been ignoring for a long time, but an important technical point is multithreading. Good life is over. Welcome to the age of chaos.
I know it seems out of date to write this article because there are already countlessArticleI have discussed multiple threads.CommunityI have also developed another thread framework to help you solve annoying things. However, my main purpose today is to solve a thorny problem in kernel development (that is, what we will talk about in 7.2, press the table first.
Speaking of multithreading, the most annoying thing is synchronization. I agree with Mr. Joe in the OSR email list: User statusProgramThere should be no (custom) locks. Any time you find that you need to consider using locks for synchronization, it indicates that your design is wrong. I have a bunch of things to say about synchronizing this mess, but not today. Today I want to talk about another easy-to-be-ignored point: the creation and destruction of threads. Many people do not know how to correctly create and destroy threads. I have seen countless incorrect statements, and the program runs normally in a miraculous way, but the error is wrong. Now there is no problem, not in the future.
Create
The API created by the thread is createthread. There is only one principle for this API. Note that you must never use it. Let's roll back the time to the actual 1970s, thenC LanguageShortly after its birth, the C run time library was also developed, and multitasking was a high level of stuff. If there was a consulting company at the time, they could even make a fortune by training multitasking-related technologies. Naturally, the authors of the C Runtime Library did not consider multithreading. They assumed that the entire C language program had only one thread, and there was no switching, and naturally there was no re-entry, therefore, the C Runtime Library has countless global variables, and errno is the most famous one. Later, the emergence of multi-tasking, and the concept of processes and threads emerged one after another. These global variables became tricky: they would be reloaded. After careful consideration, we can find that these global variables should not be visible to the whole address space, but should be copied to each thread. In fact, the current C library is doing this. Microsoft's msvcr puts errno and other things in TLS (local thread storage block), allocates when creating the thread, and recycles when destroying the thread. But as a system API, createthread does not care about these issues. People are system-level, and the C Runtime Library has nothing to do with it. The problem lies in this: do you dare say that the program you write does not use the C Runtime Library, and all the work is done using pure APIs? Don't talk about it, or be obedient. Don't touch createthread. MS Vc has an alternative function _ beginthreadex/_ endthreadex, which must be used to create and destroy threads at any time. If you are using the C library of other vendors, use the thread functions they provide-no matter what it is, how stupid it is, right-don't touch createthread.
Destroy
Nothing is more disgusting than thread destruction. As mentioned above, the exitthread function cannot be touched. In addition, you must note that the only correct exit method is to allow it to run allCodeExit naturally. However, naturally exiting is simply a luxury. If your main thread needs to wait for all threads to exit before doing the next thing, it is necessary to add a timeout value, because you cannot keep the main thread waiting for too long, it is still a problem that some threads (especially io-related threads) will not return. If a timeout event occurs, we have to force the thread to exit. There are many hidden risks in this practice. I can think of the following:
1. resource leakage. If the thread applied for memory, opened the file, or any other type of resources at the beginning, and released the resources before exiting_ EndthreadexTerminatethreadThen these resources are leaked, and no one will recycle them.
2. Lock status. If the thread obtains mutex at the beginning and is released before exiting_ EndthreadexTerminatethreadAfter that, mutex enters the abandoned State. The wait_abandoned value is returned for other waitsingleobject points. Think about whether your code processes the returned value. Most of them are not...
3. Io problems. If your getoverlapresult call sets the wait parameter to true, it will not return until the IRP is completed. Forcing the thread to exit will cause a misunderstanding of the driver. The driver thinks that as long as the complete has this IRP, the app will do some things, but the app has not actually done it. Worse, if the getoverlapresult call sets the wait parameter to false and waits for a timeout in the subsequent code, cancelio will occur if it fails to wait, like this:
Res = getoverlapresult (..., False); If (! Res) {If (wait_timeout = waitforsingleobject (overlap. hevent, 5000) {cancelio (m_hdriver );}}
After the thread is forced to return, cancelio may not be executed, and the IRP may never be completed. There is nothing worse than there is an IRP that will never be complete in the system. Your process will never be killed, and the shutdown process of the system will also be suspended, congratulations, unplug the power supply.
4. The appverifier will directly crash the process. Appverifier is always running during development, which exposes various risks to developers. For example, if the program is forced to run without running it, the end user will not know what is going on, but the program with appverifier will explode and burn your hard disk, and triggered a magnitude 9 earthquake. Okay, I'm kidding, but your manager won't tolerate crash.
5.If you call a function that requires seh to implement: Raise/signal
This is different from TLS. If a _ Try block is missing, it cannot be supplemented later.
This is a serious problem.
However, there are not many signal codes developed in windows.(Thanks to ownwaterloo)
Is there any way to secure the strong thread retreat? There are actually a few. I can think of the following:
1. Set a signal to notify the target thread to exit. For example, if you define a bool exitthread value, you should write the large loop of the target thread as follows:
While (! Exitthread ){...}
The main thread is like this:
Exitthread = true; waitforsingleobject (m_hthread, infinite );
This method works in most cases, but with race condition, exitthread will be reinjected. It would be better to change it to this.
While (waitforsingleobject (hexitevent, 0 )! = Wait_timeout ){...}
Main thread
Setevent (hexitevent); waitforsingleobject (hthread, infinite );
But there is still a problem. If the logic of the target thread calls waitforsingleobject (..., Infinite) infinite wait for an event, then it still cannot return.
2. Do this in the main thread:
Suspendthread (hthread); getthreadcontext (hthread, & threadcontext); threadcontext. EIP = my_exit; setthreadcontext (hthread, & threadcontext); resumethread (hthread); waitforsingleobject (hthread, infinite );
Release resources in my_exit and exit the thread. This method is effective in addition to io-related issues, and it is also very pleasant to write. it is cool to forcibly modify the thread execution path, Like hook system call in the kernel. It is a bit chewy by hackers. My suggestion is: never do this.
3. Replace waitforsingleobject in the target thread with waitforsingleobejectex, and set the alertable parameter to true. Do this in the main thread:
Queueuserapc (userapcproc, hthread, 0); waitforsingleobject (hthread, infinite );
Userapcproc does not need to do anything, just empty functions. After the queueuserapc function is executed, the waitforsingleobejectex function of the target thread is immediately awakened and the wait_io_completion status is returned. This is a Tianji from Jeffrey Daniel, the author of Windows core programming. This code is also very pleasant to write, and actually uses APC. I am a master! But I suggest you never do this.
4. Replace waitforsingleobject in the target thread with waitformultipleobjects, and write the following in the target thread:
While (waitforsingleobject (hexitevent, 0 )! = Wait_timeout ){... Handle events [2]; events [0] = hexitevent; events [1] = hyouranothereventthathavetowait; If (wait_object_0 = waitformultipleobjects (2, events, false, infinite )) // you have to exit thread {... }}
The main thread is like this:
Setevent (hexitevent); waitforsingleobject (hthread, infinite );
This is a standard practice, including a series of problems related to Io operations. It is recommended that you use this method if you want to actively exit the target thread and ensure that the waitforsingleobject function does not exist in all non-main threads. If you want to wait, use waitformultipleobjects.
So far I have almost finished talking about it, and the only hidden danger lies in I/O. Correct exit requires the thread to cancel your IRP after returning from the wait function and execute the cancelio operation. This requires the driver to set cancelroutine In the IRP. If there is no cancelroutine, The cancelio operation fails, and the above-mentioned horror stories will still happen. I plan to talk about this next time.