【问题标题】:Single lane bridge Synchronization using semaphores使用信号量的单通道桥接同步
【发布时间】:2017-07-16 06:09:54
【问题描述】:

我正在尝试实现单车道桥同步问题。 一次,汽车只能单向行驶,而且桥的容量是5。我想出了下面的东西。

int curr_direction = -1;
//curr_direction values can be -1,1 and 2.-1 means bridge is empty
int cars_count = 0; 
HANDLE sem_bridgempty;//To keep track whether the bridge is empty or not
HANDLE sem_bridgecount; //To keep track of count of cars on bridge
HANDLE mut_mutex;//For exclusive access
unsigned WINAPI enter(void *param) 
{
    int direction = *((int *)param);
    WaitForSingleObject(mut_mutex,INFINITE);
    if (curr_direction == -1)
        curr_direction = direction;
    ReleaseMutex(mut_mutex);
    WaitForSingleObject(sem_bridgecount, INFINITE);//Wait if more than 5 cars
    WaitForSingleObject(mut_mutex, INFINITE);
    if (direction == curr_direction)
    {
        cars_count++;
        std::cout << "Car with direction " << direction << " entered " << 
      GetCurrentThreadId() << std::endl;
    ReleaseMutex(mut_mutex);
    }
    else
    {
        ReleaseMutex(mut_mutex);
        WaitForSingleObject(sem_bridgempty, INFINITE);//Wait for bridge to be empty so other direction car can enter
        WaitForSingleObject(mut_mutex,INFINITE);
        curr_direction = direction;
        cars_count++;
        std::cout << "Car with direction " << direction << " entered " << GetCurrentThreadId() << std::endl;
        ReleaseMutex(mut_mutex);
    }
    return 0;
}
unsigned WINAPI exit(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);
sem_bridge_not_empty = CreateSemaphore(NULL, 0, 2, NULL);
mut_mutex = CreateMutex(NULL, false, NULL);

同步不起作用。当我测试这个时,我可以看到方向1 和2 的汽车同时进入。

  else
    {
        ReleaseMutex(mut_mutex);
        WaitForSingleObject(sem_bridgempty, INFINITE); //line 1
        WaitForSingleObject(mut_mutex, INFINITE);//line 2

假设方向为2 的线程1 正在等待sem_bridge_empty。 当桥变空时(direction=-1),它在第 2 行。但是在它获取mut_mutex 之前,另一个方向 = 1 的线程调用enter 并看到direction=-1 并进入。现在当控制权回来时在线程1,它也以direction=2 进入,因为它忘记了另一个线程已经进入了不同方向的事实。 我怎样才能使mut_mutex 和sem_bridge_empty 同步?

【问题讨论】:

  • exit() 应该在 enter() 函数内的线程上调用(有一些延迟来模拟过桥所需的时间),而不是从 main() 中产生的单独线程调用。既然线程模拟了你的汽车,你为什么要在其中任何一个没有通过 enter() 的线程上调用 exit()?
  • @o_weisman 非常感谢。我得到了那部分。但现在我有同步问题,我已经更新了问题。
  • 最好提出一个新问题,而不是“更新”一个旧问题。
  • 作业是否要求您使用 Semaphore?信号量非常适合限制桥上的线程(汽车)数量,但它不适合排队等待车道改变方向的线程。问题是,信号量没有提供一种可靠的方法来释放 所有 正在等待的线程。一个CountdownLatch是一个同步对象,可以一次释放所有线程,但只能使用一次。 o.notifyAll() 是另一种(更复杂的)机制,可以一次释放所有线程。

标签: c++ windows multithreading semaphore


【解决方案1】:

您的 WaitForSingleObject(mut_mutex) 与 ReleaseMutex(mut_mutex) 计数不匹配 - 您(输入)2 或 3 次调用 ReleaseMutex 以获得单个 WaitForSingleObject 这个已经很严重的错误。 if (direction == curr_direction) 已经在同步部分之外调用 - 所以 curr_direction 可以随时更改。

正确的解决方案 - 检查和修改必须是“原子”操作 - 在某些关键部分内。所以将一些 cs 与桥相关联,这将保护它的状态。当汽车尝试进入桥 - 输入一次(!)到这个 cs,决定汽车可以进入还是需要等待。退出 cs。如果需要等待 - 等待(当然在 cs 之外)。 mutex 也可以在这里使用,但使用 cs 或 SRW 锁会更好 - 因为使用 mutex 你将成为每个时间进入内核进行同步。使用 cs - 仅在真正需要等待时。

你也没有考虑下一种情况-if (direction == curr_direction)你总是进入桥接。但是如果从对面站点已经等了一些车怎么办?拳头进入桥的一侧(方向)可以无限握住它(假设这个方向有无限的车流),当另一个方向无限等待时。为了解决这个问题 - 需要添加一些“红绿灯” - 即使 carr 在当前桥方向移动并且桥上存在空闲空间 - 如果已经有汽车从对面等待,它可以停止并等待。

还请注意您如何将参数(方向)传递给线程 - 为什么通过指针而不是通过值?如果这是 c++ - 为什么不封装桥接逻辑(所有变量和同步对象都在某个结构中?

我将选择下一个解决方案(带有统计信息):

struct Bridge : CRITICAL_SECTION
{
    enum { MaxPositions = 3, MaxTransitsWhenOppositeWait = 1 };

    HANDLE _hSemaphore[2];
    ULONG _nWaitCount[2];
    ULONG _nFreePositions, _nTransits;
    LONG _direction;

    //+++ statistic for test only

    struct STAT {
        ULONG _Exits[2];
        ULONG _nWaitCount[2];
        LONG _direction;
        ULONG _CarsOnBridge;
    } _stat[16];

    ULONG _i_stat, _Exits[2], _nExits;

    void CollectStat(LONG direction)
    {
        _Exits[direction]++;

        if (!(++_nExits & 0xfff))// on every 0x1000*n exit save stat
        {
            if (_i_stat)
            {
                STAT *stat = &_stat[--_i_stat];
                stat->_CarsOnBridge = MaxPositions - _nFreePositions;
                stat->_direction = direction;
                stat->_nWaitCount[0] = _nWaitCount[0];
                stat->_nWaitCount[1] = _nWaitCount[1];
                stat->_Exits[0] = _Exits[0];
                stat->_Exits[1] = _Exits[1];
            }
        }
    }

    void DumpStat()
    {
        if (ULONG i = RTL_NUMBER_OF(_stat) - _i_stat)
        {
            do 
            {
                STAT *stat = &_stat[_i_stat++];
                DbgPrint("<(+%05u, -%05u) %c%u (+%u, -%u)>\n", stat->_Exits[0], stat->_Exits[1], 
                    stat->_direction ? '-' : '+', 
                    stat->_CarsOnBridge, stat->_nWaitCount[0], stat->_nWaitCount[1]);
            } while (--i);
        }
    }

    void InitStat()
    {
        RtlZeroMemory(_Exits, sizeof(_Exits)), _nExits = 0;
        RtlZeroMemory(_stat, sizeof(_stat)), _i_stat = RTL_NUMBER_OF(_stat);
    }

    //--- statistic for test only

    void Lock() { EnterCriticalSection(this); }

    void Unlock() { LeaveCriticalSection(this); }

    Bridge()
    {
        InitializeCriticalSection(this);

        _hSemaphore[0] = 0, _hSemaphore[1] = 0;
        _nWaitCount[0] = 0, _nWaitCount[1] = 0;

        _nFreePositions = MaxPositions, _nTransits = MaxTransitsWhenOppositeWait, _direction = -1;

        InitStat();
    }

    ~Bridge()
    {
        DeleteCriticalSection(this);

        if (_hSemaphore[1]) CloseHandle(_hSemaphore[1]);

        if (_hSemaphore[0]) CloseHandle(_hSemaphore[0]);

        if (_nWaitCount[0] || _nWaitCount[1] || _nFreePositions != MaxPositions)
        {
            __debugbreak();
        }

        DumpStat();
    }

    BOOL Create()
    {
        return (_hSemaphore[0] = CreateSemaphore(0, 0, MaxPositions, 0)) &&
            (_hSemaphore[1] = CreateSemaphore(0, 0, MaxPositions, 0));
    }

    BOOL IsOppositeSideWaiting(LONG direction)
    {
        return _nWaitCount[1 - direction];
    }

    void EnterCars(LONG direction, ULONG n)
    {
        if (IsOppositeSideWaiting(direction))
        {
            _nTransits--;
        }

        _nFreePositions -= n;
    }

    void Enter(LONG direction)
    {
        BOOL bWait = FALSE;

        Lock();

        if (_direction < 0)
        {
            _direction = direction;
            goto __m0;
        }
        else if (_direction == direction && _nFreePositions && _nTransits)
        {
__m0:
            EnterCars(direction, 1);
        }
        else
        {
            bWait = TRUE;
            _nWaitCount[direction]++;
        }

        Unlock();

        if (bWait)
        {
            if (WaitForSingleObject(_hSemaphore[direction], INFINITE) != WAIT_OBJECT_0)
            {
                __debugbreak();
            }
        }
    }

    void Exit(LONG direction)
    {
        if (_direction != direction)
        {
            __debugbreak();
        }

        Lock();

        CollectStat(direction);

        if (++_nFreePositions == MaxPositions)
        {
            // bridge is empty
            _direction = -1, _nTransits = MaxTransitsWhenOppositeWait;

            // change direction if opposite side wait
            if (IsOppositeSideWaiting(direction))
            {
                direction = 1 - direction;
            }
        }

        HANDLE hSemaphore = _hSemaphore[direction];

        ULONG n = _nTransits ? min(_nWaitCount[direction], _nFreePositions) : 0;

        if (n)
        {
            _direction = direction;
            EnterCars(direction, n);
            _nWaitCount[direction] -= n;

            ReleaseSemaphore(hSemaphore, n, 0);
        }

        Unlock();
    }

} g_Bridge;

ULONG car(LONG direction)
{
    direction &= 1;// 0 or 1

    WCHAR caption[16];

    int i = 0x10000;// Number of transits

    do 
    {
        swprintf(caption, L"[%u] %08x", direction, GetCurrentThreadId());
        //MessageBoxW(0, 0, caption, MB_OK);

        SwitchToThread();// simulate wait

        g_Bridge.Enter(direction);

        SwitchToThread();// simulate wait
        //MessageBoxW(0, 0, caption, direction ? MB_ICONWARNING : MB_ICONINFORMATION);

        g_Bridge.Exit(direction);

        direction = 1 - direction;// reverse direction

    } while (--i);

    return direction;//visible thread exit code in debug window
}

void SLBT()
{
    if (g_Bridge.Create())
    {
        HANDLE hThreads[8], *phThread = hThreads, hThread;
        ULONG n = RTL_NUMBER_OF(hThreads), m = 0;
        do 
        {
            if (hThread = CreateThread(0, PAGE_SIZE, (PTHREAD_START_ROUTINE)car, (PVOID)n, 0, 0))
            {
                *phThread++ = hThread, m++;
            }
        } while (--n);

        if (m)
        {
            WaitForMultipleObjects(m, hThreads, TRUE, INFINITE);
            do 
            {
                CloseHandle(*--phThread);
            } while (--m);
        }
    }
}

为了检查汽车如何在桥上行驶,我在每个 n*0x1000 出口收集一些统计数据。另请注意,在每次出口时,我都会检查方向是否正确:if (_direction != direction) __debugbreak();

一些统计输出:(每个方向有多少汽车经过桥,桥上有多少汽车及其方向(+/-)。现在有多少汽车在等待)

<(+32768, -32768) +3 (+2, -3)>
<(+30720, -30720) -2 (+2, -3)>
<(+28672, -28672) +3 (+2, -3)>
<(+26625, -26623) +1 (+2, -5)>
<(+24576, -24576) -3 (+3, -2)>
<(+22529, -22527) +1 (+2, -5)>
<(+20480, -20480) +2 (+3, -2)>
<(+18432, -18432) +3 (+1, -3)>
<(+16385, -16383) +1 (+2, -3)>
<(+14335, -14337) -1 (+4, -2)>
<(+12288, -12288) +3 (+2, -3)>
<(+10239, -10241) -1 (+3, -2)>
<(+08192, -08192) +2 (+3, -3)>
<(+06143, -06145) -1 (+3, -2)>
<(+04096, -04096) +3 (+2, -3)>
<(+02048, -02048) +2 (+3, -3)>

您还可以取消注释消息框并减少“手动”模式下控制车的运输次数

of corse 也可以使用MaxPositions 和MaxTransitsWhenOppositeWait 例如当enum { MaxPositions = 5, MaxTransitsWhenOppositeWait = 2 };

<(+32766, -32770) -1 (+7, -0)>
<(+30721, -30719) -5 (+0, -1)>
<(+28674, -28670) +1 (+0, -7)>
<(+26623, -26625) +5 (+2, -0)>
<(+24574, -24578) -1 (+7, -0)>
<(+22528, -22528) -5 (+0, -0)>
<(+20479, -20481) -3 (+2, -0)>
<(+18431, -18433) +5 (+2, -1)>
<(+16383, -16385) +5 (+2, -0)>
<(+14337, -14335) -5 (+0, -2)>
<(+12290, -12286) +1 (+0, -5)>
<(+10241, -10239) -5 (+0, -2)>
<(+08190, -08194) -1 (+7, -0)>
<(+06143, -06145) -2 (+3, -1)>
<(+04096, -04096) +5 (+0, -1)>
<(+02047, -02049) -3 (+1, -0)>

【讨论】:

    【解决方案2】:

    我想我会以不同的方式解决这个问题。

    我看待问题的方式是更接近地模拟现实生活。

    有一座桥。桥的每一端都有一个队列等待车辆进入。有一个代理(对应于桥头的旗手)可以从队列中释放汽车。

    想要过桥的汽车(线程)会创建一个事件并将该事件放入队列中,以便在任何方向过桥。然后它在事件上休眠。当它到达队列的最前面并且代理决定释放那辆车时,它会从队列中删除该事件并发出信号。然后汽车继续过桥。当它到达另一端时,它会通过执行 ReleaseSemaphore 通知它正在离开网桥。

    每个代理都在等待(我相信)三个因素:

    1. 方向 == 我的方向
    2. 桥上有房间
    3. 我的队列中至少有一辆车

    当这些发生时,它会从队列中释放一辆车,然后重复。

    如果您想添加一点装饰,代理可以在其队列为空时释放其剩余的时间片(或者可能仅在其空置时间超过一段时间时)。

    至少在我看来,这似乎更容易实现,而且更加现实。当桥改变方向时,我们不想只是随机选择五辆汽车让我们通过——我们总是选择那些等待时间最长的。

    如果我们想让事情变得更加真实,我们可以将其设为双端队列而不是队列。紧急车辆(救护车、消防车等)将开到双端队列的前面而不是后面。当他们到达时,向另一个方向行驶的车辆的计时器将立即到期(但他们仍会等待桥上的车辆退出后再进入)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      • 2011-04-25
      相关资源
      最近更新 更多