65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 Win32 同步对象确保单例执行

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.85/5 (6投票s)

2008年8月12日

CPOL

4分钟阅读

viewsIcon

22031

downloadIcon

249

同步对象句柄:实现单例运行的另一种机制。

引言

有很多场景需要单例运行,这意味着强制只有一个程序实例在运行。传统上,有几种技术可以防止加载第二个实例。列举一些:

  1. 使用全局静态标志
  2. 检查窗口标题
  3. 创建文件锁

本文将探讨 Windows 平台上的另一种机制,使用同步对象,即事件、互斥体和信号量,来保证程序的单例运行。

背景

虽然互斥体/信号量/事件最初是为解决多线程程序中的竞态条件和死锁问题而构思的,但它们在 Windows 上的创建具有一个固有的属性:任何命名对象在相同条件下只能在全局命名空间中创建一次。这个全局唯一的句柄(尽管 Win32 提供了复制句柄的 API)相当于一个建议性锁。当所有观察方都遵守该锁时,它基本上可以作为第一个运行实例的明确衡量标准。

Using the Code

互斥体、信号量和事件分别有三对 CPP 和头文件。将其中一对与基类一起包含到您的项目中,您将获得程序单例运行的效果。

附带的测试套件提供了如何使用这些实用类的一个示例。

CSingleton *pSingleton = NULL;
switch( type )
{
    case eMutex:
      pSingleton = new CMutexSingleton(MUTEX_NAME, NULL);
      if( pSingleton )
      {
          if( pSingleton->IsExclusiveRun() )
           {
              bWait = true;
               printf("Running (mutex singleton)");
           }
           else
           {
               printf("another instance (mutex) is running, let's go home\n");
           }
       }
       break;

关注点

深入讨论同步需要大量的文字。本文侧重于一个方面:同步对象的创建,然后将对象句柄引向我们感兴趣的领域:单例运行。一路上,我将尝试用通俗易懂的语言回答一些让非英语或非 C++ 母语者感到困惑的相关问题(作者承认自己也是其中之一:-))

  1. 什么是信号?互斥体的信号与信号量或事件的信号相同吗?
  2. 互斥体发出信号后会发生什么?
  3. 同理,这些对象的取消信号又如何?
  4. 我可以使用临界段来实现与互斥体/信号量/事件相同的结果吗?

在多个在线或印刷来源中,我认为 Julian Templeman 对信号和取消信号的解释最好。在他的书《Windows NT 编程入门》第 222 页,有一个表格列出了各种对象类型以及“信号”的相应含义。

已信号的互斥体意味着它被任何进程拥有。换句话说,已信号的互斥体是可以被获取的。违反直觉?这就是它的结构方式。当信号量发出信号时,意味着它的计数大于零。普遍的说法是信号量是 n 路互斥体。换句话说,互斥体是信号量的一个特例,其计数为 1。Julian 指出了计数为 1 的信号量与互斥体之间的一个细微差别;那就是,“在 Win32 中,计数为 1 的信号量没有 WAIT_ABANDONED 返回值”。当有人稍后在计数为 1 的信号量上调用 WaitForXXXX 时,这种晦涩的行为很重要。不要指望在该信号量句柄上获得 WAIT_ABANDONED

在这三种同步对象中,事件信号/取消信号的操作最为棘手。

通过标准的 Win32 CreateEvent 创建事件后,需要调用以下函数之一:

  • SetEvent()
  • ResetEvent()
  • PulseEvent()

由于事件可以手动或自动创建,并且其初始状态可以是已信号或未信号,让我们检查上述哪个调用用于正确设置事件。

对于自动事件:SetEvent()PulseEvent 具有相同的效果,它们将事件设置为已信号,然后返回到未信号状态,唤醒单个等待线程(《Windows NT 编程入门》第 251 页)。

对于手动事件:SetEvent() 将事件设置为已信号并保持在那里;ResetEvent() 将事件设置为未信号并保持在那里;PulseEvent() 将事件设置为已信号,然后返回到未信号状态,唤醒所有等待线程(来源:同上)。

总之,如果您想创建一个事件并一直持有它,请创建一个手动事件并对其句柄调用 ResetEvent()

取消信号具有相反的效果:取消互斥体的信号意味着它不再可用,而取消信号量的信号表示其计数已返回到 0。最后,取消事件的信号意味着它已从其先前状态中取消设置。

临界段是另一种广泛使用的同步机制。与上述三种不同,它仅仅是一个结构,而不是一个内核对象,在创建过程中可见。本文最终的目标是在每个机器的基础上实现单例运行。因此,临界段不适合在机器范围的全局命名空间中完成此任务。

历史

  • 首次提交:2008 年 8 月 11 日。
© . All rights reserved.