当前位置: 首页 > 文档资料 > VC 经典教程 >

12.1 多任务、进程和线程

优质
小牛编辑
134浏览
2023-12-01

12.1.1 Windows 3.x的协同多任务

在16位的Windows 3.x中,应用程序具有对CPU的控制权。只有在调用了GetMessage、PeekMessage、WaitMessage或Yield后,程序才有可能把CPU控制权交给系统,系统再把控制权转交给别的应用程序。如果应用程序在长时间内无法调用上述四个函数之一,那么程序就一直独占CPU,系统会被挂起而无法接受用户的输入。

因此,在设计16位的应用程序时,程序员必须合理地设计消息处理函数,以使程序能够尽快返回到消息循环中。如果程序需要进行费时的操作,那么必须保证程序在进行操作时能周期性的调用上述四个函数中的一个。

在Windows 3.x环境下,要想设计一个既能执行实时的后台工作(如对通信端口的实时监测和读写),又能保证所有界面响应用户输入的单独的应用程序几乎是不可能的。

有人可能会想到用CWinApp::OnIdle函数来执行后台工作,因为该函数是程序主消息循环在空闲时调用的。但OnIdle的执行并不可靠,例如,如果用户在程序中打开了一个菜单或模态对话框,那么OnIdle将停止调用,因为此时程序不能返回到主消息循环中!在实时任务代码中调用PeekMessage也会遇到同样的问题,除非程序能保证用户不会选择菜单或弹出模态对话框,否则程序将不能返回到PeekMessage的调用处,这将导致后台实时处理的中断。

折衷的办法是在执行长期工作时弹出一个非模态对话框并禁止主窗口,在消息循环内分批执行后台操作。对话框中可以显示工作的进度,也可以包含一个取消按钮以让用户有机会中断一个长期的工作。典型的代码如清单12.1所示。这样做既可以保证工作实时进行,又可以使程序能有限地响应用户输入,但此时程序实际上已不能再为用户干别的事情了。

清单12.1 在协同多任务环境下防止程序被挂起的一种方法

bAbort=FALSE;

lpMyDlgProc=MakeProcInstance(MyDlgProc, hInst);

hMyDlg=CreateDialog(hInst, “Abort”, hwnd, lpMyDlgProc); //创建一个非模态对话框

ShowWindow(hMyDlg, SW_NORMAL);

UpdateWindow(hMyDlg);

EnableWindow(hwnd, FALSE); //禁止主窗口

. . .

while(!bAbort)

{

. . . //执行一次后台操作

. . .

while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))

