现在的位置: 首页 > 综合 > 正文

_beginthreadex与createthread和AfxBeginThread的区别

2013年12月08日 ⁄ 综合 ⁄ 共 20321字 ⁄ 字号 评论关闭

1、不要在一个MFC程序中使用_beginthreadex()或CreateThread()。 
这句话的意思是由于AfxBeginThread()是MFC封装的启动线程的函数,里面包含了很多和MFC相关的启动信息,而且封装了一些常用的操作,使用起来也比较简便。而用另外两个函数就需要程序员对类型,安全性检查进行更多的思考! 
2、用_beginthreadex()函数应该是最佳选择,因为_beginthreadex()函数是C Run-time Library 中的函数,函数的参数和数据类型都是C Run-time Library中的类型,这样在启动线程时就不需要进行Windows数据类型和C Run-time Library中的数据类型之间的转化。减低了线程启动时的资源消耗和时间的消耗! 
3、在C++程序中,几乎都要用到new和delete,难道只有使用_beginthreadex()? 
不,因为MFC也是C++类库(只不过是Microsoft的C++类库,不是标准的C++类库),在MFC中也封装了new和delete两中运算符,所以用到new和delete的地方不一定非要使用_beginthreadex() 函数,用其他两个函数都可以! 
其实在程序中使用上面的哪个函数并不是绝对的,书的作者只不过是提了一个更佳的搭配方法,我在 
MFC程序中也经常使用_beginthreadex()和CreateThread()这两个函数,运行的效果也没有多大的区别,有的时候只是需要你额外的进行一些类型检查和其他的一些转化操作,其余没有其他不妥!
简言之:
 AfxBeginThread是MFC的全局函数,是对CreateThread的封装。
 CreateThread是Win32 API函数,前者最终要调到后者。

摘录1:
 CreateThread函数是windows提供的API函数,是SDK的标准形式,在使用的过程中要考虑到进程的同步与互斥的关系,进程间的同步互斥等一系列会导致操作系统死锁的因素,用起来比较繁琐一些,初学的人在用时可能会产生不可预料的错误,建议多用AfxBeginThread,是编译器对原来的CreateThread函数的封装,用于MFC编程(当然,只要修改了项目属性,console和win32项目都能调用)而_beginthread是C的运行库函数。
 使用AfxBeginThread时,线程函数的定义为:UINT _yourThreadFun(LPVOID pParam),参数必须如此。
 使用CreateThread时,线程的函数定义为: DWORD WINAPI _yourThreadFun(LPVOID pParameter)。
 两者实质是一样的,不过AfxBeginThread返回CWinThread指针,就是说它会new一个CWinThread对象,而这个对象在线程运行结束时是会自动删除的,给我们带来的不便是无法获得它的状态,因为随时都有可能这个指针指向的是一个已经无效的内存区域,所以使用时(如果需要了解它的运行状况的话)首先CREATE_SUSPENDED让他挂起,然后m_bAutoDelete=FALSE,接着才ResumeThread,最后不要了delete那个指针。
 CreatThread就方便多了,它返回的是一个句柄,如果你不使用CloseHandle的话就可以通过它安全的了解线程状态,最后不要的时候CloseHandle,Windows才会释放资源,所以我一般使用CreatThread,方便。
摘录2:
 用MFC编程,不要用CreateThread,如果只是使用Runtime Library,用_BegingThread,总之,不要轻易使用CreateThread。这是因为在MFC和RTL中的函数有可能会用到些它们所封装的公用变量,也就是说AfxBeginThread和_BeginThread都有自己的启动代码是CreateThread所没有的。在用CreateThread所创建的线程中使用MFC的类和RTL函数就有可能出现问题。如果你是用汇编编写win32程序并且在线程函数中也不调用MFC和RTL的函数,那用CreateThread就没问题,或者你虽然是用C写线程函数,但你很小心没调用RTL函数也不会有问题。
  CreateThread是由操作系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。
  在可能的情况下,不要调用_beginthread,而应该调用_beginthreadex。以及对应的 _endthreadex。这都是C++运行期函数。但是使用_beginthread,无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程ID,_endthread的情况类似,它不带参数,
  这意味这线程的退出代码必须硬编码为0。这两个函数在_beginthreadex和_endthreadex中进行调用。CreateThread不要进行直接调用。

