Win32多线程程序设计(二)

序:

本讲主要介绍产生,监视,退出线程的Win32函数及一个线程的运转过程

1.一个单纯的函数调用和通过启动线程调用的比较

面对一个单纯的函数调用操作,控制权会转移到被调用函数中,执行完毕后再返回到原调用处,如:

void main()

{

       int resuilt;

       resuilt = square(5);

       print(resuilt);

}

int resuilt(int n)

{

       …

       …

}

2.启动一个线程调用函数

启动线程调用函数时,我们不直接调用函数,而是通过CreateThread()创建一个线程,把要调用的函数的地址传给这个线程,在这个新线程中调用函数,原来的线程继续前进,即函数调用异步的进行了,一旦被调用的函数启动,它就完全独立于原调用端,如:

DWORD WINAPI ThreadFunc(LPVOID)

 void main()

{

       HANDLE hThread;

       DWORD threadId;

       hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threaded);

       printf(“Thread Running”);

}

DWORD WINAPI ThreadFunc(LPVOID p)

{

       …

       …

}

补充:

当一个函数被调用时,函数的参数会被传递给被调用的函数,返回值会被返回给调用函数,函数的调用约定是描述参数是怎样传递,返回值是如何返回及调用堆栈是如何清理的

常见的函数调用约定有__stdcall,__cdecl,__pascal

当我们在函数的前面用__stdcall作为修饰符时,函数就会采用__stdcall调用约定,如:

int __stdcall func(void);

Win32 API函数大部分采用__stdcall

#define WINAPI __stdcall

 

 3.多线程带来的问题

1)      程序无法预期

2)      执行次序无法保证

3)      线程对于小的改变有高度的敏感

   在多线程程序中,如果采用printf()调试程序时,可能会完全改变多线程程序的行为

4)      创建的线程并不总是立刻启动

 

4.内核对象,GDI对象和USER对象

  在使用c++进行windows编程时,程序员除了管理使用new/malloc动态从堆上分配的内存外,还需要对windows的内核对象,GDI对象和USER对象进行管理,这些对象使用句柄来标示,通过操作这些句柄就使用不同的资源对象,和堆内存一样,程序员也需要管理这些对象资源,以免造成系统资源泄漏

  句柄(HANDLE)是WINDOWS用来标示被应用程序建立或使用的对象的唯一整数,句柄实际上是指向某种资源的指针,但与指针又有所不同,指针对应着一个数据所在内存的地址,得到指针就可以自由的修改该数据,但WINDOWS不希望应用程序修改其内部数据结构,故用句柄标示这个内部数据结构,使用户无法修改

  GDI对象和USER对象只能有单一的拥有者,不是进程就是线程,GDI对象与绘图相关,USER对象与交互相关

  内核对象的直接拥有者是操作系统内核,内核对象可以有多个拥有者即可被多个进程或线程拥有,故内核对象保持一个引用计数以对每个拥有者进行跟踪,这样才能保证内核对象被正确的创建和销毁,当一个进程或线程创建一个内核对象,对象的引用计数为1,如果该对象又被另外的进程或线程共享,每多一个进程,引用计数加1,当一个进程调用CloseHandle()函数后,引用计数减1,如果引用计数变为0,操作系统会销毁该内核对象

5.CloseHanle()的重要性

  内核对象的销毁需要借助CloseHanle(),拥有内核对象的进程或线程不在使用该内核对象时调用CloseHanle()减少该内核对象的引用计数,当引用计数减为0时,操作系统销毁此内核对象

  GDI对象和USER对象的销毁不需要调用CloseHanle(),每一个GDI对象和USER对象的销毁都有其对象的Destroy或Delete方法

补充:

对内核对象的逻辑上的清除及实体上的清除:

  如果一个进程或线程在结束之前,没有针对它所打开的核心对象调用CloseHandle(),则当该进程或线程结束后,操作系统自动把这些对象的引用计数降1,这就是所谓的系统做实体上的清除工作

  逻辑上的清除工作是指进程或线程通过CloseHandle()清除所拥有的核心对象

  我们一般不要依赖系统做实体上的清除工作,这样容易引起资源泄漏

补充:

