// ================================================ ========================================
// Title:
// How to Write elegant code (4) -- simple and effective play with the thread
// Author:
// Norains
// Date:
// Monday 23-November-2009
// Environment:
// The Windows CE 5.0
// ================================================ ========================================
The use of threads is complicated, but there are only a few functions. It is nothing more than creating a thread through createthread, and then closing the handle through closehanle. It is a big deal to add a setthreadpriority to set the priority; simply put, how to exit the thread normally and how to use the thread effectively often brings headaches to beginners.
The topic of this article is how to use threads in a simple but effective manner, but it does not involve data exchange between complicated threads.
First, let's learn how to create a thread. It's easy to call the createthread function. The function is prototype as follows:
Handle createthread (<br/> lpsecurity_attributes lpsa, <br/> DWORD cbstack, <br/> lpthread_start_routine lpstartaddr, <br/> lpvoid lpvthreadparam, <br/> DWORD fdwcreate, <br/> lpdword lpidthread <br/>)
Lpsa parameters are easy to handle, so we don't have to worry about it. As long as we set it to null directly, cbstack is also easy. Generally, we rarely use custom stacks, therefore, this parameter can be set to null. Lpstartaddr is the most important one, pointing to the processing function of our thread. Lpvthreadparam is the form parameter passed to the processing function. If the thread processing function is encapsulated in a class, this cannot be ignored. Fdwcreate and lpidthread can be set to null if they have no special purpose.
Therefore, the call to the simplest thread-created function can be as follows:
Handle hthrd = createthread (null, null, startaddr, null, null); <br/>
If you do not need to set this thread in the future, such as changing the priority, after the creation, we can call closehandle to close the handle:
Closehandle (hthrd );
It should be noted that the closure of the handle here does not mean to close the thread processing function, but to delete the handle object from the system. Simple but rigorous, there is a list for the system to record the created thread object. When this object is no longer used, we must close it to avoid handle leakage.
Next, let's look at the prototype of the thread processing function:
DWORD threadproc (<br/> lpvoid lpparameter <br/> );
There is nothing special. The return value is DWORD, and there is only one form parameter, which is lpparameter. The value of this lpparameter is the fourth form parameter of createthread.
Let's briefly talk about how this parameter is passed.
If our Code creates a thread like this:
DWORD dwvalue = 123; <br/> createthread (null, null, threadproc, reinterpret_cast <void *> (dwvalue), null, null );
Then our thread can obtain the value as follows:
DWORD threadproc (lpvoid pparam) <br/>{< br/> DWORD dwvalue = reinterpret_cast <DWORD> (pparam); <br/> return 0; <br/>}
The value in threadproc is 123.
It seems that this parameter does not play a major role. If we only want to pass a DWORD value, we can use global variables. Now let's take a look at the situation of encapsulating thread processing functions in the class. At this time, this seemingly useless parameter is the only bridge for accessing member variables or functions.
In the description of createthread, it is clear that the address of the object function cannot be passed as a parameter, but only a class function can be passed. In general, only functions modified with static can be used as form parameters.
For example:
Class cbase <br/>{< br/> Public: <br/> DWORD threadproc (lpvoid pparam); <br/> };
The preceding threadproc cannot be used as a function parameter. However, the following parameters can be used as parameters:
Class cbase <br/>{< br/> Public: <br/> static DWORD threadproc (lpvoid pparam); <br/> };
Although the added static modifier can be passed as a form parameter, we will inevitably encounter a problem that the threadproc cannot access object members or object functions. It is also very easy to solve this problem. We only need to pass this pointer as a parameter to the threadproc function, and then convert it into an object pointer, so that we can access the object members normally.
When creating a thread:
Handle hthrd = createthread (null, null, threadproc, this, null, null); <br/> closehandle (hthrd );
Then there is the thread processing function:
DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> cbase * pobj = reinterpret_cast <cbase *> (pparam ); <br/> If (pobj = NULL) <br/>{< br/> assert (false); <br/> return 0x10; <br/>}</P> <p> pobj-> checksum (); // you can directly call the object function here <br/>}< br/>
It is so simple to encapsulate a thread function in a class. The key is to pass the this pointer. The thread basics are similar here. For more details, refer to the relevant documentation. However, the descriptions so far are sufficient for the subsequent descriptions.
For convenience, we will all assume that all the operations are encapsulated in the class.
We have discussed before that closehandle does not close the thread, but just deletes the thread handle from the system list. How should we close the thread?
The common and most recommended one is to let the thread return the result on its own.
For example:
DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> // todo: Do thing. <br/>... <br/> return 0; <br/>}
Some may ask why the API does not have the terminatethread function? Of course you can, but it is very bad.
The code for adding a thread function is as follows:
DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> label1: <br/> If (istimeout () = false) <br/>{< br/> g_iflag | = mutex_nop; <br/>}</P> <p> label2: <br/> If (ischecksystem () = false) <br/>{< br/> g_iflag | = mutex_check; <br/>}</P> <p> label3: <br/> If (isbeautiful () = false) <br/> {<br/> g_iflag | = beautifule; <br/>}</P> <p> return 0; <br/>}</P> <p>
If terminatethread is called when the thread function is still being executed, what is the final value of g_iflag?
If label1 is executed and terminatethread is called, g_iflag is equal to the original value. If label2 is executed, g_iflag sets the mutex_check bit. If label3 is executed, then it is completely different from the previous one.
More importantly, multithreading means that when you call terminate, you cannot know exactly which step threadproc has taken. In other words, every implementation of this program may be different from the previous one. Isn't this a disaster?
So, it's still honest. The thread should be like this. If you quit, let it survive and let it go!
Threads are used in a variety of ways and cannot be listed one by one in this article. Therefore, we will narrow down the scope in the following discussion, limited to threads that continuously receive events.
According to this requirement, we can easily list the corresponding code:
Void cbase: Create () <br/>{< br/> // create an event <br/> m_heventwait = createevent (null, false, false, text ("event_wait"); </P> <p> // create a thread <br/> m_hthrd = createthread (null, null, threadproc, this, null, null ); <br/>}</P> <p> DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> cbase * pobj = reinterpret_cast <cbase *> (pparam); <br/> If (pobj = NULL) <br/>{< br/> return 0x10; <br/>}</P> <p> while (true) <br/>{< br/> // wait event <br/> waitforsingleobject (pobj-> m_hevnetwait, infinite); </P> <p> // todo: the action to receive the event <br/>}</P> <p>
However, this code does have a problem because we cannot let the thread exit. Then, we first adopt the simplest way to set a flag bit. When the flag bit is true, we let the thread jump out of the loop and then return the result directly.
Some code changes in the thread are as follows:
DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> cbase * pobj = reinterpret_cast <cbase *> (pparam ); <br/> If (pobj = NULL) <br/>{< br/> return 0x10; <br/>}</P> <p> while (pobj-> m_exitproc! = False) <br/>{< br/> // returns the result from the function every MS and determines whether the thread needs to exit. <br/> If (waitforsingleobject (pobj-> m_hevnetwait, 100 )! = Wait_timeout) <br/>{< br/> // todo: the action to receive the event <br/>}< br/>
However, such modifications are still somewhat inefficient. Because we need to judge the m_exitproc value, we need to return the waitforsingleobject from the waiting state at intervals, and then judge the flag. In this interval, We have consumed a lot of CPU time.
To avoid unnecessary losses, we should use the waitformultipleobjects function and wait for two events at the same time. One of the events is of course what we need before. Another new event is called a wake-up event. When this event is received, we exit the thread directly.
According to this idea, our code can be modified as follows:
Void cbase: Create () <br/>{< br/> // create a wake-up event <br/> m_hevent [0] = createevent (null, false, false, null); </P> <p> // create a wait event <br/> m_hevent [1] = createevent (null, false, false, text ("event_wait ")); </P> <p> // creation thread <br/> m_hthrd = createthread (null, null, threadproc, this, null, null ); <br/>}</P> <p> DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> cbase * pobj = reinterpret_cast <cbase *> (pparam); <br/> If (pobj = NULL) <br/>{< br/> return 0x10; <br/>}</P> <p> while (true) <br/> {<br/> // wait for multiple events <br/> DWORD dwobj = waitformultipleobjects (pobj-> m_hevnetwait, infinite ); </P> <p> If (dwobj = wait_object_0) <br/> {<br/> // jump out of the loop and exit the function <br/> break; <br/>}< br/> else <br/> {<br/> // todo: the action to receive the event <br/>}</P> <p>
Well, efficiency is coming soon. If nothing happens, the thread will rest well. If something happens, it will immediately wake up and then look at the outside world.
However, working properly does not mean elegance. Simply put, if we want to know whether the current thread is running or not?
This is also very simple. We will add a variable to the thread function, set true when entering, and false when exiting. Is it easy?
The Code is as follows:
DWORD cbase: threadproc (lpvoid pparam) <br/>{< br/> cbase * pobj = reinterpret_cast <cbase *> (pparam ); <br/> If (pobj = NULL) <br/>{< br/> return 0x10; <br/>}</P> <p> // set the thread running ID here <br/> interlockedexchange (reinterpret_cast <long *> (& m_bthrdrunning), true ); </P> <p> while (true) <br/> {<br/> // wait for multiple events <br/> DWORD dwobj = waitformultipleobjects (pobj-> m_hevnetwait, infinite); </P> <p> If (dwobj = wait_object_0) <br/> {<br/> // jump out of the loop and exit the function <br/> break; <br/>}< br/> else <br/> {<br/> // todo: the action to receive the event <br/>}</P> <p> // set the thread exit ID here <br/> interlockedexchange (reinterpret_cast <long *> (& m_bthrdrunning ), false); <br/> return 0; <br/>}</P> <p>
It may be strange to see that, because for the m_bthrdrunning variable, it is only in the thread that the value is changed. Why should I sacrifice interlockedexchange? Yes, that's right. If you only need to read the value from the outside without changing it, you just need to simply call the equal sign. So why should we do this? It mainly takes into account the function of shutting down the thread.
To put it simply, the function for shutting down a thread can be divided into two modes. One mode is synchronous, and the other is asynchronous. In other words, when it is in asynchronous mode, we only need to send an event like a thread; if it is in synchronous mode, after the event is sent, we need to determine the exit ID. At this time, interlockedexchange comes in handy. We can use it to make a spin judgment, until it is false, we exit to close the function.
As mentioned above, the function is disabled as follows:
Void cbase: Close (closeflag flag) <br/>{< br/> If (m_bprocrunning! = False) <br/>{< br/> setevent (m_hevent [0]); </P> <p> If (flag = close_async) <br/>{< br/> // asynchronous mode, World return <br/> return; <br/>}</P> <p> // spin wait, until the thread exits <br/> while (interlockedexchange (reinterpret_cast <long *> (& m_bthrdrunning), true) = true) <br/>{< br/> setevent (m_hevent [0]); <br/> sleep (100 ); <br/>}< br/> interlockedexchange (reinterpret_cast <long *> (& m_bthrdrunning), false ); <br/>}</P> <p> // close the thread handle <br/> closehandle (m_hthrd); <br/> m_hthrd = NULL; <br/>}</P> <p>