先得多说两句进程与线程的基本知识。不过自己不是学计算机的,看《操作系统》的感觉就跟看马哲差不多,全是原理性的内容,感觉很干涩,就勉强说几句吧。
进程是程序,数据以及进程控制块(PCB)组成的,它是资源分配的最小单位。为什么要提出线程的概念呢?因为进程是资源的拥有者,所以进程使用起来代价太大了,得创建,撤销,切换,而且进程间数据的交换必须使用特定的机制。这导致一个系统中同时存在的进程不宜过多。由此,提出了一个比进程更小的概念——线程。线程是资源调度的最小单位,占有的资源少,而且可以共享进程资源。这意味着多个线程可以共享数据等资源,这就避免了复杂的通信机制。总而言之,线程的速度比进程的快。
肤浅的理解就是这些吧。还是看程序。
程序的要求是这样的:
显示4个窗口。第一个窗口必须显示一系列的递增数,第二个必须显示一系列的递增质数,而第三个必须显示Fibonacci数列(Fibonacci数列以数字0和1开始,后头每一个数都是其前两个数的和-即0、1、1、2、3、5、8等等)。这三个窗口应该在数字达到窗口底部时或者进行滚动,或者自行清除窗口内容。第四个窗口必须显示任意半径的圆,而程序必须在按下一个Escape键时终止。
先看一个非多线程的版本:
/*--------------------------------------- MULTI1.C -- Multitasking Demo (c) Charles Petzold, 1998 ---------------------------------------*/ #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 ; 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, TEXT ("Multitasking Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; 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) ; //将行宽置为0 iLine = 0 ; } return iLine ; } //窗口1:展示递增的序列 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) ; } //窗口2:显示递增的质数 //求素数的方法:对于N来说,不必用从2到N一1的所有素数去除,用小于等于√N(根号N)的所有素数去除就可以了 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 { if (++iNum < 0) iNum = 0 ; //获得该数的开方 iSqrt = (int) sqrt ((double)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) ; } //窗口3:显示Fibonacci数列 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) ; //F0=0,F1=1,Fn=F(n-1)+F(n-2) iTemp = iNum ; iNum = iNext ; iNext += iTemp ; iLine++ ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } //窗口4:显示半径随机的圆 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) ; //确保直径最小为1,最大不会超过客户区的显示范围 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) ; } //主窗口程序: LRESULT APIENTRY 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) { 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_CHAR: //16进制1B即10进制27:ESC键的ASC码 if (wParam == '\x1B') DestroyWindow (hwnd) ; return 0 ; case WM_DESTROY: KillTimer (hwnd, 1) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
程序流程比较简单:
在主窗口的回调函数的WM_CREATE消息下,创建并注册4个子窗口,然后设置一个计时器;在WM_SIZE消息下将它们移动到正确的位置上;每当WM_TIMER到来,向4个子窗口发送时钟信号。对于每个子窗口,当接收到WM_TIMER消息时,完成自己工作。
再看一个多线程的版本:
/*--------------------------------------- MULTI2.C -- Multitasking Demo (c) Charles Petzold, 1998 ---------------------------------------*/ #include <windows.h> #include <math.h> #include <process.h> typedef struct { HWND hwnd ; int cxClient ; int cyClient ; int cyChar ; BOOL bKill ; } PARAMS, *PPARAMS ; LRESULT APIENTRY 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 ; 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, TEXT ("Multitasking Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; 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 ; } //窗口1:展示递增的序列 void Thread1 (PVOID pvoid) { HDC hdc ; int iNum = 0, iLine = 0 ; 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) ; } //窗口2:显示递增的质数 void Thread2 (PVOID pvoid) { HDC hdc ; int iNum = 1, iLine = 0, i, iSqrt ; PPARAMS pparams ; TCHAR szBuffer[16] ; pparams = (PPARAMS) pvoid ; while (!pparams->bKill) { do { if (++iNum < 0) iNum = 0 ; iSqrt = (int) sqrt ((double)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) ; } //窗口3:显示Fibonacci数列 void Thread3 (PVOID pvoid) { HDC hdc ; int iNum = 0, iNext = 1, iLine = 0, iTemp ; 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) ; } //窗口4:画半径随机的椭圆 void Thread4 (PVOID pvoid) { HDC hdc ; int iDiameter ; 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.cxClient = LOWORD (lParam) ; params.cyClient = HIWORD (lParam) ; return 0 ; case WM_DESTROY: params.bKill = TRUE ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } //主窗口的回调函数 LRESULT APIENTRY 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) { 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) ; }
多线程程序的架构是这样的:你的主执行绪建立您程序所需要的所有窗口,并在其中包含所有的窗口消息处理程序,以便处理这些窗口的所有消息;所有其它执行绪只进行一些背景处理,除了和主执行绪通讯,它们不和使用者进行交流。
可以把这种架构想象成:主线程处理使用者输入(和其它消息),并建立程序中的其它线程,这些附加的线程完成与使用者无关的工作。
两个程序区别不大。主要在于WM_CREATE消息处理期间呼叫_beginthread函数来建立另一个线程。
注意:
注意如果你使用的是较老的编译器,比如VC++6.0,需要在「Project Settings」对话框中做一些修改。选择「C/C++」页面标签,然后在「Category」下拉式清单方块中选择「Code Generation」。在「Use Run-Time Library」下拉式清单方块中,可以看到用于「Release」设定的「Single-Threaded」和用于Debug设定的「Debug Single-Threaded」。将这些分别改为「Multithreaded」和「Debug Multithreaded」。但是如果使用的是VS2010,这些改动就不是必须的了。
我们可以看到第二个程序的运行速度明显比第一个快很多。从我的电脑的CPU使用率也可以发现,第一个程序的使用率只有10%左右,第二个达到了80%。
虽然多线程程序运行起来速度很快,但是很多人还是不建议使用它,因为有些时候,会出现线程的切换会产生很严重但又难以察觉的后果,比如线程间必须同步,这需要很多保护机制来避免它们。