多任务是指操作系统能够并行运行多个程序的能力。
多线程是指一个应用程序在其自身内部也有支持多任务的能力。
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;