《Windows程序设计》读书笔二十 多任务和多线程

本文详细介绍了Windows操作系统中的多任务模型,从DOS时代的TSR程序到Windows的非抢占式和抢占式多任务处理。重点讨论了多线程的概念,包括线程创建、线程同步和事件对象的使用,以及线程本地存储。文中还通过实例演示了如何在Windows中创建和管理多线程,强调了线程同步的重要性,并提出避免竞争条件和死锁的策略。
摘要由CSDN通过智能技术生成

多任务是指操作系统能够并行运行多个程序的能力。

多线程是指一个应用程序在其自身内部也有支持多任务的能力。


20.1 多任务的模型

20.1.1 DOS下的多任务

早期DOS使用终止并驻留(Terminate-and-stay-resident, TSR)程序 如打印机(spooler)


20.1.2  非抢占式的多任务(消息机制)

windows1.0时代,windows运行在实时模式下(real mode),能够在物理内存中移动内存块-多任务的必要条件之一。


窗口环境让多个程序在同一个屏幕下一起运行。切换方便,也能快速移动数据(如复制黏贴)


早期windows,多任务采用非抢占式时间分片方式。建立在windows的消息处理的体系结构上。windows程序在内存中处于休眠状态,直到他收到一条消息。

这些消息是直接或间接由鼠标或键盘输入引发的。在处理完相应的消息,程序会把控制权返回给windows。


16位的windows并不随意根据计时器触发的信号(timer tick)而把控制从一个程序切换到另一个程序。任何任务切换只发生在一个程序已经处理完一条消息,并主动把控制权返回给windows之后。 也成为“协同多任务”(弊端是如果一个应用程序处理一条消息很长时间的话,会占用整个系统)


当然16位windows用抢占式多任务运行dos程序,也让动态库接受硬件计时器中断以执行多媒体功能。


PeekMessage 就算没有待处理的消息,也会把控制权返回给程序(异步)。在应用程序执行长时间任务中参入PeekMessage调用。


20.1.3 PM和串行消息队列

图形表示管理器(Presentation Manager, PM)在上一个用户输入消息被完全处理之前,不会把下一个键盘或鼠标的消息发送给程序。


win32 采用非串行消息队列。如果一个程序在忙于运行一个长任务,可以把输入焦点切换到另一个程序。


20.1.4 多线程的解决方案

在一个多线程环境中,程序把自己分成几个块,叫做“执行线程”,他们并行地运行。

从代码的角度,程序中线程只是简单地由函数来表示,先进入主线程(main)然后进行一个系统调用(CreateThread)给出初始线程函数的名字,从而生成新的执行线程。

操作系统在线程间做抢占式的控制切换,类似进程切换。


在OS/2 PM,每个线程可以创建或不创建消息队列。如果一个PM线程想从当前线程创建窗口,就必须创建消息队列。如果做数据运算或图像输出可以不需要创建消息队列。 没消息队列的线程不能向有消息队列的窗口发送消息,也不能调用任何会造成消息发送的函数。


因此,PM程序员把程序分成一个消息队列线程(用来生存所有的窗口并处理发送给他们的消息)和一个或多个废消息队列线程(用以运行很长的后台任务)。 PM程序员还遵循"1/10"秒规则。 一个消息队列线程处理一条消息不应该花费1/10秒以上的时间。任何超过这个时间的处理都应该在另一个线程中完成。遵循这个规则,一个PM程序顶多只能让系统不反应1/10秒


20.1.5 多线程架构

建议: 主线程创建程序所需要的所有窗口,包括这些窗口的所有窗口过程,并处理这些窗口的消息。任何其他线程应该是简单的后台运算。除了和主线程通讯以外,他们不和用户打交道。


即主线程处理用户的输入(UI),此过程或许会生成其他的二级线程。这些额外的线程处理跟用户不相关的任务。每个线程共享程序的内存,共享静态变量。然而每个线程有自己的堆栈,所以他们的自动变量是唯一的。线程有自己的处理状态,自从保存和恢复。


20.1.6 线程的麻烦

多线程程序的一个常见的缺陷叫“竞争条件”。线程同步Semaphore(信号量)能让代码在某一个特定点上,暂停一个线程的执行,知道另一个线程发信号让他继续,Critical Section(临界区),不能被中断的代码段。


但信号量可能会造成一种缺陷,叫“死锁”


20.1.7 windows的好处

