第二十章 多任务和多线程(多任务的各种模式1)

来源:互联网 发布:田径女神走红网络 编辑:程序博客网 时间:2024/06/05 03:28

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

}
原创粉丝点击