1. 产生一个线程
(winbase.h)
WINBASEAPI
HANDLE
WINAPI
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
注意线程一旦启动(异步asynchronous执行),他就独立于原始调用了。
线程函数的约定:返回值是DWORD,调用约定是WINAPI,有一个LPVOID的参数。
#define WINAPI __stdcall (windef.h)
注意有c、pascal、stdcall
多线程的调用无法预期
执行次序无法保证,Task Switch可能在任何时刻任何地点发生
线程并不是立刻启动
例子:
#define WIN32_LEAN_ANDMEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD WINAPI ThreadFunc(LPVOID)
int main()
{
HANDLE hThread’
DWORD theadID;
int i;
for(i = 0; i<5; i++)
{
hThread = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&threadID);
if(hThread)
{
printf(“Thread launched %d/n”,i);
}
sleep(2000);
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
int i;
for (I = 0; i<10;i++)
{
printf(“%d%d%d%d%d%d%d%d/n”,n,n,n,n,n,n,n,n);
}
return 0;
}
可能会有
33344444444
….
33333
….
发生。
像这样的问题是可以解决的,只要在编译时使用/MT或/MD选项,表示要使用多线程版本的C runtime library。
2. 核心对象kernel object
CreateThread()传回两个值,用于识别一个新的线程:HANDLE和lpThreadId带来的线程ID。线程ID是个全局变量,可以独一无二的表示系统任一进程中的某个线程。AttachThreadInput()和PostThreadMessage()就可需要用到线程ID,这两个函数允许你影响其他人(线程)的消息队列。调试器和进程观察器也需要线程ID。为了防护的缘故,不可以更具一个线程ID而获得其handle。
CreateThread()返回的handle即是一个核心对象(kernel object),他有Kernel32.dll管理。所谓handle,其实是个指针,直线操作系统内存空间中的某样东西,它被操作系统隐藏了细节,不允许被直接获得,为的就是维护系统的安全性。
Win32核心对象有:
进程processes
线程threads
文件files
事件events
信号量semaphores
管道pipes
核心对象可以有一个以上的拥有者,甚至可以跨进程,核心对象保持了一个引用计数(reference count),以记录有多少个handles对应到此对象,对象中也记录了哪一个进程或线程是拥有者。如调用CreateThread()或其他会传回handle的函数,引用计数会加1。 当调用CloseHandle()时,引用计数会减1。一旦引用计数降至0,这一核心对象即自动销毁。
由于引用计数的设计,对象有可能在“产生该对象之进程”结束之后还继续幸存。Win32提供各种机制,让其他进程得以取得一个核心对象的handle,如果某个进程握有某个核心对象的安顿了,而该对象的原创者(进程)已经“作古”了,此核心对象并不会被销毁。
CloseHandle()的重要性
BOOL CloseHandle( HANDLE hObject);
如果一个进程在结束之前没有针对其核心对象调用CloseHandle(),操作系统会自动把那些对象的引用计数减1。但是如果一个进程常常产生“Worker线程”(纯粹做运算的线程)而老是不关闭线程的handle,那么这个进程可能最终有数百甚至数千个开启的“线程核心对象”留给操作系统去清理。这样的资源泄漏(resource leaks)可能会对效率带来负面的影响。
线程对象和线程是不同的,因此可以在不结束线程的情况下关闭其handle。
线程handle是指向“线程核心对象”,而不是指向线程本身。调用CloseHandle()只是说明与此核心对象无任何瓜葛。
“线程核心对象”引用的那个线程也会令核心对象开启。因此线程对象的的默认引用计数是2。当调用CloseHandle()时,引用计数减1, 当线程结束时再减1,只有当两件事情都发生了,不管顺序如何,这个对象才会被真正清除。
3.线程结束 Exit Code
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode);
可以获得线程结束代码。
4.结束线程
VOID ExitThread( DWORD dwExitCode);
强制结束线程,并制定线程之结束代码dwExitCode。
结束主线程
程序启动后就执行的线程成为primary thread。他有两个特点:第一,它必须负责GUI程序中的主消息循环。第二,该线程结束会使得程序中所有线程都被强迫结束,程序也因此而结束。其他线程没有机会做清理工作。
因此需要注意,在main()或WinMain()结束(返回,return)之前,总是先等待所有的线程都结束。(WaitForSingleObject等的应用)
5.错误处理
MTVERIFY宏处理
参考Win32多线程程序设计。
The Microsoft Threading Model微软多线程模型
Win32种,线程分为GUI线程和worker线程两种,GUI线程负责建造窗口以及处理主消息循环。Worker负责执行纯粹运算工作。
一般而言,主线程绝对不会做哪些不能够马上完成的工作。
GUI线程:拥有消息队列的线程。Worker线程不能够产生窗口。
多线程设计成功的关键:
1. 各线程的数据要分离开来,避免使用全局变量,因为容易被改写。
2. 不要再线程之间共享GDI对象。
3. 确定知道线程的状态,不要直接结束程序而不等待他们的结束。
4. 让主线程处理用户界面UI。