{

if(!IsDialogMessage(hMyDlg, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

}

EnableWindow(hwnd, TRUE); //允许主窗口

DestroyWindow(hMyDlg);

FreeProcInstance(lpMyDlgProc);

 

12.1.2 Windows 95/NT的抢先式多任务

在32位的Windows系统中,采用的是抢先式多任务,这意味着程序对CPU的占用时间是由系统决定的。系统为每个程序分配一定的CPU时间,当程序的运行超过规定时间后,系统就会中断该程序并把CPU控制权转交给别的程序。与协同式多任务不同,这种中断是汇编语言级的。程序不必调用象PeekMessage这样的函数来放弃对CPU的控制权,就可以进行费时的工作,而且不会导致系统的挂起。

例如,在Windows3.x 中,如果某一个应用程序陷入了死循环,那么整个系统都会瘫痪,这时唯一的解决办法就是重新启动机器。而在Windows 95/NT中,一个程序的崩溃一般不会造成死机,其它程序仍然可以运行,用户可以按Ctrl+Alt+Del键来打开任务列表并关闭没有响应的程序。

12.1.3 进程与线程

在32位的Windows系统中,术语多任务是指系统可以同时运行多个进程,而每个进程也可以同时执行多个线程。

进程就是应用程序的运行实例。每个进程都有自己私有的虚拟地址空间。每个进程都有一个主线程,但可以建立另外的线程。进程中的线程是并行执行的,每个线程占用CPU的时间由系统来划分。

可以把线程看成是操作系统分配CPU时间的基本实体。系统不停地在各个线程之间切换,它对线程的中断是汇编语言级的。系统为每一个线程分配一个CPU时间片,某个线程只有在分配的时间片内才有对CPU的控制权。实际上,在PC机中,同一时间只有一个线程在运行。由于系统为每个线程划分的时间片很小(20毫秒左右),所以看上去好象是多个线程在同时运行。

进程中的所有线程共享进程的虚拟地址空间,这意味着所有线程都可以访问进程的全局变量和资源。这一方面为编程带来了方便,但另一方面也容易造成冲突。

虽然在进程中进行费时的工作不会导致系统的挂起,但这会导致进程本身的挂起。所以,如果进程既要进行长期的工作,又要响应用户的输入,那么它可以启动一个线程来专门负责费时的工作,而主线程仍然可以与用户进行交互。

12.1.4 线程的创建和终止

线程分用户界面线程和工作者线程两种。用户界面线程拥有自己的消息泵来处理界面消息,可以与用户进行交互。工作者线程没有消息泵,一般用来完成后台工作。

MFC应用程序的线程由对象CWinThread表示。在多数情况下,程序不需要自己创建CWinThread对象。调用AfxBeginThread函数时会自动创建一个CWinThread对象。

例如,清单12.2中的代码演示了工作者线程的创建。AfxBeginThread函数负责创建新线程,它的第一个参数是代表线程的函数的地址,在本例中是MyThreadProc。第二个参数是传递给线程函数的参数,这里假定线程要用到CMyObject对象,所以把pNewObject指针传给了新线程。线程函数MyThreadProc用来执行线程,请注意该函数的声明。线程函数有一个32位的pParam参数可用来接收必要的参数。

清单12.2 创建一个工作者线程

//主线程

pNewObject = new CMyObject;

AfxBeginThread(MyThreadProc, pNewObject);

 

//新线程

UINT MyThreadProc( LPVOID pParam )

{

CMyObject* pObject = (CMyObject*)pParam;

 

if (pObject == NULL ||

!pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))

return -1; // 非法参数

 

// 用pObject对象来完成某项工作

 

return 0; // 线程正常结束

}

 

 

AfxBeginThread的声明为:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

 

参数pfnThreadProc是工作线程函数的地址。pParam是传递给线程函数的参数。nPriority是线程的优先级,一般是THREAD_PRIORITY_NORMAL,若为0,则使用创建线程的优先级。nStackSize说明了线程的堆栈尺寸,若为0则堆栈尺寸与创建线程相同。dwCreateFlags指定了线程的初始状态,如果为0,那么线程在创建后立即执行,如果为CREATE_SUSPENDED,则线程在创建后就被挂起。参数lpSecurityAttrs用来说明保密属性,一般为0。函数返回新建的CWinThread对象的指针。

程序应该把AfxBeginThread返回的CWinThread指针保存起来,以便对创建的线程进行控制。例如,可以调用CWinThread::SetThreadPriority来设置线程的优先级,用CWinThread::SuspendThread来挂起线程。如果线程被挂起,那么直到调用CWinThread::ResumeThread后线程才开始运行。

如果要创建用户界面线程,那么必须从CWinThread派生一个新类。事实上,代表进程主线程的CWinApp类就是CWinThread的派生类。派生类必须用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来声明和实现。需要重写派生类的InitInstance、ExitInstance、Run等函数。

可以使用AfxBeginThread函数的另一个版本来创建用户界面线程。函数的声明为:

CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

参数pThreadClass指向一个CRuntimeClass对象,该对象是用RUNTIME_CLASS宏从CWinThread的派生类创建的。其它参数以及函数的返回值与第一个版本的AfxBeginThread是一样的。

当发生下列事件之一时,线程被终止:

线程调用ExitThread。

线程函数返回,即线程隐含调用了ExitThread。

ExitProcess被进程的任一线程显示或隐含调用。

用线程的句柄调用TerminateThread。

用进程句柄调用TerminateProcess。