32位windows有一个非串行的消息队列。  win NT和win98 没有消息队列线程和非消息队列线程的区分。每个线程被生存都有自己的消息队列。


包含在一个线程中终止同一个进程中的另一个线程的函数。


支持“线程本地存储”(Thread Local Storage, tls)  静态变量全局局部都被线程共享(位于进程的数据内存空间)。 自变量局部,因为他们占据堆栈空间,每个线程都有自己的堆栈,因为对每个线程来说他们是独立的。


20.1.8 新的! 改进过的! 加了线程的!


20.2 Windows中的多线程


hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc, pParam, dwFlags, &idThread);

security_attributes,指向SECURITY_ATTRIBUTES结构的指针,可以设置为NULL

dwStackSize初始栈大小,0为默认值

ThreadProc 线程函数指针  DWORD WINAPI ThreadProc(PVOID pParam);

pParam 传递给线程函数的参数

dwFlags 通常为0. 如果设置为CREATE_SUSPENDED表示创建后不被立即执行。线程处于挂起状态知道我们调用ResumeThread位置

idThread 指针指向接受新线程的ID值的变量

采用C的运行时库函数开启线程

hThread = _beginthread(ThreadProc, UIStackSize, pParam);

线程函数格式

void _cdecl ThreadProc(void * pParam);


20.2.1 随机矩形程序的多线程版本

RNDRCMT.c

/*
	RNDRCTMT.c -- Displays Random Rectangles
*/

#include <windows.h>
#include <process.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HWND hwnd;
int cxClient, cyClient;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static      TCHAR szAppName[] = TEXT("RndRctMT");
	MSG         msg;
	WNDCLASS    wndClass;       //The window Class  

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.  
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	//Register the Window Class to the Windows System.   
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.  
	hwnd = CreateWindow(szAppName,      //Window class name  
		TEXT("Random Rectangles"),      //Window caption  
		WS_OVERLAPPEDWINDOW,            //Window Style  
		CW_USEDEFAULT,                  //initial x position  
		CW_USEDEFAULT,                  //initial y position  
		CW_USEDEFAULT,                  //initial x size  
		CW_USEDEFAULT,                  //initial y size  
		NULL,                           //parent window handle  
		NULL,                           //window menu handle  
		hInstance,                      //program instance handle  
		NULL);                          //creation parameters  

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd); //This function will generate a WM_PAINT message.  

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

VOID Thread(PVOID pvoid)
{
	HBRUSH	hBrush;
	HDC		hdc;
	int		xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue;

	while (TRUE)
	{
		if (cxClient != 0 || cyClient != 0)
		{
			xLeft	= rand() % cxClient;
			xRight	= rand() % cxClient;
			yTop	= rand() % cyClient;
			yBottom = rand() % cyClient;
			iRed	= rand() & 255;
			iGreen	= rand() & 255;
			iBlue	= rand() & 255;

			hdc = GetDC(hwnd);
			hBrush = CreateSolidBrush(RGB(iRed, iGreen, iBlue));
			SelectObject(hdc, hBrush);

			Rectangle(hdc, min(xLeft, xRight), min(yTop, yBottom),
				max(xLeft, xRight), max(yTop, yBottom));

			ReleaseDC(hwnd, hdc);
			DeleteObject(hBrush);
		}
	}
}

//define the Window Procedure WndProc  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) //get the message  
	{
	case WM_CREATE:
		_beginthread(Thread, 0, NULL);
		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}


运行结果




确认VS中配置程序的运行RunTime Library为多线程版本 这样编译器会增加一个 /MT标志,让编译多线程应用程序OBJ文件中的LIBC.LIB被LIBCMT.LIB取代

例如C标准库strtok会在静态内存中存储一个指针。在多线程程序中strtok会有自己的静态指针(thread local storage)

另外_beginthread 必须开启/MT编译选项才会被定义。



20.2.2 编程竞赛问题

创建一个多线程仿真程序,第一个窗口显示递增的数列,第二个窗口显示一个递增的质数数列,第三个窗口显示一个递增的斐波那契数列,第四个窗口产生一个大小不一的圆。

按ESC退出整个程序。


MULTI1 实现

/*
		MULTI1.c -- Multitasking Demo
*/

