Управление общими данными с использованием мьютекса и семафоров - PullRequest
3 голосов
/ 01 марта 2012

Я хотел, чтобы кто-то разрешил мою путаницу в этой теме. Это может показаться простым, но я действительно сбит с толку.

В задаче производитель / потребитель я использовал решение с 4 семафорами. Я использовал разные блокировки для каждого из критических разделов. Скажем,

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" в производителе.

Теперь мой вопрос. Если мой производитель и потребитель не используют буфер (тыл и фронт), а используют стек, где только они могут манипулировать [top]. Нужно ли использовать один мьютекс или две разные блокировки, как в моем 4-семафоре, чтобы обеспечить взаимное исключение.

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

Лично я думаю, что мне нужна одна блокировка для обеих процедур, поэтому я удостоверяюсь, что никто из производителя и потребителя не будет одновременно обращаться к вершине. Но я не уверен в этом. Спасибо.

1 Ответ

2 голосов
/ 03 апреля 2012

Я не уверен на 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) , потому что нам нужно заблокировать наш стек, когда к нему обращаются и , мы должны сигнализировать нашему потребителю, когда данные доступныи заблокировать его, когда в стеке ничего нет.

Надеюсь, что ответил на ваш вопрос.Я обновлю свой ответ, если у вас есть какие-либо конкретные вопросы.

  1. Теоретически, когда процесс запускается, вы можете предварительно инициализировать свой стек с данными.В этом случае вы можете должны инициализировать свой семафор itemsInStack со значением, равным количеству стеков.Однако в случае этого примера мы предполагаем, что в стеке нет ни данных, ни инициализируемых.

  2. Стоит отметить, что при одном конкретном случае вы можететеоретически сойдет с рук только stackAccessMutex.Рассмотрим случай, когда стек всегда содержит данные.Если стек бесконечен, нам не нужно сигнализировать потребителю, что данные были добавлены, потому что всегда будут данные.Однако в действительности «бесконечный стек» не существует.Даже если это имеет место в вашем текущем контексте, при добавлении защитной сети семафора itemsInStack нет лишних затрат. Кроме того, может возникнуть искушение выбросить семафор подсчета itemsInStack, еслив ваших текущих обстоятельствах вызов stack.pop() не вызовет никакой ошибки, если он не вернет никаких данных в пустом стеке.

    Это правдоподобно, но не рекомендуется.Если предположить, что поток потребителя выполняет код в цикле, цикл будет непрерывно выполнять код потребления стека, пока нет данных для потребления.Используя семафор itemsInStack, вы приостанавливаетечитать до тех пор, пока не поступят данные, которые должны сохранить несколько циклов ЦП.

...