【问题标题】:Multiple threads waiting for same semaphore多个线程等待相同的信号量
【发布时间】:2017-12-21 03:55:20
【问题描述】:

假设有 5 个线程在等待一个信号量

CreateSemaphore(sem_bridgempty,0,1,INFINITE);
WaitForSingleObject(sem_bridgempty, INFINITE);

现在,当 sem_bridgeempty 发出信号时,5 个线程中的一个将被唤醒,其余线程将再次等待 sem_bridgeempty 发出信号。我在这里吗?

我正在实施一个车道桥梁问题,一次只能有车辆从一个方向移动。桥梁的容量也固定为 5。到目前为止我所做的是

unsigned WINAPI enter(void *param)
{
    int direction = *((int *)param);
    while (1)
    {
        WaitForSingleObject(sem_bridgecount, INFINITE);
        WaitForSingleObject(mut_mutex, INFINITE);
        if (curr_direction == -1 || direction == curr_direction)
        {
            curr_direction = direction;
            cars_count++;
            std::cout << "Car with direction " << direction << " entered " << GetCurrentThreadId() << std::endl;
            ReleaseMutex(mut_mutex);
            break;
        }
        else
        {
            ReleaseMutex(mut_mutex);
            WaitForSingleObject(sem_bridgempty, INFINITE);
        }
    }
    Sleep(5000);
    exit1(NULL);
    return 0;
}

 unsigned WINAPI exit1(void *param)
{   
    WaitForSingleObject(mut_mutex, INFINITE);

    cars_count--;
    std::cout << "A Car exited " << GetCurrentThreadId() << std::endl;
    ReleaseSemaphore(sem_bridgecount, 1, NULL);
    if (cars_count == 0)
    {
        curr_direction = -1;
        std::cout << "Bridge is empty " << GetCurrentThreadId() << std::endl;
        ReleaseSemaphore(sem_bridgempty, 1, NULL);
    }
    ReleaseMutex(mut_mutex);
    return 0;
}

int main()
{
    sem_bridgecount = CreateSemaphore(NULL, 5, 5, NULL);
    sem_bridgempty = CreateSemaphore(NULL, 0, 1, NULL); 
    mut_mutex = CreateMutex(NULL, false, NULL);
    //create threads here
}

