我希望有人解决我对这个话题的困惑。这听起来很简单,但我真的很困惑。
在生产者/消费者问题中,我使用了 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(,因为我们需要在访问堆栈时锁定堆栈,并且我们需要在数据可用时向消费者发出信号,并在堆栈中没有任何内容时锁定它。
希望能回答你的问题。 如果您有任何具体问题,我会更新我的回复。
从理论上讲,当该过程开始时,您可以使用数据预初始化堆栈。 在这种情况下,
您应该使用以下值初始化itemsInStack
信号量等于堆栈计数。 但是,在本例中,我们假设堆栈中没有数据,也没有数据初始化。值得一提的是,在一种特定情况下,你理论上可以侥幸使用
stackAccessMutex
.考虑堆栈始终包含数据的情况。 如果堆栈是无限的,我们不需要向消费者发出数据信号已添加,因为总会有数据。 然而,在现实中,"无限堆栈"并不存在。 即使那应该是在您当前上下文中的案例中,添加itemsInStack
信号量的安全网。此外,抛弃
itemsInStack
计数可能很诱人信号量(如果在您当前的情况下调用stack.pop()
如果不返回任何空堆栈上的数据。这是合理的,但不建议这样做。 假设使用者线程正在执行代码在循环中,循环将连续执行堆栈消耗代码,同时没有可供使用的数据。 通过使用
itemsInStack
信号量,您将暂停线程直到数据到达,这应该可以节省几个 CPU 周期。