在
多线程入门这篇文章中,地址如下:
http://blog.chinaunix.net/u/5391/showart_546194.html
我们写了一个计数器程序,用一个子线程进行计数,但是你打开任务管理器,你会发现,这个这个Counter.exe程序运行时CPU占用非常高,我的机器达到了50%,即使你按暂停按钮的话,cpu占用率仍然不会减少,这样的程序,效率是很低的。为什么会在暂停的时候也有这么高的占用率呢?
我们可以看看那个源程序,在Counter函数中,即使你在主线程中点击了暂停,但是仍然在进行条件测试,CPU仍然会给它分配时间片,所以它的CPU占用率不会因为你暂停而减少。为了解决这个问题,提高程序的效率,我们可以用事件来解决这个问题。
先不多说,看看程序:
// counter.c
#include <windows.h>
#include "resource.h"
HWND hWinMain = NULL;
HWND hWinCount = NULL;
HWND hWinPause = NULL;
DWORD dwOption = 0;
HANDLE hEvent = NULL;
#define F_PAUSE 0x01
#define F_STOP 0x02
#define F_COUNTING 0x04
char szStop[] = TEXT("停止计数");
char szStart[] = TEXT("计数");
DWORD WINAPI Counter(LPVOID lpParameter);
LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
{
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, 0);
return 0;
}
LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HANDLE hThread = NULL;
switch ( uMsg )
{
case WM_COMMAND:
if ( LOWORD(wParam) == IDOK )
{
if ( dwOption & F_COUNTING )
{
SetEvent(hEvent);
dwOption |= F_STOP;
}
else
{ // 创建一个线程来计数
hThread = CreateThread(NULL, 0, Counter, NULL, 0, NULL);
CloseHandle(hThread);
}
}
else if ( LOWORD(wParam) == IDC_PAUSE )
{
dwOption ^= F_PAUSE;
if ( dwOption & F_PAUSE )
ResetEvent(hEvent); // 事件复位,表示暂停
else
SetEvent(hEvent); // 事件置位,表示继续
}
break;
case WM_CLOSE:
CloseHandle(hEvent);
EndDialog(hWnd, 0);
break;
case WM_INITDIALOG:
hWinMain = hWnd;
hWinCount = GetDlgItem(hWnd, IDOK);
hWinPause = GetDlgItem(hWnd, IDC_PAUSE);
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
break;
default:
return FALSE;
}
return TRUE;
}
DWORD WINAPI Counter(LPVOID lpParameter)
{
DWORD dwNum = 0;
dwOption |= F_COUNTING; // 正在计数标志
dwOption &= ~(F_STOP | F_PAUSE); // 清除停止和暂停标志
SetEvent(hEvent); // 计算开始,事件被置位
SetWindowText(hWinCount, szStop);
EnableWindow(hWinPause, TRUE);
while ( !(dwOption & F_STOP) ) // 不是停止,暂停或者正在进行
{
++dwNum;
SetDlgItemInt(hWinMain, IDC_COUNTER, dwNum, FALSE);
WaitForSingleObject(hEvent, INFINITE);// 等待事件被置位
}
SetWindowText(hWinCount, szStart);
EnableWindow(hWinPause, FALSE);
dwOption &= ~(F_STOP | F_PAUSE | F_COUNTING);
return 0;
}
这个程序,除了.c文件有所更改之外,其它的都跟多线程入门这篇文章中的一样。
我们来分析一下这个程序,在主对话框创建的时候,我们用
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
来创建一个手工重置的事件对象。CreateEvent的函数原型是:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset, // reset type
BOOL bInitialState, // initial state
LPCTSTR lpName // object name
);
lpEventAttributes安全标志,一般可以为NULL,bManualReset指明事件是否手工置位,如果是手工置位,则必须由自己来调用SetEvent和ResetEvent来改变事件的状态,而自动置位,则是有操作系统来自动改变事件状态,具体来说就是当测试事件的函数返回时(返回原因可能是超时,也可能是对象状态被置位引起),对象的状态会自动被复位。
bInitialState参数指定事件对象创建时的初始状态,TRUE表示初始状态是置位状态,FALSE表示初始状态是复位状态
lpName指向一个以0结尾的字符串,用来指定事件对象的名称,和内存共享文件一样,为事件对象命名是为了在其他地方使用OpenEvent函数获取事件对象的句柄。如果不需要命名,那么可以在这里使用NULL。
如果函数执行成功,函数的返回值是事件的句柄,如果失败,则返回0。
事件可以看着是windows内部的一个标志,它有2中状态,一个是置位,一个是复位,置位可以用函数SetEvent来实现,复位可以用函数ResetEvent来实现。等待事件就是用来测试事件的状态,可以用WaitForSingleObject来实现:
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds );
WaitForSingleObject函数可以测试的不仅是事件对象,它也可以用来测试线程和进程等对象的状态,hHandle参数用来指定为等待的对象句柄,dwMilliseconds参数指定以ms为单位的超时时间,当以下两种情况中的任意一种发生的时候,函数就返回:
● 测试对象的状态变为置位状态。
● 到了dwMilliseconds指定的超时时间。
如果dwMilliseconds参数指定为0的话,WaitForSingleObject在测试对象的状态后马上返回,如果需要函数无限期等待直到对象的状态变为“置位”为止的话,可以在该参数中使用INFINITE预定义值。
如果函数执行失败,返回值为WAIT_FAILED。如果函数执行成功,返回值代表函数返回的原因,当返回值是WAIT_OBJECT_0时,表示返回原因是对象的状态被置位,返回值是WAIT_TIMEOUT的时候表示返回原因是超时。
函数可以测试的对象有多种,不同的对象对状态的定义是不同的, 对事件对象调用SetEvent函数后,状态为“置位”,对事件对象调用ResetEvent函数后,状态为“复位”。
在Counter函数中,当正在计算的时候SetEvent(hEvent);将事件置位,并且开始计数,当执行到
WaitForSingleObject(hEvent, INFINITE);
的时候,如果事件还是置位状态,那么立即返回,否则就一直等在那里,这个操作使得操作系统暂时挂起这个子线程,并且,不会给它分配CPU事件,于是当我们点击暂停的时候,CPU的占用率就立即降了下来,直到为0,当我们点击恢复的时候,事件重新被置位,那么WaitForSingleObject函数立即返回,于是子线程重新被激活,继续进行计数的工作。这样通过事件就大大的提高了程序的工作效率。并且事件是操作系统内部的状态标志,这样效率更高,操作系统可以根据事件的状态来决定如何给应用程序分配时间片。
本程序有罗云彬32位汇编语言中的例子改写而来,理论也主要来源于此书,如果想了解更多的信息,请参看此书。
转载请注明出处。
author: cnhnyu
e-mail: cnhnyu<AT>gmail<DOT>com
qq: 94483026