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

Windows核心编程--用内核对象进行线程同步(一)

2013-03-26 21:43 351 查看
用内核对象进行线程同步(一)
无论是进程还是线程都有一个内核对象。我们可以通过检查进程对象的布尔值就可以知道进程是否在运行。线程内核对象在创建的时候处于未触发状态,当线程终止时候,操作系统会自动将线程对象的状态改为已触发。只有在已经触发的情况下,才是可以调度的。那么这就是等待。决定每个对象处于触发还是未触发状态的规则与对象的类型有关。

1、等待函数,就是使一个线程自愿进入等待状态,知道指定的内核对象被触发为止。若是调用一个等待函数,而内核对象已经处于触发状态,那么它是不会在等待状态的

使用WaitForSingleObject函数,在函数中的两个参数分别代表的是等待的内核对象以及等待的时间,等待的时间可以是指定的时间也可以是无线长的时间。也就是使用INFINITE这个标志,这个标志告诉系统线程原意一直等待,或者等到这个进程终止。这种方式,显然的会浪费CPU的时间。因为没有被触发,会一直等待着。永远不会被唤醒

那么这个函数的返回类型也有3种。被触发也就是最好的结果是WAIT_OBJECT_0,这是对象被触发了。WAIT_TIMEOUT则是超时,WAIT_FAILED为失败。可以使用WaitForMultipleObjects在调用线程时同时检查多个内核对象的触发状态。它将指出内核对象的数量,以及一个指向内核对象句柄的数组。可以决定是在一个对象被触发之后还是所有的内核对象被触发为止才返回。通过返回值我们可以知道具体被触发的对象。这是由返回类型的WAIT_OBJECT与内核对象数量决定的、

2、等待成功所引起的副作用

使得时间的状态由触发变为非触发状态。比如两个线程一相同的方式调用WaitForMulitipleObjects函数,注意第3个参数它定的是true.也就是说只有两个内核对象都被触发了才能就行调用。在等待这个的时候在我们的操作系统中,若是一个被触发了就返回true,,使得一个线程拥有这个对象,另外一个或许就会拥有另一个对象,那么这个时候就会形成死锁,互相等待对方已经占有的资源。那么在这个函数中又会是怎样的处理的呢,它发现一个已经被触发了,系统是不会唤醒其中任何一个的,只有两个内核对象都被触发了,才会唤醒其中一个,然后会将两个内核对象(这里的内核对象指的是事件内核对象)就会重新的设置为非触发,那么另外一个线程势必要继续的等待。也就是说它会改变事件对象的状态。或许我们会有这样的疑问,就是具体的调用哪一个呢,也许会基于优先级,但是也有的会根据等待时间的长短,那么这样的调度算法的好坏就直接的影响到了性能。不好的算法就会导致频繁的挂起和恢复。

3、事件内核对象

上面我们在等待成功引起的副作用中提到事件内核对象。那么具体的什么是...?事件常用的地方:一个进行初始化,一个进行操作的两个线程函数,只有在初始化结束之后我们才可以进行处理,那么这个时候使用一个事件对象。初始化结束后,触发事件,也许我们在想为什么不直接在那个线程结束后之前进行创建一个新的线程呢??

使用CreateEvent函数创建一个事件内核对象,在创建的时候可以指定是手动的还是自动的。所谓自动就是在线程成功等到自动重置对象的时候会自动的将事件对象置为未触发状态。因此在自动重置对象中是不需要调用ResetWvent函数的。(ResetEvent函数将时间变成未触发状态)。所以应该有下面的操作:

当使用WaitForSingleObject函数时会等到事件对象为已触发状态,这个时候在函数返回之前会将事件设置为未触发状态,这样子,其它的线程就不可访问。记住在调用WaitForSingleObject函数的函数体最后要进行SetEvent函数,将事件对象置为已触发状态。这个时候事件内核对象中的计数是否也是相应的增减??而且自动重置对象在被触发的时候只有一个正在等待该事件的线程会变为可调度状态,也就是说我们下面的程序中

g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);//创建一个手动重置的对象并将这个事件对象放在一个全局变量中

DWORD dwThreadID;

HANDLE hThrasd[3];

hThrasd[0]=_beginthreadex(NULL,0,WordCount,NULL,0,&dwThreadID);