#include <windows.h>
#include <math.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int cyChar;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static      TCHAR szAppName[] = TEXT("Multi1");
	HWND		hwnd;
	MSG         msg;
	WNDCLASS    wndClass;       //The window Class  

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.  
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	//Register the Window Class to the Windows System.   
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.  
	hwnd = CreateWindow(szAppName,      //Window class name  
		TEXT("Multitasking Demo"),      //Window caption  
		WS_OVERLAPPEDWINDOW,            //Window Style  
		CW_USEDEFAULT,                  //initial x position  
		CW_USEDEFAULT,                  //initial y position  
		CW_USEDEFAULT,                  //initial x size  
		CW_USEDEFAULT,                  //initial y size  
		NULL,                           //parent window handle  
		NULL,                           //window menu handle  
		hInstance,                      //program instance handle  
		NULL);                          //creation parameters  

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd); //This function will generate a WM_PAINT message.  

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

int CheckBottom(HWND hwnd, int cyClient, int iLine)
{
	if (iLine * cyChar + cyChar > cyClient)
	{
		InvalidateRect(hwnd, NULL, TRUE);
		UpdateWindow(hwnd);
		iLine = 0;
	}
	return iLine;
}

// window 1: Display increasing sequence of numbers
LRESULT APIENTRY WndProc1(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	iNum, iLine, cyClient;
	HDC			hdc;
	TCHAR		szBuffer[16];

	switch (message)
	{
	case WM_SIZE:
		cyClient = HIWORD(lParam);
		return 0;
	case WM_TIMER:
		if (iNum < 0)
			iNum = 0;

		iLine = CheckBottom(hwnd, cyClient, iLine);
		hdc = GetDC(hwnd);

		TextOut(hdc, 0, iLine * cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++));
		ReleaseDC(hwnd, hdc);
		iLine++;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 2: Display increasing sequence of prime numbers
LRESULT APIENTRY WndProc2(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	iNum = 1, iLine, cyClient;
	HDC			hdc;
	int			i, iSqrt;
	TCHAR		szBuffer[16];

	switch (message)
	{
	case WM_SIZE:
		cyClient = HIWORD(lParam);
		return 0;

	case WM_TIMER:
		do // generate the prime number sequence.
		{
			if (++iNum < 0)
				iNum = 0;

			iSqrt = (int)sqrt(iNum);

			for (i = 2; i <= iSqrt; i++)
				if (iNum % i == 0)
					break;
		} while (i <= iSqrt);

		iLine = CheckBottom(hwnd, cyClient, iLine);
		hdc = GetDC(hwnd);

		TextOut(hdc, 0, iLine * cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum));
		ReleaseDC(hwnd, hdc);
		iLine++;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 3: Display increasing sequence of Fibonacci numbers
LRESULT APIENTRY WndProc3(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	iNum = 0, iNext = 1, iLine, cyClient;
	HDC			hdc;
	int			iTemp;
	TCHAR		szBuffer[16];

	switch (message)
	{
	case WM_SIZE:
		cyClient = HIWORD(lParam);
		return 0;

	case WM_TIMER:
		if (iNum < 0)
		{
			iNum = 0;
			iNext = 1;
		}

		iLine = CheckBottom(hwnd, cyClient, iLine);
		hdc = GetDC(hwnd);

		TextOut(hdc, 0, iLine * cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum));
		ReleaseDC(hwnd, hdc);
		iTemp = iNum;
		iNum = iNext;
		iNext += iTemp;
		iLine++;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 4: Display circles of random radii
LRESULT APIENTRY WndProc4(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	cxClient, cyClient;
	HDC			hdc;
	int			iDiameter;
	

	switch (message)
	{
	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);
		return 0;

	case WM_TIMER:
		InvalidateRect(hwnd, NULL, TRUE);
		UpdateWindow(hwnd);

		iDiameter = rand() % (max(1, min(cxClient, cyClient)));
		hdc = GetDC(hwnd);

		Ellipse(hdc, (cxClient - iDiameter) / 2,
			(cyClient - iDiameter) / 2,
			(cxClient + iDiameter) / 2,
			(cyClient + iDiameter) / 2);
		ReleaseDC(hwnd, hdc);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

//define the Window Procedure WndProc  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND		hwndChild[4];
	static TCHAR *	szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") };

	static WNDPROC	ChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 };
	HINSTANCE		hInstance;
	int				i, cxClient, cyClient;
	WNDCLASS		wndclass;

	switch (message) //get the message  
	{
	case WM_CREATE:
		hInstance = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE);

		wndclass.style = CS_HREDRAW | CS_VREDRAW;
		wndclass.cbClsExtra = 0;
		wndclass.cbWndExtra = 0;
		wndclass.hInstance = hInstance;
		wndclass.hIcon = NULL;
		wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wndclass.lpszMenuName = NULL;
		
		for (i = 0; i < 4; i++)
		{
			wndclass.lpfnWndProc = ChildProc[i];
			wndclass.lpszClassName = szChildClass[i];

			RegisterClass(&wndclass);
			hwndChild[i] = CreateWindow(szChildClass[i], NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,
				0, 0, 0, 0,
				hwnd, (HMENU)i, hInstance, NULL);
		}

		cyChar = HIWORD(GetDialogBaseUnits());
		SetTimer(hwnd, 1, 10, NULL);
		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);

		for (i = 0; i < 4; i++)
			MoveWindow(hwndChild[i], (i % 2) * cxClient / 2,
			(i > 1) * cyClient / 2,
			cxClient / 2, cyClient / 2, TRUE);
		return 0;

	case WM_TIMER:
		for (i = 0; i < 4; i++)
			SendMessage(hwndChild[i], WM_TIMER, wParam, lParam);
		return 0;

	case WM_DESTROY:
		KillTimer(hwnd, 1);
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}
运行结果

WM_PAINT消息windows程序需要维护足够的信息来重新绘制一个窗口的内容。如果窗口更新足够快没必要维护以前的内容,就使用WM_TIMER来重新绘制窗口内容

使用一个计时器来完成多线程的任务。



20.2.3 多线程的解决方案。

MULTI2.c

/*
		MULTI2.c -- Multitasking Demo
*/

#include <windows.h>
#include <math.h>
#include <process.h>

typedef struct
{
	HWND	hwnd;
	int		cxClient;
	int		cyClient;
	int		cyChar;
	BOOL	bKill;
}
PARAMS, *PPARAMS;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static      TCHAR szAppName[] = TEXT("Multi2");
	HWND		hwnd;
	MSG         msg;
	WNDCLASS    wndClass;       //The window Class  

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.  
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	//Register the Window Class to the Windows System.   
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.  
	hwnd = CreateWindow(szAppName,      //Window class name  
		TEXT("Multitasking Demo"),      //Window caption  
		WS_OVERLAPPEDWINDOW,            //Window Style  
		CW_USEDEFAULT,                  //initial x position  
		CW_USEDEFAULT,                  //initial y position  
		CW_USEDEFAULT,                  //initial x size  
		CW_USEDEFAULT,                  //initial y size  
		NULL,                           //parent window handle  
		NULL,                           //window menu handle  
		hInstance,                      //program instance handle  
		NULL);                          //creation parameters  

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd); //This function will generate a WM_PAINT message.  

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

