第4章 同步控制 Synchronization —-事件(Event Objects)

    Win32 中最具弹性的同步机制就属 events 对象了。Event 对象是一种核心对象,它的唯一目的就是成为激发状态或未激发状态。这两种状态全由程序来控制,不会成为 Wait...() 函数的副作用。
    Event 对象之所以有大用途,正是因为它们的状态完全在你掌控之下。Mutexes 和 semaphores 就不一样了, 它们的状态会因为诸如WaitForSingleObject() 之类的函数调用而变化。所以,你可以精确告诉一个event 对象做什么事,以及什么时候去做。
    Event 对象被运用在多种类型的高级 I/O 操作中。你可以在第6章看到一些例子。Event 对象也可以用来设计你自己的同步对象。
    为了产生一个 event 对象,你必须调用 CreateEvent():
HANDLE CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,
    BOOL bManualReset,
    BOOL bInitialState,
    LPCTSTR lpName
);
参数
    lpEventAttributes     安全属性。NULL 表示使用默认属性。该属性在Windows 95 中会被忽略。
    bManualReset         如为 FALSE,表示这个 event 将在变成激发状态(因而唤醒一个线程)之后,自动重置(reset)为非激发状态。如果是 TRUE,表示不会自动重置,必须靠程序操作(调用 ResetEvent())才能将激发状态的 event 重置为非激发状态。
    bInitialState         如为 TRUE,表示这个 event 一开始处于激发状态。如为 FALSE,则表示这个 event 一开始处于非激发状态。
    lpName Event         对象的名称。任何线程或进程都可以根据这个文字名称,使用这一 event 对象。
返回值
    如果调用成功,会传回个 event handle,GetLastError() 会传回 0。如果lpName 所指定的 event 对象已经存在,CreateEvent() 传回的是该 event handle , 而不会产生一个新的。这时候 GetLastError() 会传回ERROR_ALREADY_EXISTS。如果 CreateEvent() 失败,传回的是 NULL,GetLastError() 可以获得更进一步的失败信息。
    SetEvent()         把 event 对象设为激发状态
    ResetEvent()         把 event 对象设为非激发状态(译注:在此我要提醒读者,"Reset" 的意思是“设定为非激发状态”,而非“重新设定为激发状态”。)
    PulseEvent()         如果是一个 Manual Reset Event:把 event 对象设为激发状态,唤醒“所有”等待中的线程,然后 event 恢复为非激发状态。如果是一个 Auto Reset Event:把 event 对象设为激发状态,唤醒“一个”等待中的线程,然后 event 恢复为非激发状态
    如果你选择一个新的 event 类型(若不是“Automatic”就是“Manual”),原有的 event 对象和线程统统会被摧毁,程序重新产生出新的 event 和新的线程。
    重点:操作系统会强迫让等待中的线程有轮番更替的机会。对于本章介绍的所有同步机制,这都是一个重要的行为。如果操作系统没有强迫实现某种层次的公平性,可能会有某个线程不断获得执行机会,而某个线程一直未能获得 CPU 的青睐。这种情况被称为starvation(饥饿)。
    如果你面对一个 AutoReset event 对象调用 SetEvent() 或 PulseEvent(),而彼时并没有任何线程正在等待,会怎样?EVENTTST 程序并没有实地论证这一点。这种情况下这个 event 会被遗失。换句话说,除非有线程正在等待,否则 event 不会被保存下来。这样的行为使得“要求苏醒”的请求颇容易被遗失掉。例如,线程A累加一个计数器,之后调用 WaitForSingleObject() 等待一个 event 对象。如果在这些动作之间发生 context switch,线程B起而执行,它检查计数器内容然后对着同一个 event 对象调用 PulseEvent()。这时候这个“要求苏醒”的请求会遗失掉,因为这个 pulse 不会被储存起来(译注:因为还没有线程处于等待状态嘛)。
    另一种情况可能会引起死锁。假设“receiver”线程检查队列中是否有字符,这时候发生 context switch,切换到“sender”线程,它对一个 event 对象进行 pulse 操作,这时候又发生 context switch,回到 receiver 线程,调用WaitForSingleObject(),等待 event 对象。由于这个动作发生在 sender 线程激发 event 之后,所以 event 会遗失,于是 receiver 永远不会醒来,程序进入死锁状态。这正是 semaphore 之所以被创造用以解决问题的地方。
从 Worker 线程中显示输出
    此刻我想先打个岔,请各位看看 EVENTTST 如何让 worker 线程(那三个等待中的线程)把字符串放到列表框(listbox)中。列表框的消息循环总是被单一线程(也就是程序的主线程)掌管,虽然这并非绝对必要,但是让主线程负责所有的屏幕更新工作,是相当理想的。
    我在程序中定义了一个消息,名为 WM_PLEASE_UPDATE。当 worker线程认为需要把一笔新的项目放到列表框中时,就送这个消息给主线程。Worker 线程使用 SendMessage() 完成这件事情,以便制造出一种“函数调用”的效果。在主线程处理完毕该消息之前,SendMessage() 不会返回,所以我们可以保证所有的输出有条不紊,不至于乱了次序。
    请注意,我一直仰赖一件事实:所有的数据可以被所有的线程取用。我使用sprintf() 在线程的堆栈中产生一个字符串,然后将此字符串地址以 SendMessage()送出。主线程在更新列表框的画面时,即使用到这个地址,一旦主线程完成这个消息的处理,SendMessage() 便返回,worker 线程于是继续进行下去。
    想象一下,如果我以 PostMessage() 代替 SendMessage(),会发生什么情况?由于 PostMessage() 会立刻返回,所以当主线程抓取字符串内容要显示时,或许该字符串内容早已又被 worker 线程改写了。这就是多线程序设计中最常见的一种两难取舍:在最佳速度和最佳安全性之间取舍。在这里我宁愿选择比较慢但是比较安全的做法。

原文链接: https://www.cnblogs.com/azbane/p/7561262.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第4章   同步控制 Synchronization    ----事件(Event Objects)

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/399210

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月12日 上午10:01
下一篇 2023年4月12日 上午10:01

相关推荐