20.1 进程和线程
联系与区别 |
进程 |
线程 |
概念 |
是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位(即进程是资源分配的最小单位)。是文件使用资源的总和(包含地址空间、代码、数据、对象句柄、环境变量和执行单元。进程不是一个可执行的实体。 |
线程是该进程中代码的“执行单元”,是操作系统分配处理器时间的基本单位(即是CPU调度的最小单位) |
关系 |
是线程的容器。一个进程必须要有一个主线程。 |
是进程的内容物。在进程中创建,不能脱离进程而单独存在,整个生命期都存在于进程中,进程被结束,线程也就自然被结束。 |
地址空间 |
进程间是相互独立的 |
同一进程的各线程间共享地址空间 |
资源分配 |
是资源分配的基本单位。同一进程中的线程共享其进程代码、数据及文件等资源 |
线程仅拥有自己的寄存器和栈,除了CPU和在线程中创建的窗口或安装钩子外,线程与任何系统资源分配都无关。 |
通信 |
进程间通信IPC |
线程间的通信:直接读写进程数据段(如全局变量),但需要同步以保证数据的一致性。 |
调度和切换 |
进程切换时,耗费资源较大,效率要差 |
较快 |
消息队列 |
①如果线程创建了一个窗口,Windows会单独给他分配一个消息队列,为了让窗口正常工作。则线程中必须实现消息循环。(这种情况在主线程和子线程都应该如此)。这也是控制台程序不需要消息循环的原因(因为没创建窗口)。 ②如果在子线程中创建窗口,则主线程的消息循环根本不会获得这个窗口的消息,子线程必须设置窗口消息循环。当向窗口发送消息时,系统会先判断窗口是被哪个线程创建的,并把消息分派到正确线程的消息队列中。 ③有了消息循环就必须遵守1/10秒规则,这就意味着线程不该用来处理长时间的工作。如果超过,可另开线程来处理。 |
20.2 Windows中的多线程
20.2.1 多线程的结构
(1)处理用户界面的线程
这些线程创建窗口并设置消息循环来负责处理窗口消息,一个进程中并不需要太多这种线程,一般让主线程负责这个工作就可以了。同时,这种线程不该处理1/10秒以上的工作
(2)工作线程
该类线程不处理窗口界面,当然也就不用处理消息了,它一般在后台运行,干一些繁重的、需要长时间运行的粗活,一般都要遵循1/10秒规则,但工作线程的要求相对比较宽松。
20.2.2 线程的麻烦
(1)竞争条件:不同的线程要访问同一资源,就不可避免会出现“竞争”,而问题解决的结果依赖于线程运行的时序,而不是程序的逻辑,这种情况称为竞争条件
(2)“死锁”:当两个程序己经中断彼此的运行,但是它们只有继续进行才能解除对方被中断的运行。
20.2.4 与线程有关的函数
(1)创建线程 CreateThread函数:返回值为线程的句柄
参数 |
含义 |
lpTreadAttributes |
指向SECURITY_ATTRIBUTES结构,用来定义线程的安全属性。如果想让线程使用默认的安全属性,可设为NULL |
dwStackSize |
线程的堆栈大小。如果指定为0,那么线程的堆栈大小和主线程使用的大小相同。系统自动在进程的地址空间中为每个新线程分配私有的堆栈空间,这些空间在线程结束的时候会自动被释放,如果需要,会自动增大空间。 |
lpStartAddress |
线程开始执行的地址。这个地址是一个规定格式的函数的入口地址,也被称为“线程函数” |
dwParameter |
传递给线程函数的自定义参数,可传递额外的数据 |
dwCreateFlags |
创建标志。 0——线程创建后立即开始运行 CREATE_SUSPENDED——创建后处于挂起状态,直到调用函数ResumeThread去启动 |
lpThreadID |
用来接收函数返回的线程ID(某些函数要用到线程ID) |
(2)线程函数:DWORD WINAPI ThreadProc(LPVOID lpParameter);
参数 |
含义 |
lpParameter |
该参数为CreateThread中dwParameter传入的参数。 |
(3)终止线程:线程结束后的退出码可被其他线程用GetExitCodeThread检测到。
方法 |
介绍 |
第1种方法 |
自然退出,当线程函数执行完毕后,就会完成线程的执行。 |
第2种方法 |
VOID ExitThread(dwExitCode ); //只能终止自己,不能终止另外一个线程。 其中dwExitCode是用户定义的,可以用别的线程用GetExitCodeThreads判断退出的类型。用法:如因某种原因需在线程函数内部提前退出线程,可在线程内部调用该函数,并指定dwExitCode以便让其他线程有机会判断。 |
第3种方法 |
TerminateThread(hThread,dwExitCode); A、hThread:要终止的线程句柄,dwExitCode:用做被上线程的退出码。 B、TerminateThread是异步执行的,要确认线程己经真正结束,可用GetExitCodeThread来检测。 C、一般这个函数被强烈建议不要使用,因为一旦执行该函数,程序无法预测目标线程会在何处被中止,可能导致没有机会来做线程内部的一些清除工作。 |
第4种方法 |
ExitPorcess函数结束进程。——也应该避免使用,因为这种方法相当于对每个线程使用TerminateThread函数。 |
★几点注意
①一般都应该尽量让线程自然结束,如果主线程要求某个线程结束,可以通过各种方法通知线程,线程收到通知后,做完扫尾工作后自行退出。只有在万不得己的情况下才使用TerminateThread函数去终止一个线程。
②当一个线程终止时,Windows释放线程所需的各种资源(如堆栈与寄存器环境),并且不再继续分配时间片,但线程对象并不马上释放,因为以后其他线程还需要GetExitCodeThread函数检测线程的退出码。线程对象会一直保存到CloseHandle才关闭。
(4)挂起线程SuspendThread(hThread)
系统为每个线程维护一个暂停计数器,SuspendThread一次,计数器加1。如果计数器的值大于0,则不会安排时间片。即线程处理挂起。
(5)恢复(唤醒)线程:ResumeThread(hThread):计数器减1,直到0时,才唤醒。
★一个线程可以将别的线程挂起,也可以自己挂起。但一旦挂起,就必须由别人来唤醒,自己无法恢复自己(因为该线程没时间片,就不能执行任何函数了)。
(6)获取线程的退出码:GetExitCode(hThread,&ExitCode);
①当一个线程没有结束的时候,ExitCode==STILL_ACTIVE。
②如果结束了,则ExitCode返回的是由终止线程 (如ExitThread函数)中指定的值或线程函数中的返回值或进程的退出码。
20.2.5 多线程应用举例
(1)【RndRctMT程序】多线程画随机矩形
/*------------------------------------------------------------ RNDRCTMT.C -- Displays Random Rectangles (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #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 ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; 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 ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } 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) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } VOID ThreadProc(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; //取出最低位8位 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)); DeleteObject(hBrush); ReleaseDC(hwnd, hdc); } } } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: _beginthread(ThreadProc, 0, NULL); //需包含process.h文件 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) ; }
(2)【Multi1】编程竞赛问题——利用计时器来模拟多线程(但不是真正的多线程程序)
①当移动窗口或改变窗口大小时,所有的窗口输出会停止(因为要处理主窗口的消息)
②如果在所有的任务能在一条WM_TIMER处理完,并有空余,就意味着没有充分利用CPU的计算能力。(此时CPU空闲较多,没被充分利用)
/*------------------------------------------------------------ MULTI1.C -- Multitasking Demo(使用计时器模拟多线程) (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #include <math.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; //主窗口过程 LRESULT APIENTRY WndProc1(HWND, UINT, WPARAM, LPARAM); //窗口过程1(显示自然数递增的) LRESULT APIENTRY WndProc2(HWND, UINT, WPARAM, LPARAM); //窗口过程2(质数递增) LRESULT APIENTRY WndProc3(HWND, UINT, WPARAM, LPARAM); //窗口过程3(斐波那契数) LRESULT APIENTRY WndProc4(HWND, UINT, WPARAM, LPARAM); //窗口过程3(随机半径的圆) int cyChar; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Multi1") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; 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 ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } 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) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndChild[4]; static WNDPROC ChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 }; static TCHAR* szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") }; HINSTANCE hInstance; int i, cxClient, cyClient; WNDCLASS wndclass; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->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: //每个子窗体发送WM_TIMER消息,也可以是其他自定义的消息,主要是触发绘图用的 for (i = 0; i < 4; i++){ SendMessage(hwndChild[i], WM_TIMER, wParam, lParam); } return 0; case WM_CHAR: if (wParam == 'x1B') //ESC DestroyWindow(hwnd); return 0; case WM_DESTROY: KillTimer(hwnd, 1); PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } int CheckBottom(HWND hwnd, int cyClient, int iLine) { //如果当前是最后一行 if (iLine*cyChar + cyChar >cyClient) { InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); iLine = 0; } return iLine; } //窗口过程1(显示自然数递增) LRESULT APIENTRY WndProc1(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cyClient, iLine,iNum; TCHAR szBuffer[16]; HDC hdc; switch (message) { case WM_SIZE: cyClient = HIWORD(lParam); return 0; case WM_TIMER: hdc = GetDC(hwnd); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(hwnd, cyClient, iLine); TextOut(hdc, 0, iLine*cyChar, szBuffer, wsprintf(szBuffer,TEXT("%d"),iNum++)); iLine++; ReleaseDC(hwnd, hdc); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程2(质数递增) LRESULT APIENTRY WndProc2(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cyClient, iLine, iNum=1; TCHAR szBuffer[16]; int i, iSqrt; HDC hdc; switch (message) { case WM_SIZE: cyClient = HIWORD(lParam); return 0; case WM_TIMER: hdc = GetDC(hwnd); // do { //iNum是静态变量,所以每找到一个,会被保留下来,下次是从iNum接下去找 if (++iNum < 0) //iNum递增超过最大整数时(出现负数),则从新开始 iNum = 0; //判断iNum是不是质数 iSqrt = (int)sqrt(iNum); for (i = 2; i <=iSqrt; i++) { //能整除,说明不是质数 if (iNum % i == 0) break; //跳出for,找下个iNum } } while (i<=iSqrt); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(hwnd, cyClient, iLine); TextOut(hdc, 0, iLine*cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++)); iLine++; ReleaseDC(hwnd, hdc); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程3(斐波那契数) LRESULT APIENTRY WndProc3(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cyClient, iLine, iNum=0,iNext=1; int iTemp; TCHAR szBuffer[16]; HDC hdc; switch (message) { case WM_SIZE: cyClient = HIWORD(lParam); return 0; case WM_TIMER: //随着WM_TIMER时增加,依次计算斐波那契数列的各个值。 if (iNum<0) //当iNum超过整数的最大值时(变为负数),则从新开始计算 { iNum = 0; iNext = 1; } //输出 hdc = GetDC(hwnd); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(hwnd, cyClient, iLine); TextOut(hdc, 0, iLine*cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum)); iLine++; ReleaseDC(hwnd, hdc); iTemp = iNum; iNum = iNext; iNext += iTemp; return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程4(画随机半径的圆) LRESULT APIENTRY WndProc4(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient; int iDiameter; //直径 HDC hdc; switch (message) { case WM_SIZE: cyClient = HIWORD(lParam); cxClient = LOWORD(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); }
(3)【Multi2】编程竞赛问题——利用多线程技术
①在每个子窗口过程的WM_CREATE中开辟一个独立的工作线程,线程函数名如Thread1等。
②_beginthread的第三个参数用来将子窗口的一个结构体传到线程函数中去。这个结构体有五个字段——一个窗口句柄、窗口宽度和高度、字符高度和一个是否中止线程的布尔变量。
③一般线程退出线程函数后,会自动销毁线程,但如果线程处于一个很复杂的处理过程中,调用_endthread是很有用的。
④与Multi1程序相比,在拖动主窗口或改变窗口大小时,所有子窗口的输出不会停止。
⑤以WndProc1和Thread1为例,分析该线程存在的两个问题:
A、以WndProc1在主线程,Thread1在子线程中运行,两者是并行的。这两个线程之间的切换是不可预知的。假设Thread1刚执行到检查pParams->bKill是否为TRUE时(设此时非TRUE),但这时被切换到主线程,而就在这里,用户按ESC终止了程序。WndProc1会收到WM_DESTORY消息,将bKill设为TRUE。但太晚了,设这时,操作系统突然又切换到Thread1函数中,在这个函数里会试图获取一个不存在的窗口的设备环境。
B、另一个问题就是主线程可能收到WM_ERASEBKGND或WM_PAINT消息。但这时子线程正在绘图输出。(这个问题Windows帮我们用串行化处理了),但有些GDI函数是非串行化的,如画笔、画刷、字体,位图、区域和调色板。有可能出现一个线程销毁了另一个线程正在使用的对象。——使用临界区或不要在线程间共享GDI对象。
【效果图】与Multi1程序一样,但运行速度更快了
/*------------------------------------------------------------ MULTI2.C -- Multitasking Demo(真正的多线程程序) (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> #include <math.h> #include <process.h> //_beginthread函数要用到该头文件 //供线程函数使用的额外数据结构 typedef struct { HWND hwnd; int cxClient; int cyClient; int cyChar; BOOL bKill; //用来决定是否中止线程。 //主线程按ESC键时,在退出程序前,各线程根据这个 //标出退出线程函数,当然也就退出了线程。 }PARAMS,*PPARAMS; LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; //主窗口过程 LRESULT APIENTRY WndProc1(HWND, UINT, WPARAM, LPARAM); //窗口过程1(显示自然数递增的) LRESULT APIENTRY WndProc2(HWND, UINT, WPARAM, LPARAM); //窗口过程2(质数递增) LRESULT APIENTRY WndProc3(HWND, UINT, WPARAM, LPARAM); //窗口过程3(斐波那契数) LRESULT APIENTRY WndProc4(HWND, UINT, WPARAM, LPARAM); //窗口过程3(随机半径的圆) void Thread1(PVOID pvoid); //线程函数1(显示自然数递增的) void Thread2(PVOID pvoid); //线程函数2(质数递增) void Thread3(PVOID pvoid); //线程函数3(斐波那契数) void Thread4(PVOID pvoid); //线程函数3(随机半径的圆) int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Multi2") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; 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 ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } 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) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndChild[4]; static WNDPROC ChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 }; static TCHAR* szChildClass[] = { TEXT("Child1"), TEXT("Child2"), TEXT("Child3"), TEXT("Child4") }; HINSTANCE hInstance; int i, cxClient, cyClient; WNDCLASS wndclass; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT)lParam)->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') //ESC DestroyWindow(hwnd); return 0; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } 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; } //窗口过程1(显示自然数递增) 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()); params.bKill = FALSE; _beginthread(Thread1, 0, ¶ms); //带额外参数,注意_endthread()在线程函数中调用。 return 0; case WM_SIZE: params.cyClient = HIWORD(lParam); params.cxClient = LOWORD(lParam); return 0; case WM_DESTROY: params.bKill = TRUE; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程2(质数递增) 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()); params.bKill = FALSE; _beginthread(Thread2, 0, ¶ms); //带额外参数,注意_endthread()在线程函数中调用。 return 0; case WM_SIZE: params.cyClient = HIWORD(lParam); params.cxClient = LOWORD(lParam); return 0; case WM_DESTROY: params.bKill = TRUE; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程3(斐波那契数) 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()); params.bKill = FALSE; _beginthread(Thread3, 0, ¶ms); //带额外参数,注意_endthread()在线程函数中调用。 return 0; case WM_SIZE: params.cyClient = HIWORD(lParam); params.cxClient = LOWORD(lParam); return 0; case WM_DESTROY: params.bKill = TRUE; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //窗口过程4(画随机半径的圆) 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()); params.bKill = FALSE; _beginthread(Thread4, 0, ¶ms); //带额外参数,注意_endthread()在线程函数中调用。 return 0; case WM_SIZE: params.cyClient = HIWORD(lParam); params.cxClient = LOWORD(lParam); return 0; case WM_DESTROY: params.bKill = TRUE; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } //线程函数1(显示自然数递增) void Thread1(PVOID pvoid) { PPARAMS pParams; TCHAR szBuffer[16]; HDC hdc; int iLine = 0,iNum=0; pParams = (PPARAMS)pvoid; while (!pParams->bKill) //用户如果没要出退出线程时 { hdc = GetDC(pParams->hwnd); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(pParams->hwnd, pParams->cyClient, pParams->cyChar, iLine); TextOut(hdc, 0, iLine*pParams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++)); iLine++; ReleaseDC(pParams->hwnd, hdc); } _endthread(); //一般退出该线程函数后,线程会自动销毁,但在很复杂的线程 //环境中,用这个函数有时很有用 } //线程函数2(质数递增) void Thread2(PVOID pvoid) { PPARAMS pParams; TCHAR szBuffer[16]; HDC hdc; int iLine = 0, iNum = 0,i,iSqrt; pParams = (PPARAMS)pvoid; while (!pParams->bKill) //用户如果没要出退出线程时 { hdc = GetDC(pParams->hwnd); do { //iNum是静态变量,所以每找到一个,会被保留下来,下次是从iNum接下去找 if (++iNum < 0) //iNum递增超过最大整数时(出现负数),则从新开始 iNum = 0; //判断iNum是不是质数 iSqrt = (int)sqrt(iNum); for (i = 2; i <= iSqrt; i++) { //能整除,说明不是质数 if (iNum % i == 0) break; //跳出for,找下个iNum } } while (i <= iSqrt); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(pParams->hwnd, pParams->cyClient, pParams->cyChar, iLine); TextOut(hdc, 0, iLine*pParams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum++)); iLine++; ReleaseDC(pParams->hwnd, hdc); } _endthread();//一般退出该线程函数后,线程会自动销毁,但在很复杂的线程 //环境中,用这个函数有时很有用 } //线程函数3(斐波那契数) void Thread3(PVOID pvoid) { PPARAMS pParams; int iLine=0, iNum=0,iNext=1; int iTemp; TCHAR szBuffer[16]; HDC hdc; pParams = (PPARAMS)pvoid; while (!pParams->bKill) //用户如果没要出退出线程时 { //依次计算斐波那契数列的各个值。 if (iNum<0) //当iNum超过整数的最大值时(变为负数),则从新开始计算 { iNum = 0; iNext = 1; } //输出 hdc = GetDC(pParams->hwnd); //注意,这里该子窗口不清屏,会从上到下依次输出,直到最底部时,由CheckBottom判断,并清屏。 iLine = CheckBottom(pParams->hwnd, pParams->cyClient, pParams->cyChar, iLine); TextOut(hdc, 0, iLine*pParams->cyChar, szBuffer, wsprintf(szBuffer, TEXT("%d"), iNum)); iLine++; ReleaseDC(pParams->hwnd, hdc); iTemp = iNum; iNum = iNext; iNext += iTemp; } _endthread();//一般退出该线程函数后,线程会自动销毁,但在很复杂的线程 //环境中,用这个函数有时很有用 } //线程函数4(画随机半径的圆) void Thread4(PVOID pvoid) { PPARAMS pParams; int iDiameter; //直径 HDC hdc; 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();//一般退出该线程函数后,线程会自动销毁,但在很复杂的线程 //环境中,用这个函数有时很有用 }
20.2.6 休眼的好处
(1)多线程最好的架构是让主线程创立所有的窗口,拥有所有的窗口过程和消息处理。子线程为工作线程,工作于后台。正因为如此,在主线程创建动画很容易,直接在WM_TIMER就可实现。但子线程没有窗口,没有消息处理,也就没有了时间控制,因线程切换一般很快,所以动画会运行过快。
(2)解决方法就是Sleep。调用Sleep时,线程会主动挂起,在指定的时间段里,系统不会为其分配时间片。(当传入的参数为0,使得现有的时间段作废,即马上挂起,等待下一个时间片的到来)
(3)主线程中,不应该使用Sleep函数,因为这会使主线程交给时间片,减慢消息的处理。当然,如果主线程没创立窗口(没有消息处理),那还是可以Sleep的。
(4)最牢靠而最立即的警告就是,千万不要在一个 CriticalSection 之中调用 Sleep() 或任何 Wait...() API 函数。(见《Win32多线程程序设计P101》