int CheckBottom(HWND hwnd, int cyClient, int cyChar, int iLine)
{
	if (iLine * cyChar + cyChar > cyClient)
	{
		InvalidateRect(hwnd, NULL, TRUE);
		UpdateWindow(hwnd);
		iLine = 0;
	}
	return iLine;
}



// window 1: Display increasing sequence of numbers
void Thread1(PVOID pvoid)
{
	int			iNum = 0, iLine = 0;
	HDC			hdc;
	PPARAMS		pparams;
	TCHAR		szBuffer[16];

	pparams = (PPARAMS)pvoid;

	while (!pparams->bKill)
	{
		if (iNum < 0)
			iNum = 0;

		iLine = CheckBottom(pparams->hwnd, pparams->cyClient, pparams->cyChar, iLine);
		hdc = GetDC(pparams->hwnd);

		TextOut(hdc, 0, iLine * pparams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++));
		ReleaseDC(pparams->hwnd, hdc);
		iLine++;
	}

	_endthread();
}

LRESULT APIENTRY WndProc1(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static PARAMS params;

	switch (message)
	{
	case WM_CREATE:
		params.hwnd = hwnd;
		params.cyChar = HIWORD(GetDialogBaseUnits());
		_beginthread(Thread1, 0, ¶ms);
		return 0;

	case WM_SIZE:
		params.cyClient = HIWORD(lParam);
		return 0;
	case WM_DESTROY:
		params.bKill = TRUE;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 2: Display increasing sequence of prime numbers
void Thread2(PVOID pvoid)
{
	int	iNum = 0, iLine = 0, i, iSqrt;
	HDC			hdc;
	PPARAMS		pparams;
	TCHAR		szBuffer[16];

	pparams = (PPARAMS)pvoid;

	while (!pparams->bKill)
	{
		do // generate the prime number sequence.
		{
			if (++iNum < 0)
				iNum = 0;

			iSqrt = (int)sqrt(iNum);

			for (i = 2; i <= iSqrt; i++)
				if (iNum % i == 0)
					break;
		} while (i <= iSqrt);

		iLine = CheckBottom(pparams->hwnd, pparams->cyClient, pparams->cyChar, iLine);
		hdc = GetDC(pparams->hwnd);

		TextOut(hdc, 0, iLine * pparams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++));
		ReleaseDC(pparams->hwnd, hdc);
		iLine++;
	}

	_endthread();
}


LRESULT APIENTRY WndProc2(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static PARAMS params;

	switch (message)
	{
	case WM_CREATE:
		params.hwnd = hwnd;
		params.cyChar = HIWORD(GetDialogBaseUnits());
		_beginthread(Thread2, 0, ¶ms);
		return 0;

	case WM_SIZE:
		params.cyClient = HIWORD(lParam);
		return 0;
	case WM_DESTROY:
		params.bKill = TRUE;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 3: Display increasing sequence of Fibonacci numbers
void Thread3(PVOID pvoid)
{
	int			iNum = 0, iNext = 1, iLine = 0, iTemp;
	HDC			hdc;
	PPARAMS		pparams;
	TCHAR		szBuffer[16];

	pparams = (PPARAMS)pvoid;

	while (!pparams->bKill)
	{
		if (iNum < 0)
		{
			iNum = 0;
			iNext = 1;
		}

		iLine = CheckBottom(pparams->hwnd, pparams->cyClient, pparams->cyChar, iLine);
		hdc = GetDC(pparams->hwnd);

		TextOut(hdc, 0, iLine * pparams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum));
		ReleaseDC(pparams->hwnd, hdc);
		iTemp = iNum;
		iNum = iNext;
		iNext += iTemp;
		iLine++;
	}

	_endthread();
}

LRESULT APIENTRY WndProc3(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static PARAMS params;

	switch (message)
	{
	case WM_CREATE:
		params.hwnd = hwnd;
		params.cyChar = HIWORD(GetDialogBaseUnits());
		_beginthread(Thread3, 0, ¶ms);
		return 0;

	case WM_SIZE:
		params.cyClient = HIWORD(lParam);
		return 0;
	case WM_DESTROY:
		params.bKill = TRUE;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

// window 4: Display circles of random radii
void Thread4(PVOID pvoid)
{
	int			iDiameter;
	HDC			hdc;
	PPARAMS		pparams;

	pparams = (PPARAMS)pvoid;

	while (!pparams->bKill)
	{
		InvalidateRect(pparams->hwnd, NULL, TRUE);
		UpdateWindow(pparams->hwnd);

		iDiameter = rand() % (max(1, min(pparams->cxClient, pparams->cyClient)));
		hdc = GetDC(pparams->hwnd);

		Ellipse(hdc, (pparams->cxClient - iDiameter) / 2,
			(pparams->cyClient - iDiameter) / 2,
			(pparams->cxClient + iDiameter) / 2,
			(pparams->cyClient + iDiameter) / 2);
		ReleaseDC(pparams->hwnd, hdc);
	}

	_endthread();
}

LRESULT APIENTRY WndProc4(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static PARAMS params;

	switch (message)
	{
	case WM_CREATE:
		params.hwnd = hwnd;
		params.cyChar = HIWORD(GetDialogBaseUnits());
		_beginthread(Thread4, 0, ¶ms);
		return 0;

	case WM_SIZE:
		params.cyClient = HIWORD(lParam);
		params.cxClient = LOWORD(lParam);
		return 0;
	case WM_DESTROY:
		params.bKill = TRUE;
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

//define the Window Procedure WndProc  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND		hwndChild[4];
	static TCHAR *	szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") };

	static WNDPROC	ChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 };
	HINSTANCE		hInstance;
	int				i, cxClient, cyClient;
	WNDCLASS		wndclass;

	switch (message) //get the message  
	{
	case WM_CREATE:
		hInstance = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE);

		wndclass.style = CS_HREDRAW | CS_VREDRAW;
		wndclass.cbClsExtra = 0;
		wndclass.cbWndExtra = 0;
		wndclass.hInstance = hInstance;
		wndclass.hIcon = NULL;
		wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wndclass.lpszMenuName = NULL;
		
		for (i = 0; i < 4; i++)
		{
			wndclass.lpfnWndProc = ChildProc[i];
			wndclass.lpszClassName = szChildClass[i];

			RegisterClass(&wndclass);
			hwndChild[i] = CreateWindow(szChildClass[i], NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,
				0, 0, 0, 0,
				hwnd, (HMENU)i, hInstance, NULL);
		}

		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);

		for (i = 0; i < 4; i++)
			MoveWindow(hwndChild[i], (i % 2) * cxClient / 2,
			(i > 1) * cyClient / 2,
			cxClient / 2, cyClient / 2, TRUE);
		return 0;

	case WM_CHAR:
		if (wParam == '\x1B')
			DestroyWindow(hwnd);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

执行结果



调用_endthread可以结束当前线程。当然退出线程函数会自动结束当前线程。

多线程版本在移动窗口时候仍然更新绘图,而使用定时器的版本不会更新。


20.2.4 还有问题么?

线程同步问题。

MULTI2 如果收到WM_ERASEBKGND或WM_PAINT,而二级线程正在输出绘图。  使用临界区可以防止两个线程同时在一个窗口上绘制。

win98以上系统会串行化绘图函数使用(即一个线程如果正在绘图,另一个线程是不可能在这个窗口上同时绘图)

当然也有一类图形函数是非串行化的。GDI对象等



20.2.5 休眠的好处

Sleep函数 ,主线程中不应该使用sleep函数,这会减慢消息处理。


20.3 线程的同步

临界区

操作系统只能在机器指令之间从一个线程切换到另一个线程。如果只是单个整数被共享,有通常对该变量的改变是一个指令完成的,所以一般不会有问题。

如果堆多个变量活一个数据结构的共享,为了保持一致,更新过程中必须中断某些线程。否则可能会看到不一致的结果导致程序崩溃。


CRITICAL_SECTION cs;

InitializeCriticalSection(&cs); //初始化


EnterCriticalSection(&cs); //进入临界区  ,只有一个线程可以拥有他,如果有另一个线程试图进入会被挂起。知道当前线程离开临界区


LeaveCriticalSection(&cs); //离开临界区, 其他被挂起的线程可以继续执行。

当程序不再需要临界区对象时,可以删除他

DeleteCriticalSection(&cs);


主线程中使用临界区要非常小心,如果一个二级线程在临界区中停留过久,他会阻碍主线程很长时间。

临界区的一个局限是他只能在同一个进程中使用。如果你要协调两个共享某资源的进程(比如共享内存),就不能使用临界区,而应该使用“互斥对象”(mutex)


20.4 触发事件

多线程通常用于长时间运行的任务(大于1/10秒)


20.4.1 BIGJOB1程序


各种浮点运算 未使用线程同步


/*
		BIGJOB.c -- Multitasking Demo
*/

#include <windows.h>
#include <math.h>
#include <process.h>


#define REP				1000000

#define STATUS_READY	0
#define STATUS_WORKING	1
#define STATUS_DONE		2

#define WM_CALC_DONE	(WM_USER + 0)
#define WM_CALC_ABORTED	(WM_USER + 1)

typedef struct
{
	HWND hwnd;
	BOOL bContinue;
}
PARAMS, *PPARAMS;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static      TCHAR szAppName[] = TEXT("BigJob1");
	HWND		hwnd;
	MSG         msg;
	WNDCLASS    wndClass;       //The window Class  

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.  
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	//Register the Window Class to the Windows System.   
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.  
	hwnd = CreateWindow(szAppName,      //Window class name  
		TEXT("Multitasking Demo"),      //Window caption  
		WS_OVERLAPPEDWINDOW,            //Window Style  
		CW_USEDEFAULT,                  //initial x position  
		CW_USEDEFAULT,                  //initial y position  
		CW_USEDEFAULT,                  //initial x size  
		CW_USEDEFAULT,                  //initial y size  
		NULL,                           //parent window handle  
		NULL,                           //window menu handle  
		hInstance,                      //program instance handle  
		NULL);                          //creation parameters  

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd); //This function will generate a WM_PAINT message.  

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}



// window 1: Display increasing sequence of numbers
void Thread(PVOID pvoid)
{
	double		A = 1.0;
	INT			i;
	LONG		lTime;
	volatile	PPARAMS	pparams;

	pparams = (PPARAMS)pvoid;

	lTime = GetCurrentTime();

	for (i = 0; i < REP && pparams->bContinue; i++)
		A = tan(atan(exp(log(sqrt(A * A))))) + 1.0;

	if (i == REP)
	{
		lTime = GetCurrentTime() - lTime;
		SendMessage(pparams->hwnd, WM_CALC_DONE, 0, lTime);
	}
	else
		SendMessage(pparams->hwnd, WM_CALC_ABORTED, 0, 0);

	_endthread();
}



//define the Window Procedure WndProc  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static INT		iStatus;
	static LONG		lTime;
	static PARAMS	params;
	static TCHAR *	szMessage[] = { TEXT("Ready (left mouse button begins)"),
		TEXT("Working (right mouse button ends)") ,
		TEXT("%d repetitions in %ld msec") };

	HDC				hdc;
	PAINTSTRUCT		ps;
	RECT			rect;
	TCHAR			szBuffer[64];

	switch (message) //get the message  
	{
	case WM_LBUTTONDOWN:
		if (iStatus == STATUS_WORKING)
		{
			MessageBeep(0);
			return 0;
		}

		iStatus = STATUS_WORKING;

		params.hwnd = hwnd;
		params.bContinue = TRUE;

		_beginthread(Thread, 0, ¶ms);

		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_RBUTTONDOWN:
		params.bContinue = FALSE;
		return 0;

	case WM_CALC_DONE:
		lTime = lParam;
		iStatus = STATUS_DONE;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_CALC_ABORTED:
		iStatus = STATUS_READY;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		wsprintf(szBuffer, szMessage[iStatus], REP, lTime);
		DrawText(hdc, szBuffer, -1, &rect,
			DT_SINGLELINE | DT_CENTER | DT_VCENTER);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_CHAR:
		if (wParam == '\x1B')
			DestroyWindow(hwnd);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}


运行结果



killThread只有在正常退出无法时候才使用。防止资源无法释放。例如采用RAII机制,或者智能指针。

此代码未采用临界区进行同步。


20.4.2 事件对象

一个事件对象有两种状态,已触发或未触发。以下代码创建一个事件对象。

hEvent = CreateEvent(&sa, fManual, fInitial, pszName);

sa    指向SECURITY_ATTRIBUTES结构的指针

pszName 事件对象名,只有在事件对象在进程间共享时才有意义。

fInitial 初始触发状态。 TRUE表示事件对象已经触发,FALSE表示未触发。

fManual 此事件触发以后是否自动设置为未触发。


为了触发一个事件对象可以使用SetEvent(hEvent);


解除一个事件对象的触发状态, ResetEvent(hEvent);

一个程序调用下面的函数来等待一个事件对象被触发

WaitForSingleObject(hEvent, dwTimeOut);

如果事件已经触发函数会立即返回,否则函数会等待dwTimeOut毫秒  (如果设置为INFINITE 函数会一只等待到事件触发才返回)

如果在创建事件对象的时候 fManual 设置为FALSE, 在函数WaitForSingleObject返回以后,事件对象的状态会被自动设置为未触发。 这样就不要调用ResetEvent函数去重置事件对象。


参考 BIGJOB2

/*
		BIGJOB2.c -- Multitasking Demo
*/

#include <windows.h>
#include <math.h>
#include <process.h>


#define REP				1000000

#define STATUS_READY	0
#define STATUS_WORKING	1
#define STATUS_DONE		2

#define WM_CALC_DONE	(WM_USER + 0)
#define WM_CALC_ABORTED	(WM_USER + 1)

typedef struct
{
	HWND hwnd;
	HANDLE hEvent;
	BOOL bContinue;
}
PARAMS, *PPARAMS;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static      TCHAR szAppName[] = TEXT("BigJob2");
	HWND		hwnd;
	MSG         msg;
	WNDCLASS    wndClass;       //The window Class  

	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;// assign the window procedure to windows class.  
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	//Register the Window Class to the Windows System.   
	if (!RegisterClass(&wndClass))
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.  
	hwnd = CreateWindow(szAppName,      //Window class name  
		TEXT("Multitasking Demo"),      //Window caption  
		WS_OVERLAPPEDWINDOW,            //Window Style  
		CW_USEDEFAULT,                  //initial x position  
		CW_USEDEFAULT,                  //initial y position  
		CW_USEDEFAULT,                  //initial x size  
		CW_USEDEFAULT,                  //initial y size  
		NULL,                           //parent window handle  
		NULL,                           //window menu handle  
		hInstance,                      //program instance handle  
		NULL);                          //creation parameters  

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd); //This function will generate a WM_PAINT message.  

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}



