您的位置:首页 > 编程语言

Windows编程--线程和内核对象的同步-事件内核对象

2011-01-05 17:37 351 查看
在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态布尔值

事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件

当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。

当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。这第二个线程知道第一个线程已经完成了它的操作。

下面是CreateEvent函数,用于创建事件内核对象:

HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOLfManualReset,
BOOLfInitialState,
PCTSTRpszName);


fMannualReset参数是个布尔值,它能够告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)。

fInitialState参数用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。当系统创建事件对象后, createEvent就将与进程相关的句柄返回给事件对象。

createEvent就将与进程相关的句柄返回给事件对象。

其他进程中的线程可以获得对该对象的访问权,方法是使用在pszName参数中传递的相同值(命名对象),使用继承性,使用DuplicateHandle函数等来调用CreateEvent,或者调用OpenEvent ,在pszName参数中设定一个与调用CreateEvent时设定的名字相匹配的名字:

HANDLEOpenEvent(
DWORD fdwAccess,
BOOL fInherit,
PCTSTR pszName
);


与所有情况中一样,当不再需要事件内核对象时,应该调用CloseHandle函数。

如果这里使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用SetEvent之后,系统只允许一个辅助线程变成可调度状态。同样,也无法保证系统将使哪个线程变为可调度状态。其余两个辅助线程将继续等待。

已经变为可调度状态的线程拥有对内存块的独占访问权。让我们重新编写线程的函数,使得每个函数在返回前调用SetEvent函数(就像WinMain函数所做的那样)。这些线程函数现在变成下面的形式:

DWORD WINAPI WordCount(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}

DWORD WINAPI SpellCheck(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}

DWORD WINAPI GrammarCheck(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}


当线程完成它对数据的专门传递时,它就调用SetEvent函数,该函数允许系统使得两个正在等待的线程中的一个成为可调度线程。同样,我们不知道系统将选择哪个线程作为可调度线程,但是该线程将进行它自己的对内存块的专门传递。当该线程完成操作时,它也将调用SetEvent函数,使第三个即最后一个线程进行它自己的对内存块的传递。注意,当使用自动重置事件时,如果每个辅助线程均以读/写方式访问内存块,那么就不会产生任何问题,这些线程将不再被要求将数据视为只读数据。

BOOLPulseEvent(HANDLE hEvent);

PulseEvent函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用SetEvent后又立即调用ResetEvent函数一样。如果在人工重置的事件上调用PulseEvent函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用PulseEvent函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用。

PulseEvent函数并不非常有用(可以先不管它)。

孙鑫笔记的一个例子

#include <windows.h>
#include <iostream.h>
//首先做两个线程,实现两个线程间的同步上次是利用互斥对象实现线程间的同步CreateMutex函数,这次利用事件对象实现线程间的同步CreateEvent
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

int tickets = 100;
HANDLE g_hEvent;                                                 //1.定义一个事件句柄

int main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
hThread2=CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
//2.创建人工重制的匿名的事件对象,第一个是设置安全性,这里不管,
//  第二个是BOOL类的设置事件的类型(人工重制TRUE,自动重置FALSE),
//  第三是初始化(TRUE(已通知状态) FALSE(未通知状态)。第四个是给事件
//  取名,
//  类似如互斥对象的,取名之后就只能开取一个实例了

//  g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);             // 3.设置为自动重置
g_hEvent=CreateEvent(NULL, FALSE, FALSE, "tickets");             // 给这个对象取名叫tickets
if (g_hEvent)                                                  // 判断对象是否存在
{
if (ERROR_ALREADY_EXISTS == GetLastError())               // 当程序检测到当前的对象与你打开的实例对象相同的时候
// 就会返回ERROR_ALREADY_EXISTS,如果接收到这个信息就说明你将
// 打开一个已经打开的实例
{
cout << "不能打开一个实例!" << endl;
return 0;                                              // 直接返回,程序不往下执行
}

}
SetEvent(g_hEvent);        //  4.设置为已通知状态有一个线程可以调用
Sleep(10000);

CloseHandle(g_hEvent);
return 0;
}

DWORD WINAPI Fun1Proc(LPVOID lpParameter)                      //如果选择人工重制的事件对象,
//当这个事件处于有信号状态的时候所有等待该信号的线程都可以调用,
//所以这样是不行的
// 事件对象和互斥对象差不多,都是谁拥有谁释放,
// 但是由于它自身的特性,只要对象处于有信号状态
// 所有的线程都可以访问,所以才不能用人工重制
{
while (TRUE)
{
//5.线程等待事件信号,线程执行
WaitForSingleObject(g_hEvent,INFINITE);//等待事件对象           //设置成自动重制之后发现只有一个线程运行
    // 且只运行一次就中止线程,这是因为当自动重制的事件对象被通知时,
// 只有一个等待该事件的线程变为可调度线程,
// 这里就是线程变为可调度的,但是线程的WaitForSingleObject
// 得到信号候往下执行,系统会将该信号又设置为,这是系统维护的
//线程睡眠到线程的时候得不到信号就等待,再到线程执行玩,
// 之后就没有信号了,线程.2都等待一直到主线程睡眠事件到了,
// 然后结束进程,我们可以在线程执行完后将事件对象设置为有信号
if (tickets > 0)
{
Sleep( 1 );
cout << "Thread1 sell tickets:" << tickets-- << endl;
}
else
{
break;
}
SetEvent(g_hEvent);                                        //6.线程运行完之后发生等待成功副作用将事件对象设置为未通知信号
}

return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

while(TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE);                   //7.线程等待事件信号,等待到线程可调度执行
if(tickets > 0)
{
Sleep( 1 );
cout << "Thread2 sell tickets:" << tickets-- << endl;
}
else
{
break;
}
SetEvent(g_hEvent);                                           // 8.线程运行完之后发生等待成功副作用将事件对象
// 设置为未通知信号。这样就解决了问题,
// 一定要注意区分是人工重制的还是自动重制的
// 自动重制的事件对象能构实现只允许一个线程访问共有资源,
// 就是它在把信号交给一个线程之后就将信号设置为空,
// 其他的线程得不到信号就无法在该线程执行的时候访问资源了,
// 但是在该线程执行完保护的代码之后,我们一定要
// 将信号重新设置为有信号状态。
}
return 0;                                                       // 下面来看看命名的事件对象只能创建一个实例

}


FangSH 2011-01-01
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: