Я не уверен на 100%, что я следую вашему псевдокоду, но я сделаю все возможное, чтобы объяснить, как использовать семафоры для управления стеком в процессе Producer-customer.
Когда выЕсли у вас есть стек, к которому осуществляется доступ через несколько потоков, вам нужно будет заблокировать его, когда к данным обращаются или, более конкретно, когда они передаются и извлекаются.(Это всегда основополагающее предположение о проблеме производителя-потребителя.)
Мы начнем с определения мьютекса, который мы будем использовать для блокировки стека.
Глобальное объявление процессаСемафоры
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.
Поток 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
, вы приостанавливаетечитать до тех пор, пока не поступят данные, которые должны сохранить несколько циклов ЦП.