// window 1: Display increasing sequence of numbers
void Thread(PVOID pvoid)
{
	double		A = 1.0;
	INT			i;
	LONG		lTime;
	volatile	PPARAMS	pparams;

	pparams = (PPARAMS)pvoid;


	while (TRUE)
	{
		WaitForSingleObject(pparams->hEvent, INFINITE);

		lTime = GetCurrentTime();

		for (i = 0; i < REP && pparams->bContinue; i++)
			A = tan(atan(exp(log(sqrt(A * A))))) + 1.0;

		if (i == REP)
		{
			lTime = GetCurrentTime() - lTime;
			SendMessage(pparams->hwnd, WM_CALC_DONE, 0, lTime);
		}
		else
			SendMessage(pparams->hwnd, WM_CALC_ABORTED, 0, 0);
	}
}



//define the Window Procedure WndProc  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HANDLE	hEvent;
	static INT		iStatus;
	static LONG		lTime;
	static PARAMS	params;
	static TCHAR *	szMessage[] = { TEXT("Ready (left mouse button begins)"),
		TEXT("Working (right mouse button ends)") ,
		TEXT("%d repetitions in %ld msec") };

	HDC				hdc;
	PAINTSTRUCT		ps;
	RECT			rect;
	TCHAR			szBuffer[64];

	switch (message) //get the message  
	{
	case WM_CREATE:
		 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

		 params.hwnd = hwnd;
		 params.hEvent = hEvent;
		 params.bContinue = FALSE;
		 _beginthread(Thread, 0, ¶ms);
		 return 0;

	case WM_LBUTTONDOWN:
		if (iStatus == STATUS_WORKING)
		{
			MessageBeep(0);
			return 0;
		}

		iStatus = STATUS_WORKING;

		params.hwnd = hwnd;
		params.bContinue = TRUE;

		SetEvent(hEvent);

		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_RBUTTONDOWN:
		params.bContinue = FALSE;
		return 0;

	case WM_CALC_DONE:
		lTime = lParam;
		iStatus = STATUS_DONE;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_CALC_ABORTED:
		iStatus = STATUS_READY;
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);
		wsprintf(szBuffer, szMessage[iStatus], REP, lTime);
		DrawText(hdc, szBuffer, -1, &rect,
			DT_SINGLELINE | DT_CENTER | DT_VCENTER);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_CHAR:
		if (wParam == '\x1B')
			DestroyWindow(hwnd);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return  DefWindowProc(hwnd, message, wParam, lParam);
}

