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两种)
BOOL CloseHandle(
HANDLE hObject
);
参数说明:
线程handle 时,你只不过是表示,你希望自己和此核心对象不再有任何瓜葛。CloseHandle( )唯一做的事情就是把引用计数减1。如果该值变成0,对象会自动被操作系统摧毁。
“线程核心对象”引用到的那个线程也会令核心对象开启。因此,线程对象的默认引用计数是2。当你调用CloseHandle( )时,引用计数下降1,当线程结束时,引用计数再降1。只有当两件事情都发生了(不管顺序如何)的时候,这个对象才会被真正清除。
“引用计数”机制保证新的线程有个地方可以写下其返回值。这样的机制也保证旧线程能够读取那个返回值——只要它没有调用CloseHandle( )。
由于被CreateThread( )传回的那个handle 属进程所有,而非线程所有,所以很可能有一个新产生的线程调用CloseHandle( ),取代原来的线程。Microsoft Visual C++ runtime library 中的 _beginthread( )就是这么做的。
BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode
);
参数hThread 由CreateThread( )传回的线程handle
lpExitCode 指向一个DWORD,用以接受结束代码(exit code)
VOID ExitThread(
DWORD dwExitCode
);
参数dwExitCode 指定此线程之结束代码
返回值
没有。此函数从不返回。
5.The Microsoft Threading Model(微软的多线程模型)
Win32 说明文件一再强调线程分为GUI 线程和worker 线程两种。GUI 线程负责建造窗口以及处理主消息循环。worker 负责执行纯粹运算工作,如重新计算或重新编页等等,它们会导致主线程的消息队列失去反应。一般而言,GUI线程绝不会去做那些不能够马上完成的工作。
GUI 线程的定义是:拥有消息队列的线程。任何一个特定窗口的消息总是被产生这一窗口的线程抓到并处理。所有对此窗口的改变也都应该由该线程完成。
如果worker 线程也产生了一个窗口,那么就会有一个消息队列随之被产生出来并且附着到此线程身上,于是worker 线程摇身一变成了GUI 程。这里的意思是,worker 线程不能够产生窗口、对话框、消息框,或任何其他与UI 有关的东西。
如果一个worker 线程需要输入或输出错误信息,它应该授权给UI 线程来做,并且将结果通知给worker 线程。
2. 不要在线程之间共享GDI 对象。
3. 确定你知道你的线程状态。不要径自结束程序而不等待它们的结束。
4. 让主线程处理用户界面(UI)。