参考1:http://msdn.microsoft.com/en-us/library/s3w9x78e(v=vs.80).aspx
AfxBeginThread 
Call this function to create a new thread.


CWinThread* AfxBeginThread(
   AFX_THREADPROC pfnThreadProc,
   LPVOID pParam,
   int nPriority = THREAD_PRIORITY_NORMAL,
   UINT nStackSize = 0,
   DWORD dwCreateFlags = 0,
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL 
);


CWinThread* AfxBeginThread(
   CRuntimeClass* pThreadClass,
   int nPriority = THREAD_PRIORITY_NORMAL,
   UINT nStackSize = 0,
   DWORD dwCreateFlags = 0,
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL 
);


Parameters
pfnThreadProc
Points to the controlling function for the worker thread. Cannot be NULL. This function must be declared as follows:
UINT __cdecl MyControllingFunction( LPVOID pParam );
pThreadClass
The RUNTIME_CLASS of an object derived from CWinThread.
pParam
Parameter to be passed to the controlling function as shown in the parameter to the function declaration in pfnThreadProc.
nPriority
The desired priority of the thread.
If 0, the same priority as the creating thread will be used
. For a full list and description of the available priorities, see SetThreadPriority in the Platform SDK.
nStackSize
Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size stack as the creating thread.
dwCreateFlags
Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:
CREATE_SUSPENDED   Start the thread with a suspend count of one. Use
CREATE_SUSPENDED if you want to initialize any member data of the CWinThread object, such as m_bAutoDelete or any members of your derived class, before the thread starts running. Once your initialization
is complete, use CWinThread::ResumeThread to start the thread running. The thread will not execute until CWinThread::ResumeThread is called.

如果需要初始化CWinThread的数据成员,比如m_bAutoDelete,或者你的派生类中的其它成员,要在线程开始运行之前进行。一旦你的初始化完成了,可以调用CWinThread::Resume来运行这个线程。
0   Start the thread immediately after creation.


lpSecurityAttrs
Points to a SECURITY_ATTRIBUTES structure that specifies the security attributes for the thread. If NULL, the same security attributes as the creating thread will be used. For more information on this structure, see the Platform
SDK.


Return Value
Pointer to the newly created thread object, or
NULL if a failure occurs.


Remarks
The first form of AfxBeginThread creates a worker thread.
The second form creates a user-interface thread.
AfxBeginThread creates a new CWinThread object, calls its CreateThread function to start executing the thread, and returns a pointer to the thread. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of
the creation fail. To end the thread, call AfxEndThread from within the thread, or return from the controlling function of the worker thread.
Multithreading must be enabled by the application; otherwise, this function will fail. For more information on enabling multithreading, refer to /MD, /MT, /LD (Use Run-Time Library) under Visual C++ Compiler Options.
For more information on AfxBeginThread, see the articles Multithreading: Creating Worker Threads and Multithreading: Creating User-Interface Threads.

Example
See the example for CSocket::Attach.


MFC Macros and Globals 


Safely using this function
There is a bug which prevents waiting on a thread unless a thread is initialised in the following way. 
CWinThread* thread=AfxBeginThread(DL_Enroll_Base, &ARGUMENT, THREAD_PRIORITY, STACK_SIZE, CREATE_SUSPENDED);
thread->m_bAutoDelete=FALSE;
thread->ResumeThread();
See http://members.cox.net/doug_web/threads.htm for more info

参考2:http://members.cox.net/doug_web/threads.htm
This document answers some common questions concerning multithreaded programming in Visual C++, both with MFC and at the Windows API level. It's structured mainly as a conversation, in which one question and answer leads to the next, so you'll get the most
out of it if you read it as a whole. To give you a quick idea of what I'm going to talk about, the questions are:
Q1   How can I safely use MFC's CWinThread class?
Q2 Should I join with my secondary threads before exiting my program, or is it OK to let the system terminate them for me?
Q3 What are the advantages to using an event synchronization object over a simple bool to ask a thread to exit?
Q4 How can I ask a thread to exit and wait for it, anyway?
Q5 How can I make my program wait for a thread to exit without blocking its user interface or sacrificing its ability to process messages and wait on other objects?
Q6 What's the correct way to use MsgWaitForMultipleObjects?
Q7 How can I control the user's interaction with my program in my MsgWaitForMultipleObjects loop?
Q8 How can my thread notify another thread or a window that it has exited?


Q1. How can I safely use MFC's CWinThread class?
A. To safely use CWinThread, you must start the thread suspended and set the CWinThread object's m_bAutoDelete member to false or DuplicateHandle a copy of its m_hThread member. Only then should you resume the thread. This avoids the race condition resulting
from the default auto-delete behavior, allowing you to wait on the thread handle, something you need to do for reasons given in Q2 below. 

要安全地使用CWinThread,你必须建立一个suspended的thread,并将它的m_bAutoDelete成员设置成false。或者DuplicateHandle一个m_hThread成员的拷贝,然后resume这个“拷贝”出来的线程。这就避免了默认的auto-delete所带来的race
condition,从而允许你在这个线程句柄上等待。

Briefly, the race condition arises because by default, a CWinThread object deletes itself when its associated thread terminates, invalidating any pointer to the CWinThread object you've kept around.
Because this deletion occurs in the secondary thread, it runs asynchronously to the thread that created the CWinThread object, and unless you're very, very careful, the CWinThread pointer you kept can be invalidated while you're still using it. If that wasn't
bad enough, according to theWaitForSingleObject documentation, it's undefined for a handle to be closed while you're waiting on it. Thus, all code which waits on a CWinThread object's m_hThread member while the object auto-deletes itself is undefined, because
auto-deletion also closes m_hThread.
So what are the consequences of starting the thread suspended and dealing with this problem?
If you set m_bAutoDelete to false, you become the owner of the CWinThread object and assume the responsibility for deleting it. On the other hand, if you duplicatem_hThread, you will have to close the duplicated handle when you're
done with it. I find it easier just to change m_bAutoDelete.


Q2. Should I join with my secondary threads before exiting my program, or is it OK to let the system terminate them for me?
A. In general, it's a bad idea to allow secondary threads to continue executing as the application is shutting down. By "application", I mean the primary thread, which is the thread that initializes and destroys global data and runs the mainfunction. (Although
I'm talking about console program functions such as main, everything I say here applies to GUI programs, too.) After you return from main or call exit, the CRT takes over, destroys static duration objects, and performs other termination duties, until the CRT
finally calls ExitProcess, which forcibly terminates all remaining secondary threads. The problem is, these secondary threads have continued to execute oblivious to the fact the environment in which they're running and the data they may be using are being
destroyed all around them. Think Rome, Nero, and a fiddle, and you'll get the picture. To avoid this and achieve an orderly shutdown, you have to join with all your threads before exiting your program, and that means being able to wait on their handles. For
CWinThread, this brings us back to Q1 and the necessity of disabling auto-deletion. This whole auto-deletion concept as applied to threads is pretty much a bad idea, with limited utility.


Q3. What are the advantages to using an event synchronization object over a simple bool to ask a thread to exit?
A. Events are much more flexible. They support polling with WaitForSingleObject, but you can also use them inWaitForMultipleObjects. This comes in handy when you'd like to wait forever on some other object but retain the ability to exit quickly when asked.
You can't use WaitForSingleObject(hObj, INFINITE), because you'd miss thread exit requests that are made before the object you're waiting on is signaled. Some people whose use boolean quitNow variables solve this problem by waiting a second or two and polling
their quitNow variable in a loop. But if you use an event and store it as the first object in theWaitForMultipleObjects handle array, you can wait forever, which means you wait more efficiently, and you normally don't need the while (!quitNow) loop. My code
tends to look like this, where WaitAny is a convenient interface to WaitForMultipleObjects:
// WaitAny is overloaded on up to 5 WaitableHandles, and it waits forever.
// quitEvent is an Event object, and Event is derived from WaitableHandle.
switch (WaitAny(quitEvent, other WaitableHandles))
{
case WAIT_OBJECT_0 :
   clean up and exit;
...other cases
}
This is considerably simpler than constructing a loop to poll a bool at regular intervals, and it's more efficient to boot, especially if you can end up waiting for a long time.


Q4. How can I ask a thread to exit and wait for it, anyway?
A. Assuming you've read everything above, the following should make sense:
// The variable pThread points to a CMyThread that was started
// suspended, and whose m_bAutoDelete member was set to false,
// as described in Q1. The class CMyThread was derived from
// CWinThread and contains an event variable called quitEvent.
SetEvent(pThread->quitEvent);
WaitForSingleObject(pThread->m_hThread, INFINITE);
delete pThread;
This works great if you've designed the secondary thread to exit quickly once quitEvent has been set. However, the waiting thread cannot proceed until the other thread has terminated, so this sequence should not be used unless you can guarantee a timely exit,
or it's acceptable to wait indefinitely. For an MFC program's main GUI thread, it's typically not acceptable to wait indefinitely in WaitForSingleObject, because it prevents the program from processing messages, which makes it unresponsive to user input, redraw
requests, and so forth.


Q5. How can I make my program wait for a thread to exit without blocking its user interface or sacrificing its ability to process messages and wait on other objects?
A. The only way is to resume a message loop, which destroys the modality of what you're currently doing. You must account for this change and keep your program in a good state. For example, you may have to guard against creating another thread until the current
one finishes. You'll have to deal with the user trying to quit your program while the thread is running. And so on. Once you've accounted for all these things, your primary thread still needs to join with the secondary thread and delete theCWinThread object
when the thread has terminated. The easiest way is to do this in response to a custom message posted from the secondary thread to a window belonging to the main thread; you would typically use PostMessage for this. In some cases, you might prefer to wait on
the thread handle in a MsgWaitForMultipleObjects loop, because it lets you vet the incoming messages locally, which relieves you from modifying your main message loop, which in many applications, isn't readily available to modify, because it's buried deep
inside a library like MFC. If you do return to a global message loop, you'll have to maintain a flag that other parts of your program can inspect, so they can decide how to process a given message while your program is in this waiting for a thread to exit
state.


Q6. What's the correct way to use MsgWaitForMultipleObjects?
A. First, you have to understand the new input issue. The documentation says:
MsgWaitForMultipleObjects does not return if there is unread input of  the specified type in the message queue after the thread has called a function to check the queue. This is because functions such as PeekMessage, GetMessage, GetQueueStatus, and WaitMessage
check the queue and then change the state information for the queue so that the input is no longer considered new. A subsequent call to MsgWaitForMultipleObjects will not return until new input of the specified type arrives. The existing unread input (received
prior to the last time the thread checked the queue) is ignored.
In a nutshell, unless you're certain of everything that preceded the MsgWaitForMultipleObjects call, you need to process any queued messages in a PeekMessage loop before dropping into MsgWaitForMultipleObjects. Otherwise, MsgWaitForMultipleObjectsmight not
wake up until additional input becomes available, which can take an indefinite amount of time, even though there is a message waiting for your application at the time you called MsgWaitForMultipleObjects. Here's a sketch of aMsgWaitForMultipleObjects loop
for MFC; it can be slightly simplified by using MsgWaitForMultipleObjectsEx, but then you sacrifice Windows 95 compatibility:
LONG idleCount = 0; 
for (;;) 

   MSG msg; 
   if (PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) 
      theApp.PumpMessage(); 
   else if (!theApp.OnIdle(idleCount++)) 
   { 
      idleCount = 0; 
      if (!PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) 
      { 
         HANDLE hdls[2] = { evQuit, hWhatever }; 
         DWORD res = MsgWaitForMultipleObjects(2, hdls, 
               false, INFINITE, QS_ALLINPUT); 
         if (res == WAIT_OBJECT_0) 
            break; 
         else ... 
      } 
   } 
}
Note carefully the second PeekMessage. It's necessary because an OnIdle handler could have reset the new input flag, causingMsgWaitForMultipleObjects to stall. If you were concerned about being flooded with messages or idle-time processing, such that there
might be a significant delay before MsgWaitForMultipleObjects was called, you could "poll the handles" by injecting someWaitForMultipleObjects calls with timeout parameters of zero, and thus give the handles priority.


