Win 32 多线程程序设计学习笔记之二:线程

1.产生一个线程,是以CreateThread()作为一切行动的开始,这个函数的原型如下:

HANDLE CreateThread(  LPSECURITY_ATTRIBUTES lpsa,   
	DWORD cbStack, 
  	LPTHREAD_START_ROUTINE lpStartAddr,   
	LPVOID lpvThreadParam, 
  	DWORD fdwCreate,   
	LPDWORD lpIDThread
	); 

参数说明:

lpsa:描述施行于这一新线程的security属性。NULL表示使用缺省值。

cbStack,:新线程拥有自己的堆栈。0表示使用缺省大小:1MB

lpStartAddr:新线程将开始的起始地址。这是一个函数指针

lpvThreadParam:此值将被传送到上述所指定的新线程中去,作为参数

fdwCreate:允许你产生一个暂时挂起的线程,默认情况是“立即开始执行”

lpIDThread:新线程的ID会被传送回到这里

返回值:如果CreateThread()成功,传回一个handle,代表新线程,否则传回一个False。如果失败,可以调用GetLastError()获知原因。


lpStartAddr中的函数指针的格式:

DWORD WINAPI ThreadFunc(LPVOID n)

CreateThread()函数声明中期望的第三个参数是一个函数指针,指向某种特定类型的函数:返回值是DWORD,调用约定是WINAPI,有一个LPVOID参数。


多线程程序是无法预测其行为;

线程的执行顺序无法保证(线程彼此之间的执行顺序应该视之为随机);

线程对于小的改变有高度的敏感(程序代码的小改变,可以引起戏剧性的变化);

线程并不总是立刻执行


2.核心对象

    CreateThread()传回两个值,用来标示一个新的线程。第一个值是个HANDLE,这就是CreateThread()的返回值,大部分与线程有关的API函数都需要它。第二个值是有lpIDThread带回来的线程ID。线程ID是一个全局变量,可以独一无二地标示系统任意进程中的某个线程。AttachThreadInput()和PostThreadMessage()就需要用到线程ID,这两个函数运行你影响其他线程的消息队列。调试器和进程观察器也需要线程ID。为了安全防护的缘故,你不可能根据一个线程的ID而获取其handle。

    CreateThread()传回来的handle被称为一个核心对象(kernel object)。核心对象其实和所谓的GDI对象,如画笔,画刷,或者DC是差不多的,只不过它是由KERNEL32.DLL管理,而非GDI32.DLL管理。这两种对象之间有许多相似性。

      GDI对象是WIndows的基础部分。在Win16和Win32中他们都是由操作系统管理。通常你不需要知道其数据格式。你可能会调用SelectObject()或ReleaseObject()以处理GDI对象;Windows隐藏了实现细节,只是给你一个HDC或一个HBRUSH,那都是对象的handle。

      核心对象以HANDLE为使用时的参考依据。与GDI的HBRUSH,HPEN,HPALETTE以及其他种handles不同的是,只有一种handle可以表示核心对象。所谓handle,其实就是一个指针,指向操作系统内存空间中的某样东西,那东西不允许你直接取得。你的程序不能够直接取用它,为的是维护系统的完整性与安全性。

    为in2中核心对象如下:

  • 进程(process)
  • 线程(threads)
  • 文件(files)
  • 事件(events)
  • 信号量(semaphores)
  • 互斥器(mutexes)
  • 管道(Pipes,分为named和anonymous两种)
        GDI对象和核心对象之间有一个主要的不同,GDI对象有单一拥有者,不是进程就是线程。核心对象可以有一个以上的拥有者,甚至可以跨进程。为了保持对每一个拥有者的追踪,核心对象保持了一个引用计数(reference count),以记录有多少handles对应到此对象。对象中也记录了哪一个进程或线程是拥有者。如果你调用CreateThread或是其他会传回handle的函数,引用计数便会累加1.当你调用CloseHandle()时,引用计数便递减1.一旦引用计数降为0,这一核心对象即自动被摧毁。
        面对一个打开的对象,区分其拥有者是进程或是线程,是件很重要的事情。因为这会决定系统何时做清楚善后(clean up)操作。所谓clean up操作,包括将该进程或线程所拥有的每一个对象的引用计数减1.若有必要,则进程会被摧毁。程序员不能选择由进程或线程拥有对象,一切得视对象类型而定。
BOOL CloseHandle( 
  HANDLE hObject
); 

