使用互斥锁和信号量处理共享数据

时间:2012-03-01 15:32:55

标签: mutex semaphore

我希望有人解决我对此话题的困惑。这可能听起来很简单,但我真的很困惑。

在生产者/消费者问题中,我使用了4-semaphore解决方案。我为每个关键部分使用了不同的锁。 说,

Pseudo code of producer:
    wait(slot) // counting sem
       wait(mutex1) // binary sem
         rear <-- rear + 1
         buffer[rear] <-- item
       signal (mutex1)
    signal(items)

我使用的地方,“mutex2”作为我的消费者的第二个Mutex,作为制作人的“mutex1”。

现在,我的问题是。如果我的生产者和消费者没有使用缓冲区(后部和前部)但使用堆栈,那么只有他们可以操纵[顶部]。我需要使用一个互斥锁或两个不同的锁,就像我的4信号量一样,以确保相互排斥。

 Pseudo code of consumer with stack:
         wait (message)
            wait (mutex)
              getspace <-- stack[top]
              top – 1
            signal (mutex)
          signal (slot)

就我个人而言,我认为我需要为这两个程序提供一个锁,因此我确保生产者和消费者都不会同时访问top。但我不确定。 谢谢。

1 个答案:

答案 0 :(得分:2)

我并非100%确定我遵循您的伪代码,但我会尽力解释如何使用信号量来管理生产者 - 消费者流程中的堆栈。

当您有一个跨多个线程访问的堆栈时,您需要在访问数据时锁定它,或者更具体地说,在推送和弹出它时。 (这始终是生产者 - 消费者问题的基本假设。)

我们首先定义一个用于锁定堆栈的互斥锁。

全球流程信号量声明

stackAccessMutex = semaphore(1) # The "(1)" is the count 
                                # initializer for the semaphore.

接下来,当我们在Consumer和Producer线程中添加或删除数据时,我们需要锁定它。

制作人线程

dataPushBuff #Buffer containing data to be pushed to the stack.

…dataPushBuff is assigned…

stackAccessMutex.wait()
    stack.push(dataPushBuff)
stackAccessMutex.signal()

消费者话题

dataRecvBuff = nil # Defining a variable to store the pushed
                   # content, accessible from only within 
                   # the Consumer thread.

stackAccessMutex.wait()
    dataRecvBuff = stack.pop()
stackAccessMutex.signal()

…Consume dataRecvBuff as needed since it's removed from the stack…

到目前为止,一切都很顺利。 Producer将仅在需要时锁定堆栈。消费者也是如此。我们不应该需要另一个信号量吗?正确?不,错!

上面的场景做了一个致命的假设 - 在弹出之前,堆栈将始终用数据初始化。如果消费者线程在生产者线程有机会弹出任何数据之前执行,您将在消费者线程中生成错误,因为stack.pop()不会返回任何内容!为了解决这个问题,我们需要向消费者发出数据在堆栈中可用的信号。

首先,我们需要定义一个信号量,用于表示堆栈中的数据是否存在。

全球工艺信号声明,版本#2

stackAccessMutex = semaphore(1)
itemsInStack     = semaphore(0)

我们将itemsInStack初始化为堆栈中的项目数,即0 (参见1)

接下来,我们需要在Producer和Consumer线程中实现新的信号量。首先,我们需要生成一个已添加项目的生产者信号。我们现在更新制作人。

制作人线程,版本#2

dataPushBuff

…dataPushBuff is assigned…

stackAccessMutex.wait()
    stack.push(dataPushBuff)
stackAccessMutex.signal()
itemInStack.signal() #Signal the Consumer, we have data in the stack!
                     #Note, this call can be placed within the 
                     #stackAccessMutex locking block, but it doesn't 
                     #have to be there.  As a matter of convention, any
                     #code that can be executed outside of a lock, 
                     #should be executed outside of the lock.

现在我们可以通过信号量检查堆栈中是否有数据,让我们重新编写Consumer线程。

消费者帖子,版本#2

dataRecvBuff = nil # Defining a variable to store the pushed
                   # content, accessible from only within 
                   # the Consumer thread.

itemsInStack.wait()
stackAccessMutex.wait()
    dataRecvBuff = stack.pop()
stackAccessMutex.signal()

…Consume dataRecvBuff as needed since it's removed from the stack…

......就是这样。正如您所看到的,有两个信号量,两者都是必需的(参见2),因为我们需要在访问时锁定我们的堆栈,我们需要在数据时向消费者发出信号可用并在堆栈中没有任何内容时将其锁定。

希望能回答你的问题。如果您有任何具体问题,我会更新我的回复。

  1. 理论上,当过程开始时,你可以 使用数据预先初始化堆栈。在这种情况下,你可以 使用值初始化您的itemsInStack信号量 等于堆栈数。但是,在这个例子的情况下,我们 假设堆栈中没有数据,也没有数据 初始化。

  2. 值得一提的是,根据具体情况你 理论上可以仅使用stackAccessMutex。 考虑堆栈始终包含数据的情况。如果 堆栈是无限的,我们不需要向消费者发信号通知该数据 已添加,因为总会有数据。但是,在 现实中“无限叠”不存在。即使那应该是 在当前上下文中的情况下,添加没有任何开销 itemsInStack信号量的安全网。

    此外,可能很容易抛弃itemsInStack计数 信号量如果在你目前的情况下打电话给 如果不返回任何错误,stack.pop()不会导致任何错误 空堆栈上的数据。

    这似乎是合理的,但不推荐。假设Consumer线程正在执行 循环代码,循环将连续执行堆栈消耗代码 没有数据可供使用。通过使用itemsInStack信号量,您正在暂停 线程,直到数据到达,这应该节省几个CPU周期。