我不确定我是否 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)。
接下来,我们需要在生产者和消费者线程中实现新的信号量。首先,我们需要让 Producer 发出已添加项目的信号。现在让我们更新 Producer。
生产者线程,版本 #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.
现在我们可以通过信号量检查堆栈中是否有数据,让我们重新编写我们的消费者线程。
消费者线程,版本 #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…
……就是这样。如您所见,有两个信号量,并且都是强制性的可用并在堆栈中没有任何内容时将其锁定。
希望能回答您的问题。如果您有任何具体问题,我会更新我的回复。
理论上,当进程开始时,您可以
用数据预初始化你的堆栈。在这种情况下,您应该 can
使用以下值初始化您的 itemsInStack 信号量
等于堆栈数。然而,在这个例子中,我们
假设堆栈中没有数据,也没有
初始化。
-
值得一提的是,在一个特定的情况下,你
理论上可以只使用stackAccessMutex。
考虑堆栈始终包含数据的情况。如果
堆栈是无限的,我们不需要通知我们的消费者数据
已添加,因为总会有数据。然而,在
现实中不存在“无限堆栈”。即使那应该是
在您当前的上下文中的情况下,添加没有开销
itemsInStack 信号量的安全网。
此外,放弃itemsInStack 计数可能很诱人
信号量如果在您当前的情况下调用
stack.pop() 如果不返回任何内容,则不会导致任何错误
空栈中的数据。
这是合理的,但不推荐。假设消费者线程正在执行
循环上的代码,循环会不断执行栈消耗代码 while
没有要消耗的数据。通过使用itemsInStack 信号量,您正在暂停
线程直到数据到达,这应该可以节省一些 CPU 周期。