2.1 产生一个线程:CreateThread
CreateThread开启一个新线程,该线程调用ThreadFunc,原来的主线程继续前进, ThreadFunc被异步执行;
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes:描述施行于这一新线程的security属性。NULL表示使用缺省值。
dwStatckSize : 新线程拥有自几的堆栈,0表示使用缺省大小。
lpStartAddress: 新线程将开始的起始地址,这是一个函数指针(在C语言中函数名成即代表函数指针)。
lpParameter:此值将被传送到上述所指定的新线程函数去,作为参数(唯一的参数)。
dwCreationFlags:允许你产生一个暂时挂起的线程,默认情况是“立即开始执行”。
lpThreadId :新线程的ID会被传回到这里。
返回值:
如果CreateThread()成功,传回一个handle,代表新线程,否则传回一个FALSE。如果失败,你可以调用GetLastError()获知原因。
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThrd;
DWORD threadId;
int i;
for (i=0; i<2; i++)
{
hThrd = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&threadId );
if (hThrd)
{
printf("Thread launched %d\n", i);
CloseHandle(hThrd);
}
}
// Wait for the threads to complete.
// We'll see a better way of doing this later.
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;
}
多个线程时,线程次序无法保证,所以输出结果会乱序
2.2 核心对象
2.2.1 获取核心对象
CreateThread()返回的HANDLE称之为核心对象,即所谓的GDI对象。 CreateThread()还返回一个线程ID,用于影响其他线程消息队列,调试器了进程观察器也需要线程ID,也不能根据一个线程ID获得其HANDLE;
2.2.2 释放核心对象CloseHandle
如果总是不关闭相应的handle,那么这个进程可能会有数百、千个“线程核心对象”留给操作系统去处理,这样的资源泄漏会对效率带来负担;
有些对象是被进程拥有,而非线程拥有,这种对象如果不手动清理,则在进程结束之前不能够清理它们;
线程handle指向“线程核心对象”,而非线程本身,调用closehandle(handle),表示你希望自己和此核心对象不再有任何瓜葛。
Handle引用到的线程也会令核心对象开启,因此线程的默认引用计数是2,当调用closehandle时,引用计数减1,当线程结束时,引用计数再将1,只有当两件使均发生时,对象才会被真正的清除。这就是为什么可以在不结束线程的情况下关闭其handle
BOOL CloseHandle(HANDLE hObject);
参数:hObject :代表一个已打开对象handle。
返回值
TRUE:执行成功;
FALSE:执行失败,可以调用GetLastError()获知失败原因。
2.2.3 结束代码GetExitCodeThread
如何判断一个线程是否真正结束,而非利用SLEEP等待;
BOOL GetExitCodeThread (
HANDLE hThread, // in,线程handle,也就是CreateThread()的返回值
LPDWORD lpExitCode //out,存储线程结束代码,也就是线程的返回值
);
说明: 此函数调用成功返回TRUE,失败返回FALSE,只表示这个函数是否调用成功而己,不能根据返回值来判断一个线程是否结束,而要根据 lpExitCode的值来确定,lpExitCode 值STILL_ACTIVE 表示线程正在运行.若线程己经结束,则lpExitCode中存储指定线程的返回值.
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThrd1;
HANDLE hThrd2;
DWORD exitCode1 = 0;
DWORD exitCode2 = 0;
DWORD threadId;
hThrd1 = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)1,
0,
&threadId );
if (hThrd1)
printf("Thread 1 launched\n");
hThrd2 = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)2,
0,
&threadId );
if (hThrd2)
printf("Thread 2 launched\n");
for (;;)
{
printf("Press any key to exit..\n");
getch();
GetExitCodeThread(hThrd1, &exitCode1);
GetExitCodeThread(hThrd2, &exitCode2);
if ( exitCode1 == STILL_ACTIVE )
puts("Thread 1 is still running!");
if ( exitCode2 == STILL_ACTIVE )
puts("Thread 2 is still running!");
if ( exitCode1 != STILL_ACTIVE && exitCode2 != STILL_ACTIVE )
break;
}
CloseHandle(hThrd1);
CloseHandle(hThrd2);
printf("Thread 1 returned %d\n", exitCode1);
printf("Thread 2 returned %d\n", exitCode2);
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
Sleep((DWORD)n*1000*2);
return (DWORD)n * 10;
}
2.2.4 结束线程(ExitThread)
不用依靠线程函数的结束而结束线程,可以更强制的手法结束一个线程,即ExitThread(),位于此函数之后的代码都不会被执行。
VOID ExitThread(
DWORD dwExitCode
);
dwExitCode: 指定该线程的结束代码
返回值: 无返回值。
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD WINAPI ThreadFunc(LPVOID);
void AnotherFunc(void);
int main()
{
HANDLE hThrd;
DWORD exitCode = 0;
DWORD threadId;
hThrd = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)1,
0,
&threadId );
if (hThrd)
printf("Thread launched\n");
for(;;)
{
BOOL rc;
rc = GetExitCodeThread(hThrd, &exitCode);
if (rc && exitCode != STILL_ACTIVE )
break;
}
CloseHandle(hThrd);
printf("Thread returned %d\n", exitCode);
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
printf("Thread running\n");
AnotherFunc();
return 0;
}
void AnotherFunc()
{
printf("About to exit thread\n");
ExitThread(4);
printf("This will never print\n");
}
2.3 结束主线程
程序启动后就执行的那个线程成为主线程,主线程的两个特点:
- 必须负责GUI(Graphic User Interface)程序中的主消息循环;
- 主线程的结束会使得程序中的所有线程都被迫结束,程序也因此结束,从而导致其他线程没哟机会做清理工作;
原则:主线程结束前,需要先等待其他所有线程结束,否则会导致资源无法正常释放。
2.4 多线程(GUI线程、work线程)
GUI线程:拥有消息队列的线程,负责建造窗口以及处理主消息循环;
- 窗口的消息由创建窗口的线程捕获并处理;
- 对窗口的改变也须由创建窗口的线程完成;
Work线程:负责执行纯粹运算工作;
- Work线程不能产生窗口、对话框、消息框或任何其他与UI有关的东西;
- 如果一个work线程需要输入或输出错误信息,应授权给UI线程来做,并将结果通知work线程;
2.5 多线程程序设计关键
- 各线程的数据要分离开来,避免使用全局变量;
- 不要在线程之间共享GDI对象;
- 明确你知道你的线程的状态,不要径自结束而不等待他们的结束;
- 让主线程处理用户界面UI;
2.6 捕获错误并协助找出原因:MTVERIFY宏
#pragma comment( lib, "USER32" )
#include <crtdbg.h>
#define MTASSERT(a) _ASSERTE(a)
#define MTVERIFY(a) if (!(a)) PrintError(#a,__FILE__,__LINE__,GetLastError())
__inline void PrintError(LPSTR linedesc, LPSTR filename, int lineno, DWORD errnum)
{
LPSTR lpBuffer;
//char *lpBuffer;
char errbuf[256];
#ifdef _WINDOWS
char modulename[MAX_PATH];
#else // _WINDOWS
DWORD numread;
#endif // _WINDOWS
FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errnum,
LANG_NEUTRAL,
(LPSTR)&lpBuffer,
0,
NULL );
sprintf(errbuf, "\nThe following call failed at line %d in %s:\n\n"
" %s\n\nReason: %s\n", lineno, filename, linedesc, lpBuffer);
#ifndef _WINDOWS
WriteFile(GetStdHandle(STD_ERROR_HANDLE), errbuf, strlen(errbuf), &numread, FALSE );
Sleep(3000);
#else
GetModuleFileName(NULL, modulename, MAX_PATH);
MessageBox(NULL, errbuf, modulename, MB_ICONWARNING|MB_OK|MB_TASKMODAL|MB_SETFOREGROUND);
#endif
exit(EXIT_FAILURE);
}
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "MtVerify.h"
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThrd;
DWORD exitCode = 0;
DWORD threadId;
MTVERIFY( hThrd = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)1,
0,
&threadId )
);
if (hThrd)
printf("Thread launched\n");
MTVERIFY( CloseHandle(hThrd) );
for(;;)
{
BOOL rc;
MTVERIFY( rc = GetExitCodeThread(hThrd, &exitCode) );
if (rc || exitCode != STILL_ACTIVE )
break;
}
printf("Thread returned %d\n", exitCode);
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
printf("Thread running\n");
return 0;
}