创建线程
_beginthreadex函数:创建一个线程。
函数原型:
unsigned long _beginthreadex(
void *security, /*SECURITY_ATTRIBUTES构造体指针.它是定义函数返回handle是否继承于子进程;
也可NULL,handle不能继承;Win95必须是NULL */
unsigned stack_size,/* 新线程的堆栈大小 可为0 */
unsigned (__stdcall *start_address)(void *),/*新线程的起始地址,线程函数的指针 static */
void *arglist, /* 传给线程函数的参数的指针 */
unsigned initflag, /*新线程的初始状态(0:运行;CREATE_SUSPENDED:暂停;ResumeThread:执行线程)
unsigned *thrdaddr /*新线程的地址*/
);
ex.
#include "stdafx.h"
#include
#include
#include
#include //malloc
#include //for _beginthreadex && Project Setting->C/C++->Code Generation->Use run-time libray->Debug Multithread(MTd) or Multithread(MT)
struct stuArgs {
DWORD ParentThreadId;
unsigned int param;
};
volatile int ThreadRunFlg = 0;
//__declspec ( thread ) int ThreadRunFlg2= 0; //线程局部存储 (TLS)
int funcReal(struct stuArgs * args){
DWORD ParentThreadId =args->ParentThreadId;
unsigned int j=args->param ;
ThreadRunFlg=2;
printf("%s\n","New thread running funcReal().");
for (unsigned int i=0;i printf("%d\n",i);
}
Sleep(1000);
printf("%s\n","funcReal() finished.");
ThreadRunFlg=0;
return 0;
}
int main(int argc, char* argv[])
{
struct stuArgs * args =(struct stuArgs *) malloc(sizeof(*args));
if (args == NULL)
return -1;
args->ParentThreadId = GetCurrentThreadId();
args->param = 50;
unsigned threadID= 0;
void(*func)(void *) =(void(*)(void*))funcReal; //线程函数传空的时候
printf("%s\n","start a new thread...");
HANDLE ThreadHandle =(HANDLE)_beginthreadex(NULL,512,(unsigned(__stdcall *)(void *))func,args,0,&threadID);
printf("A new thread has been created %d\n",threadID);
if (ThreadHandle ) {
ThreadRunFlg=1;
}
else{
free(args);
//outlog(__FILE__,__LINE__,GetLastError);
return -1;
}
while(ThreadRunFlg){
printf("%s\n","Waiting...");
Sleep(100);
}
printf("%s\n","Met!");
return 0;
}
*--->_beginthread,CreateThread,AfxBeginThread
相关连结
2012/4/22 GetExitCodeThread exp.
线程局部存储 (TLS)
来自:
同一进程中的所有线程共享相同的虚拟地址空间。不同的线程中的局部变量有不同的副本,但是static和globl变量是同一进程中的所有线程共享的。使用TLS技术可以为static和globl的变量,根据当前进程的线程数量创建一个array,每个线程可以通过array的index来访问对应的变量,这样也就保证了static和global的变量为每一个线程都创建不同的副本。
--->本博文<C++ __declspec 的用法>
PS: (TLS的实现原理与API解释)
1:在多线程的进程中,为每一个static或是global变量创建一个void*的数组,使变量不同线程都有一个拷贝,然后拷贝的指针放入Void*的数组中。
2:TlsAlloc() 得到对应于一个static或global变量所对应的void*的数组的索引,这个用来标示不同static或global变量所对应的void*的数组。
3:LocalAlloc()用来分配变量在此线程的拷贝的指针。
4:TlsSetValue()用来把刚分配的指针加到所对应的void*的数组中,加入后一般对会对此指针赋值供此线程使用。
5:TlsGetValue()用来从对应的void*的数组中找到此线程所对应的拷贝的指针。
6: TlsFree() 释放整个void*的指针数组。
线程的同步机制
!2C8E21A6BC751D70!656&_c11_BlogPart_BlogPart=blogview&_c=BlogPart
使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象:
◆临界区(CCriticalSection)
◆事件(CEvent)
◆互斥量(CMutex)
◆信号量(CSemaphore)
通过这些类,我们可以比较容易地做到线程同步。
重要的一点:Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,
而CriticalSection只能在线程间使用。
1、 Event
用事件(Event)来同步线程是最具弹性的了。
一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。
事件又分两种类型:手动重置事件和自动重置事件。
手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。
自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为CEvent.。CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。
2、 Critical Section
使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。CriticalSection在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。
3、 Mutex
互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比CriticalSection多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像CriticalSection那样无法得知临界区域的情况,而一直死等。MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入CriticalSection一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。
4、 Semaphore
信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。对应的MFC类是Csemaphore。Win32函数CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当时不会超过初始设定的资源总数。
>>>详解>>>
A、使用 CCriticalSection 类
当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。
CCriticalSection类的用法非常简单,步骤如下:
定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section;
在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock(); 在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。
访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock(); 再通俗一点讲,就是线程A执行到critical_section.Lock();语句时,如果其它线程(B)正在执行critical_section.Lock();语句后且critical_section. Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section. Unlock();语句,线程A才会继续执行。 一个实例。
例程1 MultiThread8
建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件,两个按钮的ID分别为IDC_WRITEW和IDC_WRITED,标题分别为“写‘W’”和“写‘D’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
在MultiThread8Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam); UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
在MultiThread8Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:
#include "afxmt.h"
定义临界区和一个字符数组,为了能够在不同线程间使用,定义为全局变量:
CCriticalSection critical_section;
char g_Array[10];
添加线程函数:
UINT WriteW(LPVOID pParam)
{ CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
critical_section.Lock(); //锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i<10;i++)
{ g_Array[i]=''W'';
pEdit->SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
UINT WriteD(LPVOID pParam) {
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
critical_section.Lock(); //锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
//直至执行critical_section.Unlock();语句
for(int i=0;i<10;i++) {
g_Array[i]=''D'';
pEdit->SetWindowText(g_Array);
Sleep(1000);
}
critical_section.Unlock();
return 0;
}
5.分别双击按钮IDC_WRITEW和IDC_WRITED,添加其响应函数:
void CMultiThread8Dlg::OnWritew()
{ CWinThread *pWriteW=AfxBeginThread(WriteW,
&m_ctrlW,
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
pWriteW->ResumeThread();
}
void CMultiThread8Dlg::OnWrited() {
CWinThread *pWriteD=AfxBeginThread(WriteD, &m_ctrlD,
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
pWriteD->ResumeThread();
}
B、使用 CEvent 类
CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent 类,线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent 类对象的状态,并在相应的时候采取相应的操作。
在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent 对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent 类的对象时,默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下:
1、CEvent(BOOL bInitiallyOwn=FALSE,
BOOL bManualReset=FALSE,
LPCTSTR lpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
2、BOOL CEvent::SetEvent();
将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent 类对象由系统自动重置为无信号状态。
如果该函数执行成功,则返回非零值,否则返回零。
3、BOOL CEvent::ResetEvent();
该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。
例程2 MultiThread9
1. 建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;
2. 在MultiThread9Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam); UINT WriteD(LPVOID pParam);
3. 使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
4. 在MultiThread9Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加
#include "afxmt.h"
定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。
CEvent eventWriteD;
char g_Array[10];
添加线程函数:
UINT WriteW(LPVOID pParam) {
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
for(int i=0;i<10;i++)
{ g_Array[i]=''W'';
pEdit->SetWindowText(g_Array);
Sleep(1000);
}
eventWriteD.SetEvent();
return 0;
}
UINT WriteD(LPVOID pParam) {
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
WaitForSingleObject(eventWriteD.m_hObject,INFINITE);
for(int i=0;i<10;i++) {
g_Array[i]=''D'';
pEdit->SetWindowText(g_Array);
Sleep(1000);
}
return 0;
}
仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。
5. 双击按钮IDC_WRITEW,添加其响应函数:
void CMultiThread9Dlg::OnWritew() {
CWinThread *pWriteW=AfxBeginThread(WriteW,
&m_ctrlW,
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
pWriteW->ResumeThread();
CWinThread *pWriteD=AfxBeginThread(WriteD,
&m_ctrlD,
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
pWriteD->ResumeThread();
}
编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。
C、使用CMutex 类
互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。
例,防止程序启动两次的方法CreateMutex()
在工程文件中, WinMain函数里加上以下代码(此代码在BCB6.0下运行):
HANDLE hMutex = CreateMutex(NULL, false, "Process");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hMutex);
MessageBox(Application->Handle, "程序已经在运行中,不能重复启动!", "提示", MB_OK +MB_ICONWARNING);
Application->Terminate();
return 0;
}
Application->CreateForm(__classid(TForm1), &Form1);
D、使用CSemaphore 类
当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore 类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。
CSemaphore 类的构造函数原型及参数说明如下:
CSemaphore (LONG lInitialCount=1,
LONG lMaxCount=1,
LPCTSTR pstrName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;
lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;
后两个参数在同一进程中使用一般为NULL,不作过多讨论;
在用CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。
下面给出一个简单实例来说明 CSemaphore 类的用法。
例程3 MultiThread10
1. 建立一个基于对话框的工程MultiThread10,在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件,按钮的ID为IDC_START,标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中Read-only;
2. 在MultiThread10Dlg.h文件中声明两个线程函数:
UINT WriteA(LPVOID pParam);
UINT WriteB(LPVOID pParam);
UINT WriteC(LPVOID pParam);
3. 使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC;
4. 在MultiThread10Dlg.cpp文件中添加如下内容:
为了文件中能够正确使用同步类,在文件开头添加:
#include "afxmt.h"
定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:
CSemaphore semaphoreWrite(2,2);//资源最多访问线程2个,当前可访问线程数2个 char g_Array[10];
添加三个线程函数:
UINT WriteA(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i<10;i++)
{
pEdit->GetWindowText(str);
g_Array[i]=''A'';
str=str+g_Array[i];
pEdit->SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteB(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
CString str;
for(int i=0;i<10;i++)
{
pEdit->GetWindowText(str);
g_Array[i]=''B'';
str=str+g_Array[i];
pEdit->SetWindowText(str);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
UINT WriteC(LPVOID pParam)
{
CEdit *pEdit=(CEdit*)pParam;
pEdit->SetWindowText("");
WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
for(int i=0;i<10;i++)
{
g_Array[i]=''C'';
pEdit->SetWindowText(g_Array);
Sleep(1000);
}
ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
return 0;
}
这三个线程函数不再多说。在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。
双击按钮IDC_START,添加其响应函数:
void CMultiThread10Dlg::OnStart()
{
CWinThread *pWriteA=AfxBeginThread(WriteA,
&m_ctrlA,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteA->ResumeThread();
CWinThread *pWriteB=AfxBeginThread(WriteB,
&m_ctrlB,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteB->ResumeThread();
CWinThread *pWriteC=AfxBeginThread(WriteC,
&m_ctrlC,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
pWriteC->ResumeThread();
}
>>>>自测代码(VC6.0)
■用事件和消息进行线程间通讯
ex.
//
#include "stdafx.h"
//#define UNICODE
#include
#include
#include
#include //malloc
#include
//for _beginthreadex && Project Setting->C/C++->Code Generation->Use run-time libray->Debug Multithread(MTd) or Multithread(MT)
struct stuArgs
{
DWORD ParentThreadId;
unsigned int param;
HANDLE hEvt;
};
//Thread Function
int funcReal(struct stuArgs * args)
{
printf("%s\n","New thread running funcReal().");
printf("ParentThreadId=%d\n",args->ParentThreadId);
printf("args->param=%d\n",args->param);
for (unsigned int i=0;i
param ;i++)
printf("%d\n",i);
Sleep(1000);
//funcReal notify Thread-stated-Event
if(!SetEvent(args->hEvt))
{
printf("***********SetEvent ERROR!\n");
_endthreadex(0);
}
printf("GetMessage............\n");
MSG WinMsg;
while(GetMessage(&WinMsg, (HWND)NULL, 0, 0)) //get WM_QUIT return 0
{
/* printf("GetMessage\n");
TranslateMessage(&WinMsg);
DispatchMessage(&WinMsg);*/
}
printf("%s\n","funcReal() finished.");
return 0;
}
int main(int argc, char* argv[])
{
// MultiThread
struct stuArgs * args =(struct stuArgs *) malloc(sizeof(*args));
if (args == NULL)
return -1;
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
args->ParentThreadId = GetCurrentThreadId();
args->param = 50;
args->hEvt =hEvent;
printf("args->ParentThreadId()=%d\n",args->ParentThreadId);
unsigned threadID= 0;
//void(*func)(void *) =(void(*)(void*))funcReal;
printf("%s\n","start a new thread...");
HANDLE ThreadHandle =(HANDLE)_beginthreadex(NULL,512,(unsigned(__stdcall *)(void *))funcReal,args,0,&threadID);
if (ThreadHandle )
{
printf("A new thread has been created %d\n",threadID);
}
else
{
free(args);
//outlog(__FILE__,__LINE__,GetLastError);
return -1;
}
printf("%s\n","Waiting...");
HANDLE hWait[2];
hWait[0] = hEvent;
hWait[1] = ThreadHandle;
DWORD wRe = WaitForMultipleObjects(2, hWait, FALSE, INFINITE); //wRe is the x of hWait[x]
printf("WaitFor=%d--->have get Thread-stated-Event\n",wRe);
while('S'!=getchar())
{
printf("%s\n","Input 'S' to stop.");
}
PostThreadMessage(threadID, 0,0,0); //When GetMessage is on,Send MSG to stop thread
if(ThreadHandle)
{
//Waitfor ThreadHandle
printf("Waitfor ThreadHandle.\n");
WaitForSingleObject(ThreadHandle, INFINITE);
printf("%s\n","Yeah ~~have Met!");
CloseHandle(ThreadHandle) ;
}
return 0;
}
//-----------------------------------------------------------------
VC 线程创建及关闭
★两套API :OS API vs CRT API
本来照例要先介绍线程的几种死法,但是考虑到很多Windows程序员经常混淆线程API,搞不清楚到底该用哪个。所以先来说一下两套线程API的问题。
首先,Windows操作系统本身提供了线程的创建函数CreateThread 和销毁函数ExitThread 。其中的CreateThread 用于创建线程,ExitThread 用于在线程函数内部推出线程(也就是自杀)。
其次,在Visual C++自带的C运行库(以下简称CRT)中,还带了另外4个API函数,分别是:_beginthread ,_endthread ,_beginthreadex ,_endthreadex 。其中的_beginthread 和_beginthreadex 用于创建线程(它们内部调用了CreateThread ),_endthread 和_endthreadex 用于自杀(它们内部调用了ExitThread )。
有同学看到这里,被搞懵了,心想:“干嘛要搞这么多玩意儿出来糊弄人?有CreateThread 和ExitThread 不就够了嘛!”其实你有所不知,此中大有奥妙啊。
因为OS API作为操作系统本身提供的API函数,它被设计为语言无关的。它们不光可以被C++调用,还可以被其它诸如VB、Python、Delphi等开发语言来调用。所以它们不会(也不能够)帮你处理一些和具体编程语言相关的琐事。
而CRT API虽然最终还是要调用OS API来完成核心的功能,但是CRT API在不知不觉中多帮我们干了一些虽琐碎但重要的工作。(如果同学们想窥探一下CRT API内部都干了些啥,可以拜读一下Win32编程的经典名着《Windows 核心编程》的6.7 章节,里面介绍得挺细致的)
费了这么多口水,无非是要同学们牢记:以后在Windows平台下开发多线程程序,千万不要 直接使用这两个线程API(也就是CreateThread 和ExitThread ),否则后果自负 :-)
另外,顺便补充一下。除了上述提到的CRT库。其它一些Windows平台的C++库也可能提供了线程的启动函数(比如MFC的AfxBeginThread),这些函数也对OS API进行了包装,所以用起来也是安全的。
★三种死法
说完了两套API,开始来讨论一下线程的几种死法。线程和进程一样,也有三种死法。详见如下:
1、自然死亡
一般来说,每个线程都会对应某个函数(以下称为“线程函数”)。线程函数是线程运行的主体。所谓的“自然死亡”,就是通过return 语句结束线程函数的执行。
2、自杀
所谓的“自杀”,就是当前线程通过调用某API把自己 给停掉。前面已经说了OS API的坏话,同学们应该明白不能 再用它们。那我们能否使用CRT API来进行自杀呢?请看MSDN上的相关文档 。上面说了,如果使用_endthread 和_endthreadex ,将导致析构函数不被 调用。
3、它杀
所谓的“它杀”,很明显,就是其它线程通过调用某API把当前线程给强行 停掉。对于Windows平台来说,实现“它杀”比较简单,使用TernimateThread 就直接干掉了(它杀也是最野蛮的)。
★类对象的析构
把类对象分为三种:局部非静态对象、局部静态对象、非局部对象。由于非局部对象是在main之前就创建、在进程死亡时析构,暂时与线程扯不上太大关系。剩下的两种局部对象,在宿主线程(所谓宿主线程,就是创建该局部对象的线程)死亡时会受到什么影响捏?请看如下的对照表:
-------------------------
局部非静态对象 局部静态对象
自然死亡 能 能
自杀 不能 能
它杀 不能 能
-------------------------
从上述结果可以看出,Windows上线程的死法还是以自然死亡为最安全,这点和进程的死法类似。所以同学们在Windows上开发时,要尽量避免自杀和它杀。
★关于主线程之死
所谓“主线程”,就是进程启动时,操作系统为该进程默认创建的第一个线程。通俗地讲,可以把main 函数看成是主线程的线程函数。
主线程之死是有讲究的。由于前面已经阐述了非自然死亡的坏处,所以我们只讨论主线程自然死亡这一种情况。当主线程自然死亡时(也就是用return 从main 返回时),会导致exit 函数被调用,exit 函数就会开始清除当前进程的各种资源,为进程的死亡作准备。这时候,如果还有其它活着的线程,也会被一起干掉(其效果类似于它杀)。
为了防止出现上述情况,主线程一定要负责最终的善后工作。务必等到其它线程都死了,它才能死。
一个等待子线程结束的例子:
#include
#include
#include
HANDLE g_hEvent;
UINT _stdcall ChildFunc(LPVOID);
int main(int argc, char* argv[])
{
HANDLE hChildThread;
UINT uId;
// 创建一个自动重置的(auto-reset events),未受信的(nonsignaled)事件内核对象
g_hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
hChildThread = (HANDLE)::_beginthreadex(NULL, 0, ChildFunc, NULL, 0, &uId);
// 通知子线程开始工作
printf("Please input a char to tell the Child Thread to work:\n");
getchar();
::SetEvent(g_hEvent);
// 等待子线程完成工作,释放资源
::WaitForSingleObject(hChildThread, INFINITE);
printf("All the work has been finished.\n");
::CloseHandle(hChildThread);
::CloseHandle(g_hEvent);
return 0;
}
UINT _stdcall ChildFunc(LPVOID)
{
::WaitForSingleObject(g_hEvent, INFINITE);
printf(" Child thread is working......\n");
::Sleep(5*1000);
return 0;
}