第二十章 多任務和多線程(多任務的各種模式1)

Windows 的多線程處理

建立新的線程的API函數是CreateThread,它的語法如下:

hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,

pParam, dwFlags, &idThread) ;

第一個參數是指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,它被設爲NULL。第二個參數是用於新線程的初始堆棧大小,默認值爲0。在任何情況下,Windows根據需要動態延長堆棧的大小。

CreateThread的第三個參數是指向線程函數的指標。函數名稱沒有限制,但是必須以下列形式聲明:

DWORD WINAPI ThreadProc (PVOID pParam) ;

CreateThread的第四個參數爲傳遞給ThreadProc的參數。這樣主線程和從屬線程就可以共享數據。

CreateThread的第五個參數通常爲0,但當建立的線程不馬上執行時爲旗標CREATE_SUSPENDED。線程將暫停直到呼叫ResumeThread來恢復線程的執行爲止。第六個參數是一個指標,指向接受執行緒ID值的變量。

大多數Windows程序寫作者喜歡用在PROCESS.H表頭文件中聲明的C執行時期鏈接庫函數_beginthread。它的語法如下:

hThread = _beginthread (ThreadProc, uiStackSize, pParam) ;

它更簡單,對於大多數應用程序很完美,這個線程函數的語法爲:

void __cdecl ThreadProc (void * pParam) ;

再論隨機矩形

程序20-1 RNDRCTMT是第五章裏的RANDRECT程序的多線程版本,您將回憶起RANDRECT使用的是PeekMessage循環來顯示一系列的隨機矩形。

程序20-1 RNDRCTMT
        
RNDRCTMT.C

/*---------------------------------------------------------------------------

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, TEXT ("Random Rectangles"),

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 ;

}


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) ;

}

}

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (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) ;

}

在建立多線程的Windows程序時,需要在「Project Settings」對話框中做一些修改。選擇「C/C++」頁面標籤,然後在「Category」下拉式清單方塊中選擇「Code Generation」。在「Use Run-Time Library」下拉式清單方塊中,可以看到用於「Release」設定的「Single-Threaded」和用於Debug設定的「Debug Single-Threaded」。將這些分別改爲「Multithreaded」和「Debug Multithreaded」。這將把編譯器旗標改爲/MT,它是編譯器在編譯多線程的應用程序所需要的。具體地說,編譯器將在.OBJ文件中插入LIBCMT.LIB文件名,而不是LIBC.LIB。連結程序使用這個名稱與執行期鏈接庫函數連結。

LIBC.LIB和LIBCMT.LIB文件包含C語言鏈接庫函數,有些C語言鏈接庫函數包含靜態數據。例如,由於strtok函數可能被連續地多次呼叫,所以它在靜態內存中儲存了一個指標。在多線程程序中,每個線程必須在strtok函數中有它自己的靜態指針。因此,這個函數的多線程版本稍微不同於單線程的strtok函數。

同時請注意,我在RNDRCTMT.C中包含了表頭文件PROCESS.H,這個文件定義一個名爲_beginthread的函數,它啓動一個新的線程。只有定義了_MT標識符,纔會聲明這個函數,這是/MT旗標的另一個結果。

在RNDRCTMT.C的WinMain函數中,由CreateWindow傳回的hwnd值被儲存在一個整體變量中,因此cxClient和cyClient值也可以由窗口消息處理程序的WM_SIZE消息獲得。

窗口消息處理程序以最容易的方法呼叫_beginthread-簡單地以線程函數的地址(稱爲Thread)作爲第一個參數,其它參數使用0,線程函數傳回VOID並有一個參數,該參數是一個指向VOID的指標。在RNDRCTMT中的Thread函數不使用這個參數。

在呼叫了_beginthread函數之後,線程函數(以及該線程函數可能呼叫的其它任何函數)中的程序代碼和程序中的其它程序代碼同時執行。兩個或者多個執行緒使用一個程序中的同一函數,在這種情況下,動態區域變量(儲存在堆棧上)對每個執行緒是唯一的。對程序中的所有執行緒來說,所有的靜態變量都是一樣的。這就是窗口消息處理程序設定整體的cxClient和cyClient變量並由Thread函數使用的方式。

有時您需要唯一於各個線程的持續儲存性數據。通常,這種數據是靜態變量,但在Windows 98中,您可以使用「線程區域儲存空間」,我將在本章後面進行討論。

程序設計競賽的問題

1986年10月3日,Microsoft舉行了爲期一天,針對計算機雜誌出版社的技術編輯和作者的簡短的記者招待會,來討論他們當時的一組語言產品,包括他們的第一個交談式開發環境,QuickBASIC 2.0。當時,Windows 1.0出現還不到一年,但是沒有人知道我們什麼時候能得到與該環境類似的東西(這花了好幾年)。這一事件與衆不同的部分原因是由於Microsoft的公關人員所舉辦的「Storm the Gates」程序設計競賽。Bill Gates使用QuickBASIC 2.0,而計算機出版社的人員可以使用他們選擇的任何語言產品。

競賽的問題是從公衆提出的題目中挑選出來的(挑選那些需要寫大約半小時程序來解決的問題),問題如下:

建立一個包含四個窗口的多任務仿真程序。第一個窗口必須顯示一系列的遞增數,第二個必須顯示一系列的遞增質數,而第三個必須顯示Fibonacci數列(Fibonacci數列以數字0和1開始,後頭每一個數都是其前兩個數的和-即0、1、1、2、3、5、8等等)。這三個窗口應該在數字達到窗口底部時或者進行滾動,或者自行清除窗口內容。第四個窗口必須顯示任意半徑的圓,而程序必須在按下一個Escape鍵時終止。

當然,在1986年10月,在DOS下執行的這樣一個程序最多隻能是模擬多任務而已,而且沒有一個競賽者具有足夠的勇氣-並且其中大多數也沒有足夠的知識-來爲Windows編寫這個程序。再者,如果真要這麼做,當然不會只花半小時了!

參加這次競賽的大多數人編寫了一個程序來將屏幕分爲四個區域,程序中包含一個循環,依次更新每個窗口,然後檢查是否按下了Escape鍵。如同DOS環境下的傳統習慣,程序佔用了百分之百的CPU處理時間。

如果在Windows 1.0中寫程序,那麼結果將是類似程序20-2 MULTI1的結果。我說「類似」,是因爲我編寫的程序是32位的,但程序結構和相當多的程序代碼-除了變量和函數參數定義以及Unicode支持-都是相同的。

程序20-2 MULTI1
        
MULTI1.C

/*--------------------------------------------------------------------------

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) ;

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) ;

}
發佈了60 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章