Q7. How can I control the user's interaction with my program in my MsgWaitForMultipleObjects loop?
A. You would need to replace this code from Q6 with a version that examines incoming messages:
if (PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE))
   theApp.PumpMessage();
For example, you might want to discard input and show the busy cursor while waiting, which can be appropriate if you need to process messages while waiting but disallow user input. However, the obvious approach of examining the message betweenPeekMessage and
PumpMessage doesn't work. To understand why, consider that WM_PAINT is a low priority message that is deferred as long as higher priority messages are available to be processed. Thus, PeekMessage could return WM_PAINT, which is definitely a message we want
to process, but PumpMessage makes its own call to GetMessage, which could retrieve a higher priority message, were one to be delivered between the PeekMessage and PumpMessage calls. This could be WM_KEYDOWN, for example, one of the messages we want to discard.
My function PumpMessageDiscardingInputMessages avoids this message order inversion problem by ensuring we dispatch the exact message we peeked (and removed, another difference from the PeekMessage/PumpMessage sequence).
// For WM_KICKIDLE 
#include <afxpriv.h> 

// This function returns true if there was a message in the queue.

bool
PumpMessageDiscardingInputMessages(HWND hWnd)

   // It's possible for PeekMessage to return WM_PAINT but for a subsequent 
   // GetMessage to return WM_KEYDOWN. Thus, we can't call PumpMessage 
   // directly, as it does its own GetMessage, which could cause us to
   // process WM_KEYDOWN, one of the messages we want to discard.
   MSG msg; 
   if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 
   { 
      if (msg.message != WM_KICKIDLE) 
      { 
         if (IsInputMessage(msg) 
               && (!hWnd 
                     || msg.hwnd == hWnd 
                     || (msg.hwnd && IsChild(hWnd, msg.hwnd)))) 
            return true;
         // This is one we want to handle, so emulate PumpMessage.
         if (!AfxGetApp()->PreTranslateMessage(&msg)) 
         { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
         } 
      } 
      return true; 
   } 
   return false; 
}
Now, we need to update our MsgWaitForMultipleObjects loop. Below, I'll use MsgWaitForMultipleObjectsEx, which as I mentioned in Q6, simplifies things somewhat, as it allows me to eliminate the new input issue:
LONG idleCount = 0;
for (;;)
{
   if (PumpMessagesDiscardingInputMessages())
      continue;
   else if (!AfxGetApp()->OnIdle(idleCount++))
   {
      idleCount = 0;
      HANDLE hdls[2] = { evQuit, hWhatever }; 
      DWORD const res = MsgWaitForMultipleObjectsEx(
            2,
            hdls,
            INFINITE,
            QS_ALLINPUT,
            MWMO_INPUTAVAILABLE);
      if (res == WAIT_OBJECT_0) 
         break; 
      else ... 
   }
}
Two things are notable:
The PeekMessage/PumpMessage sequence was replaced by the single call to PumpMessagesDiscardingInputMessages in order not to create the conditions for the message order inversion problem described earlier.
Using the MWMO_INPUTAVAILABLE flag with MsgWaitForMultipleObjectsEx allowed me to eliminate the PeekMessage call following OnIdle, whose necessity when using plain old MsgWaitForMultipleObjects I discussed in Q6.
Finally, what's this function I used called IsInputMessage? It looks like this:
namespace {

template<size_t size>
inline bool
FindMsg(const UINT msg, const UINT (&array)[size])
{
   return std::find(array, array+size, msg) != array+size;
}

}