运行结果同BIGJOB1



20.5 线程的本地存储


全局变量和所有分配的内存一样被程序的所有线程共享。 函数中的局部静态变量也被所有线程共享。 函数中的局部自动变量分配在栈,是线程私有。(在函数结束后被释放)

有时候需要一种线程私有又一直存储的单元。


首先定义一个包含所有静态数据的私有结构比如

typedef struct

{

int a;

int b;

}

DATA, * PDATA;


主线程调用API函数 TlsAlloc获取一个索引值

dwTlsIndex = TlsAlloc();

然后调用

TlsSetValue(dwTlsIndex, GlobalAlloc(GPTR, sizeof(DATA));


获取值采用

PDATA pdata = (PDATA) TlsGetValue(dwTlsIndex);


释放采用内存

GlobalFree(TlsGetValue(dwTlsIndex));

当所有使用本地存储结构的线程都结束以后,在主线程释放其索引

TlsFree(dwTlsIndex);


实现方式: 参加P956页

1. TlsAlloc 会分配一块内存(初始长度0字节)并返回一个索引值,用作这块内存的指针

2. 当调用TlsSetValue 这块内存被重新分配以增加8字节。这8个字节保存调用改函数的线程ID   (GetCurrentThreadId获得) 以及传递给TlsSetValue的函数指针。

3. TlsGetValue 使用线程ID搜索索引表并返回相应的指针。

4. TlsFree最终会释放TlsAlloc分配的内存。


微软扩展C语言


__declspec (thread) int iGlobal = 1;

__declspec (thread) static int iLocal = 2;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值