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

来源:互联网 发布:祖龙娱乐 知乎 编辑:程序博客网 时间:2024/06/05 19:53

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

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


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){HBRUSHhBrush;HDChdc;intxLeft, 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");HWNDhwnd;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 numbersLRESULT APIENTRY WndProc1(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static intiNum, iLine, cyClient;HDChdc;TCHARszBuffer[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 numbersLRESULT APIENTRY WndProc2(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static intiNum = 1, iLine, cyClient;HDChdc;inti, iSqrt;TCHARszBuffer[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 numbersLRESULT APIENTRY WndProc3(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static intiNum = 0, iNext = 1, iLine, cyClient;HDChdc;intiTemp;TCHARszBuffer[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 radiiLRESULT APIENTRY WndProc4(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static intcxClient, cyClient;HDChdc;intiDiameter;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 HWNDhwndChild[4];static TCHAR *szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") };static WNDPROCChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 };HINSTANCEhInstance;inti, cxClient, cyClient;WNDCLASSwndclass;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{HWNDhwnd;intcxClient;intcyClient;intcyChar;BOOLbKill;}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");HWNDhwnd;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 numbersvoid Thread1(PVOID pvoid){intiNum = 0, iLine = 0;HDChdc;PPARAMSpparams;TCHARszBuffer[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 numbersvoid Thread2(PVOID pvoid){intiNum = 0, iLine = 0, i, iSqrt;HDChdc;PPARAMSpparams;TCHARszBuffer[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 numbersvoid Thread3(PVOID pvoid){intiNum = 0, iNext = 1, iLine = 0, iTemp;HDChdc;PPARAMSpparams;TCHARszBuffer[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 radiivoid Thread4(PVOID pvoid){intiDiameter;HDChdc;PPARAMSpparams;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 HWNDhwndChild[4];static TCHAR *szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") };static WNDPROCChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 };HINSTANCEhInstance;inti, cxClient, cyClient;WNDCLASSwndclass;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 REP1000000#define STATUS_READY0#define STATUS_WORKING1#define STATUS_DONE2#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");HWNDhwnd;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 numbersvoid Thread(PVOID pvoid){doubleA = 1.0;INTi;LONGlTime;volatilePPARAMSpparams;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);}elseSendMessage(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 INTiStatus;static LONGlTime;static PARAMSparams;static TCHAR *szMessage[] = { TEXT("Ready (left mouse button begins)"),TEXT("Working (right mouse button ends)") ,TEXT("%d repetitions in %ld msec") };HDChdc;PAINTSTRUCTps;RECTrect;TCHARszBuffer[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 REP1000000#define STATUS_READY0#define STATUS_WORKING1#define STATUS_DONE2#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");HWNDhwnd;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 numbersvoid Thread(PVOID pvoid){doubleA = 1.0;INTi;LONGlTime;volatilePPARAMSpparams;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);}elseSendMessage(pparams->hwnd, WM_CALC_ABORTED, 0, 0);}}//define the Window Procedure WndProc  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static HANDLEhEvent;static INTiStatus;static LONGlTime;static PARAMSparams;static TCHAR *szMessage[] = { TEXT("Ready (left mouse button begins)"),TEXT("Working (right mouse button ends)") ,TEXT("%d repetitions in %ld msec") };HDChdc;PAINTSTRUCTps;RECTrect;TCHARszBuffer[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 0
原创粉丝点击