线程核心对象和线程的不同之处:

  线程的handle指向”线程核心对象”,而不是指向线程本身,当调用CloseHandle()并传给其一个线程handle时,你只不过是表示自己和此核心对象不再有任何瓜葛,CloseHanle()唯一做的事情就是把引用计数减1,如果值变为0,对象就会被操作系统摧毁

  线程核心对象的默认引用计数是2,当调用CloseHandle()时,引用计数下降1,当线程结束时,引用计数在降1,只有这两件事情都发生了,不管顺序如何,线程核心对象才会被清除(可以把线程理解为一个特殊的核心对象,其初始的引用计数为2)

创建一个线程的代码段:

int main()

{

       HANDLE hThread;

       DWORD threaded;

       int i;

       hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);

       if(hThread)

       {

              printf(“Thread launched!”);

     CloseHandle(hThread);    //该行的目的是,使创建线程的线程与被创建的线程在无瓜葛,这样,当被创建的线程结束后可以自动被销毁

       }

       Sleep(2000);

       return 0;

}

注1:

  主线程中创建一个线程后,在主线程中调用CloseHandle(),传入返回的线程HANDLE,表示主线程与新创建的线程内核对象在无瓜葛,因为线程内核对象的默认引用计数为2,调用CloseHanle()后,计数变为1,当线程运行结束后计数变为0,此时线程内核对象被销毁,如果未调用CloseHanle(),即使线程运行结束,如果此时创建该线程的线程未结束,则被创建的线程不能销毁,因为此时计数依然为1,只有当创建该线程的线程销毁后,该线程内核对象才被销毁

注2:

要理解内核对象和GDI对象,USER对象的不同

进一步理解,线程作为内核对象和其他内核对象的不同

 

6.线程结束代码

线程结束代码的概念:

  之前我们提过线程入口函数的固定格式是DWORD WINAPI ThreadFun(LPVOID),这个DWORD类型的返回值就是线程结束代码,我们可以通过GetExitCodeThread()获得指定线程的线程结束代码从而得知线程的状态,也可以通过ExitThread()强制结束一个线程,并指定该线程的结束代码

  线程函数有返回值,我们把这个返回值作为线程结束代码,通过获取此返回值,来检验线程的执行状态,或强制结束一个线程并指定这个返回值,之前我们在主线程的最后放一个Sleep(2000)函数,来确保线程函数真正的执行完了,然后结束掉主线程,进而结束整个进程(主线程的地位),实际上,我们无法保证在2000ms内,线程函数真的执行完成了,有可能Sleep()返回时那些线程函数尚未结束

1)   线程函数执行完后,结束这个线程

  我们希望存在一个机制可以检验线程函数是否真的执行完成了,如果真正结束了,则结束主线程,否则继续等待,而不是通过让主线程睡眠一个大概的时间段然后直接结束主线程

GetExitCodeThread()给我们提供了一个这样的机制

BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);

参数:

hThread:要检测的线程HANDLE

lpExitCode:是一个返回值,指向一个DWORD,返回线程函数的执行状态

返回值:

如果线程已经结束,那么线程的结束代码会放在lpExitCode参数中返回,如果线程尚未结束,lpExitCode返回的值是STILL_ACTIVE

注:

GetExitCodeThread()将传回线程函数(ThreadFunc)的返回值,通过检测lpExitCode的返回值是否为STILL_ACTIVE来判断线程函数是否执行完成

 

2) 强制结束一个线程

 

之前我们在线程函数执行完成后结束线程,有时候可能需要强制结束一个线程,而不管线程函数是否执行完成

ExitThread()

VOID ExitThread(DWORD dwExitCode);

参数:

dwExitCode:指定此线程的结束代码

返回值:

无返回值

注:线程入口函数的返回值为DWORD类型是线程的结束代码,我们可以通过GetExitCodeThread()获取这个退出码,如果线程没有退出,则获取的值是一个常量STILL_ACTIVE(259),这样我们可以通过退出码来判断线程是否已退出,也可以通过ExitThread()强制结束一个线程,并指定要结束的线程的结束代码

结束语:

线程某些程序上,是提供了一种异步调用函数的方式

posted @ 2012-11-12 16:56  liuhao2638  阅读(399)  评论(0编辑  收藏  举报