hThrasd[1]=_beginthreadex(NULL,0,SpellCheck,NULL,0,&dwThreadID);

hThrasd[1]=_beginthreadex(NULL,0,Gramer,NULL,0,&dwThreadID);

DWORD WINAPI WordCount(PVOID pvParam)

{

WaitForSingleObject(g_hEvent,INFINITE);

return 0;

}

DWORD WINAPI SpellCheck(PVOID pvParam)

{

WaitForSingleObject(g_hEvent,INFINITE);

return 0;

}

DWORD WINAPI Gramer(PVOID pvParam)

{

WaitForSingleObject(g_hEvent,INFINITE);

return 0;

}

若是手动的话,在事件被触发的时候,正在等待这个事件的所有线程都将变成可调度状态,比如上面这3个程序,也就是说,尽管他们3个都是在等待同一个事件对象,但是是可以同时运行的,我们会想,这样子不会产生错误,是的,在操作系统中,这种通过手动进行重置的事件对象是以只读的形式访问的。但是在自动重置事件对象中是不一样的。它只能让线程中的一个成为可调度状态。其而且系统不能保证会调用哪一个。它的在等待着,那么也许我们可以在函数中进行调用SetEvent这个函数。使其变为已触发状态,这样其它的线程就可以继续的运行。

SetEvent函数将事件变为触发状态。

所以系统是什么时候将时间重置的呢,这种自动重置的时机在哪里??

1)下面我们使用两个事件对象来处理这样的一个问题,接受一个请求字符串,然后将颠倒字符串的顺序,最后将结果放在文本框中。可能我们会使用单线程的做法,但是使用多线程会提升我们的效率,一个线程主要负责对数据的处理,一个线程主要负责显示数据,这就等于说在主线程之外在创建一个线程。这其中处理的数据都放在了一个共享的缓存中。如果不采用这种方式直接的采用单线程方式,若程序中处理的数据足够大,那么程序会来不及处理,而且很容易出错。共享的内存必须进行互斥操作。必须在获得共享区的数据之后才能触发请求。可否采用在一个线程结束的函数返回之前再创建一个新的线程对数据进行处理,处理之后再进行PostMessage呢?这种方式同样存在着互斥的现象要进行处理。

所以总的思路就是在主线程中输入,完成之后触发发送事件,在另一个线程等待这个事件发生的函数中接收到这个消息开始处理数据,数据处理之后触发返回结果事件,在主线程中等待结果返回事件中接受到这个消息,开始处理。

4、可等待的计时器内核对象

可等待的计时器会在某个指定的时间触发,或每隔一段时间触发,它也可以是手动的或者是人工重置的,但是它的初始态是未触发的,一之前的事件内核对象不同,它不可以在创建的时间就指定了是未触发的还是已经触发的。当然我们可以使用SetWaitableTimer来触发计时器。创建可等待的计时器对象为CreateWaitableTimer函数。这种函数的作用就是在某个时间执行一些操作。

IiPeriod参数告诉系统计时器的触发频度。第二个参数指明第一次调用的时间。一次性的计时器可以将iPerod的参数传0.然后调用CloseHandle来关闭计时器,或者调用SetWaitableTimerl来重置计时器,给它设置一个新的触发时间,CancleWaitableTimer可以将句柄所标志的计时器取消。这样计时器就永远不会触发。这种效果与一次性调用计时器的效果一样,若以后再调用SetWaitableTimer还是可以进行重置。但是我的问题是这个是关闭了句柄了??

最后注意这个与SetTimer的比较

1)在SetTimer中,我们将会消耗更多的资源,因为使用用户计时器需要在应哟个程序中使用大量的用户界面基础设施。

2)可等待计时器是内核对象,这样也就是说他可以在多个线程间共享,同时是具备安全性的。

3)通常在使用SetTimer时我们是产生了一个WM_TIMER的消息,这个消息会被回调到SetTimer的线程中,或者会送回到窗口的线程中(对于基于窗口的计时器)。因此只有一个线程会得到通知,我们要是在多个线程中处理就不得不采用设置多个这样的计时器。相反,多个线程可以等待可等待计时器,若是手动重置计时器,会有多个线程可以变成可调度状态

4)但是若是在用户界面上,既要等待消息又要等待内核对象,这个时候还是使用SetTimer比较的好。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: