使用互斥锁和信号量操作共享数据



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

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

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

在我使用的地方,"mutex2"作为我的消费者的第二个互斥体,作为生产者中的"mutex1"。

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

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

就个人而言,我认为两个过程都需要一个锁,所以我确保生产者和消费者都不会同时访问顶部。但我不确定。谢谢。

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

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

我们首先定义一个互斥锁,我们将使用它来锁定堆栈。

全球过程信号量声明

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

接下来,当我们在使用者和生产者线程中添加或删除数据时,我们需要锁定它。

生产者线程

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…

到目前为止,一切都非常简单。 生产者将仅在需要时锁定堆栈。 消费者也是如此。 我们不应该需要另一个信号灯,对吧? 正确? 不,错了!

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

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

过程信号量全局声明,版本 #2

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

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

接下来,我们需要将新的信号量实现到生产者和使用者线程中。 首先,我们需要让生产者发出已添加项的信号。 现在让我们更新生产者。

生产者线程,版本 #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() 如果不返回任何空堆栈上的数据。

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

最新更新