Win32 多线程程序设计(二)线程

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;
}

发表回复