先得多说两句进程与线程的基本知识。不过自己不是学计算机的,看《操作系统》的感觉就跟看马哲差不多,全是原理性的内容,感觉很干涩,就勉强说几句吧。
进程是程序,数据以及进程控制块(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%。
虽然多线程程序运行起来速度很快,但是很多人还是不建议使用它,因为有些时候,会出现线程的切换会产生很严重但又难以察觉的后果,这需要很多保护机制来避免它们。