参数说明:
hObject:代表一个已打开的对象的handle
如果成功,传回True。如果失败则传回FALSE,此时可以调用GetLastError()获知失败原因。
        如果一个进程没有在结束之间针对它所打开的核心对象调用CloseHandle(),操作系统会自动把那些对象的引用计数下降1.虽然你可以依赖系统做实体上的清除(clean up)工作,然而逻辑上的清除工作又是完全不同的一回事,特别是如果你有许多个进程的话。因为系统不知道对象实际代表什么意义,所以他不可能知道结构顺序是否重要。
       如果一个进程常常产生“work 线程”(所谓work线程,是指完全不牵连到图像用户界面,纯粹做运算的线程)而老师不关闭线程的handle,那么这个进程可能最终有数百甚至数千个开启的“线程核心对象”留给操作系统去清理。这样的资源泄露(resource leaks)可能会对效率带来负面的影响。
     你不可能依赖“因线程的结束而清理所有被这一线程产生的核心对象”。许多对象,例如文件,是被进程拥有,而非被线程拥有。在进程结束之前不能够清理它们。
        线程的handle 是指向“线程核心对象”,而不是指向线程本身。对大部分API 而言,这项差异没什么影响。当你调用CloseHandle( )并给予它一个
线程handle 时,你只不过是表示,你希望自己和此核心对象不再有任何瓜葛。CloseHandle( )唯一做的事情就是把引用计数减1。如果该值变成0,对象会自动被操作系统摧毁。
        “线程核心对象”引用到的那个线程也会令核心对象开启。因此,线程对象的默认引用计数是2。当你调用CloseHandle( )时,引用计数下降1,当线程结束时,引用计数再降1。只有当两件事情都发生了(不管顺序如何)的时候,这个对象才会被真正清除。
        “引用计数”机制保证新的线程有个地方可以写下其返回值。这样的机制也保证旧线程能够读取那个返回值——只要它没有调用CloseHandle( )。
        由于被CreateThread( )传回的那个handle 属进程所有,而非线程所有,所以很可能有一个新产生的线程调用CloseHandle( ),取代原来的线程。Microsoft Visual C++ runtime library 中的 _beginthread( )就是这么做的。

3.线程结束代码
BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode
);
参数
hThread 由CreateThread( )传回的线程handle
lpExitCode 指向一个DWORD,用以接受结束代码(exit code)
        如果成功,GetExitCodeThread( )传回TRUE,否则传回FALSE。如果失败,你可以调用GetLastError( )找出原因。如果线程已结束,那么线程的结束代码会被放在lpExitCode 参数中带回来。如果线程尚未结束,lpExitCode 带回来的值是STILL_ACTIVE。

4.结束一个线程
VOID ExitThread(
DWORD dwExitCode
);
参数
dwExitCode 指定此线程之结束代码
返回值
没有。此函数从不返回。
        程序启动后就执行的那个线程称为主线程(primary thread)。主线程有两个特点。第一,它必须负责GUI(Graphic User Interface)程序中的主消息循环。第二,这一线程的结束(不论是因为返回或因为调用了ExitThread( ))会使得程序中的所有线程都被强迫结束,程序也因此而结束。其他线程没有机会做清理工作。

5.The Microsoft Threading Model(微软的多线程模型)
        Win32 说明文件一再强调线程分为GUI 线程和worker 线程两种。GUI 线程负责建造窗口以及处理主消息循环。worker 负责执行纯粹运算工作,如重新计算或重新编页等等,它们会导致主线程的消息队列失去反应。一般而言,GUI线程绝不会去做那些不能够马上完成的工作。
        GUI 线程的定义是:拥有消息队列的线程。任何一个特定窗口的消息总是被产生这一窗口的线程抓到并处理。所有对此窗口的改变也都应该由该线程完成。
        如果worker 线程也产生了一个窗口,那么就会有一个消息队列随之被产生出来并且附着到此线程身上,于是worker 线程摇身一变成了GUI 程。这里的意思是,worker 线程不能够产生窗口、对话框、消息框,或任何其他与UI 有关的东西。
        如果一个worker 线程需要输入或输出错误信息,它应该授权给UI 线程来做,并且将结果通知给worker 线程。

6.多线程程序注意点
1. 各线程的数据要分离开来,避免使用全局变量。
2. 不要在线程之间共享GDI 对象。
3. 确定你知道你的线程状态。不要径自结束程序而不等待它们的结束。
4. 让主线程处理用户界面(UI)。


你可能感兴趣的:(Win 32 多线程程序设计学习笔记之二:线程)