事件Event:简单的线程同步

来源:互联网 发布:车生活是什么软件 编辑:程序博客网 时间:2024/05/18 19:47


多线程入门这篇文章中,地址如下:
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