bool
IsInputMessage(const UINT msg, const bool keys, const bool mice, const bool commands)
{
   static const UINT keyMsgTab[] = 
   {
      WM_KEYDOWN,
      WM_KEYUP,
      WM_SYSKEYDOWN,
      WM_SYSKEYUP,
      WM_CONTEXTMENU
   };

   static const UINT miceMsgTab[] = 
   {
      WM_LBUTTONDOWN, WM_LBUTTONUP,
      WM_LBUTTONDBLCLK,
      WM_MBUTTONDOWN, WM_MBUTTONUP,
      WM_MBUTTONDBLCLK,
      WM_RBUTTONDOWN, WM_RBUTTONUP,
      WM_RBUTTONDBLCLK,
      WM_XBUTTONDOWN, WM_XBUTTONUP,
      WM_XBUTTONDBLCLK,
      WM_NCLBUTTONDOWN, WM_NCLBUTTONUP,
      WM_NCLBUTTONDBLCLK,
      WM_NCMBUTTONDOWN, WM_NCMBUTTONUP,
      WM_NCMBUTTONDBLCLK,
      WM_NCRBUTTONDOWN, WM_NCRBUTTONUP,
      WM_NCRBUTTONDBLCLK,
      WM_NCXBUTTONDOWN, WM_NCXBUTTONUP,
      WM_NCXBUTTONDBLCLK,
   };

   static const UINT commandMsgTab[] = 
   {
      WM_COMMAND,
      WM_SYSCOMMAND,
      WM_CONTEXTMENU
   };

   return (keys && FindMsg(msg, keyMsgTab))
         || (commands && FindMsg(msg, commandMsgTab))
         || (mice && FindMsg(msg, miceMsgTab));
}
This simple function identifies keyboard, mouse, and command messages, returning true if the message is one you're looking for, and false otherwise.


Q8. How can my thread notify another thread or a window that it has exited?
A. Sometimes it is useful for a thread to notify another thread that it exited. For example, the primary thread might want to be notified when a secondary thread has finished, so that it can free resources it allocated for use by the thread, change UI state
to reflect the current "job" has finished, etc. Having the secondary thread notify the primary relieves the primary from polling the secondary to determine these things, and eliminating polling is a good thing. So how do we do it? The best way is to post a
user-defined message in the WM_APP range or obtained from RegisterWindowMessage. (For more on user-defined messages, see Raymond Chen's article, Which message numbers belong to whom?) If the primary thread has created no windows but runs a single message loop
to handle posted thread messages, then PostThreadMessage is a good choice. (To understand these restrictions and when that function can be a bad choice, see KB article PRB: PostThreadMessage Messages Lost When Posted to UI Thread.) Most of the time, however,
the primary thread is an UI thread that manages windows, and PostThreadMessage is inappropriate for reasons given in the KB article. In such cases, the best approach is to designate a target window to receive the notification, which the secondary thread posts
with PostMessage.
Of course, the secondary thread doesn't exit immediately after it PostMessages the UWM_THREADEXITED message, and for the reasons given in Q2, someone, usually the recipient of the exit message, must wait on the secondary thread to really exit. That is, your
message handler should look something like this:
   LRESULT ThreadOwnerWindow::OnThreadExited(WPARAM, LPARAM lp)
   {
      CWinThread* pThread = reinterpret_cast<CWinThread*>(lp);
      threads[pThread] = 0; // 1
      WaitForSingleObject(pThread->m_hThread);
      delete pThread;
      return 0;
   }
The line commented (1) exists because the ThreadOwnerWindow owns the CWinThread object as discussed in Q1; after the thread has died, it's only appropriate for ThreadOwnerWindow to zero the pointer it holds to the thread. (You can perform this mapping however
you like.) But what about the WaitForSingleObject call? Since it blocks the UI, isn't it bad? Not at all! Posting this message is the last thing the secondary thread does, and you can expect it to terminate without delay. Indeed, Q4 says about usingWaitForSingleObject
in a similar situation, "This works great if you've designed the secondary thread to exit quickly," and exit quickly it does if you've designed things properly.

Q9. SendMessage vs. PostMessage

抱歉!评论已关闭.