考虑以下部分

    else
    {
        ReleaseMutex(mut_mutex);
        WaitForSingleObject(sem_bridgempty, INFINITE);

一辆车正朝 1 方向行驶。现在有 3 个方向 2 的进入请求。所有 3 个都将在WaitForSingleObject(sem_bridgempty, INFINITE); 被阻止。现在当桥空了。三个中的一个将被接走。被选中的那个up 将再次使桥非空。即使方向相同,其他两个仍将等待桥空。 所以即使桥上有direction=2的车,其他同方向的车还在等sem_bridgempty。 我什至想过使用sem_bridgempty 作为事件而不是semaphore(setevent() 在cars_count=0resetevent()enter()resetevent() 当第一辆车进入时)。但仍然所有线程都不会唤醒起来。

【问题讨论】:

  • 您不使用std::condition_variable的任何特殊原因?
  • 没有特别的原因。但是我必须使用Windows同步功能来做到这一点,所以我想我可以尝试sleepconditionvariablecs(),但是我也必须使用EnterCriticalSection()。这可以使用信号量来完成吗和互斥锁?我对这里的最佳方法感兴趣,即使我必须使用其他东西。
  • 所选解决方案逻辑存在问题。你怎么看它不是最好的。试试this code
  • CreateSemaphore 的参数不正确,丢弃返回值是不正确的,我希望你不要在每个线程中调用它。

标签: c++ windows multithreading visual-c++ semaphore


【解决方案1】:

最简洁的选择是使用临界区和条件变量。

ENTER 算法如下所示:

  • 声明临界区。
  • 在循环中调用 SleepConditionVariableCS,如Using Condition Variables 所示,直到:
    • 交通正向正确的方向行驶,桥梁还有剩余容量,或者
    • 桥是空的。
  • 更新状态以表示您的汽车进入桥梁。
  • 释放关键部分。

EXIT 算法如下所示:

  • 声明临界区。
  • 更新状态以表示您的汽车离开桥梁。
  • 释放关键部分。
  • 调用 WakeConditionVariable。

条件变量可以是一个整数,其大小表示桥上汽车的数量,其符号表示行进方向。


如果您想避免条件变量,我能想出的最简单的解决方案需要一个临界区和三个自动重置事件:每个行进方向一个,加上一个表示桥是空的。您还需要一个表示桥上汽车数量的变量。

ENTER 算法如下所示:

  • 使用 WaitForMultipleObjects,声明与您的行进方向相对应的事件或与空桥相对应的事件,以先可用者为准。
  • 进入临界区。
  • 增加计数以表示您的汽车进入桥梁。
  • 如果计数未满,请设置代表您行进方向的事件。
  • 离开临界区。

EXIT 算法如下所示:

  • 进入临界区。
  • 减少计数以表示您的汽车离开桥梁。
  • 如果计数为零,则设置表示桥为空的事件。
  • 如果计数不为零,请根据您的行进方向设置事件。
  • 释放关键部分。

【讨论】:

    【解决方案2】:

    需要创建最符合任务的对象。在当前任务中 - 我们有 2 个队列 - 在两个方向上。从某种意义上说,这两个队列都是先进先出的。我们需要能够准确地唤醒队列中的条目数——不仅仅是一个或全部。 windows的信号量正好对应这个。这是 FIFO 队列,通过调用 ReleaseSemaphore,我们可以准确设置要唤醒的线程(条目)数量 - 这是 api lReleaseCount 的第二个参数。万一事件或条件变量我们只能唤醒单个或所有服务员。

    您的错误不在于您选择了信号量 - 这是此任务的最佳选择。你误认为你选择了错误的本质 - sem_bridgecount,sem_bridgempty - 这根本不是按顺序排列的。您需要 2 个信号量用于 2 个方向 - HANDLE _hSemaphore[2]; - 每个方向一个信号量 - 将其创建为 _hSemaphore[0] = CreateSemaphore(0, 0, MAXLONG, 0) - 初始计数为 0(!),最大计数无限制(但可以选择任何值 >= 5)。当汽车尝试进入 direction 桥接但不能,因为现在另一个 direction 处于活动状态或桥接上没有可用空间 - 它必须等待信号量(在 FIFO 队列中)@ 987654325@。并且当汽车从桥上离开时 - 他需要检查桥上的当前情况并唤醒一个或另一个 方向 上的一些确切的汽车计数 (n) (不是全部或单个) - 所以拨打ReleaseSemaphore(_hSemaphore[direction], n, 0);

    一般:

    void enter(int direction)
    {
      EnterCriticalSection(..);
      BOOL IsNeedWait = fn(direction);
      LeaveCriticalSection(..);
      if (IsNeedWait) WaitForSingleObject(_hSemaphore[direction], INFINITE)
    }
    

    void exit(int direction)
    {
      EnterCriticalSection(..);
      direction = calc_new(direction);
      if (int WakeCount = calc_wake_count(direction))
      {
        ReleaseSemaphore(_hSemaphore[direction], WakeCount, 0);
      }
      LeaveCriticalSection(..);
    }
    

    请注意,在每次进入 - 车仅一次进入关键部分,并在等待 _hSemaphore[direction] 之后,它只是进入桥而不再次进入 cs 并检查条件。这是因为我们可以准确计算 exit 中的汽车数量(不是单个或全部)和方向 - 并且只唤醒必须进入桥梁的汽车,如果使用事件或条件变量,这将是不可能的

    尽管有条件变量和 CS 的解决方案是可能的,但我认为这不是最好的,因为: 在 SleepConditionVariableCS 中等待后的线程 - 再次进入绝对不需要的 cs 我们需要或只唤醒一辆车 WakeConditionVariable 当真正可以多辆车进入桥时,或唤醒所有 WakeAllConditionVariable 但在这种情况下,并发的几个线程再次尝试进入同一个 cs,只有一个会赢,另一个会在这里等待 等待线程的数量可能超过桥上的最大位置(在您的情况下为 5 个) - 并且某些线程将需要在循环中再次开始等待。 如果正确使用信号量,这一切都可以避免

    完整的工作实现here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-23
      • 2011-12-08
      • 1970-01-01
      相关资源
      最近更新 更多