Взаимное исключение и семафоры - PullRequest
11 голосов
/ 03 октября 2010

Я пишу программу (для домашней работы), которая имитирует ванную комнату унисекс.Только 4 человека допускаются одновременно, и мужчины и женщины не могут войти, если другой пол уже использует ванную.Моя проблема в том, чтобы в ванной разрешалось не более 4 человек.Как видно из вывода, только 1 человек одновременно попадает в туалет.Вот мой код:

const int Delayx = 60;
int i;
int restroom = 0;
int Menwaiting = 0;
int Womenwaiting = 0;
semaphore max_capacity;
semaphore woman;
semaphore man;
semaphore mutex;
semaphore restroomcount;
void Delay(void)
{
    int DelayTime;
    DelayTime = random(Delayx);
    for (i = 0; i<DelayTime; i++);
}

void Woman(void)
{
//  for(;;){
    Womenwaiting++;
    //wait(mutex);
    wait(woman);
    wait(max_capacity);
        //wait(woman);
        wait(mutex);
        wait(restroomcount);
        cout << "A Woman has entered Restroom"<<endl;
        cout << "People in the Restroom:" << restroom++ <<endl <<endl;
        signal(restroomcount);
        Womenwaiting--;
        Delay();
        wait(restroomcount);
        cout << "A woman has exited Restroom"<<endl;
        cout << "People in the Restroom:" << restroom-- <<endl<<endl;
        signal(restroomcount);
        signal(mutex);
        signal(max_capacity);
        if(Menwaiting > Womenwaiting){
              signal(man);
                  }
              else{
            signal(woman);
        }
        //signal(max_capacity);
    //signal(man);
//  }
}
void Man(void)
{
//  for(;;){
    Menwaiting++;
    //wait(mutex);
    wait(man);
    wait(max_capacity);
    //wait(man);
        wait(mutex);
        wait(restroomcount);
        cout <<"A Man has entered the Restroom"<<endl;
        cout <<"People in the Restroom:" << restroom++ <<endl<<endl;
        signal(restroomcount);
        Menwaiting--;
        //signal(mutex);
        Delay();
        //wait(mutex);
        wait(restroomcount);
        cout << "A man has exited the Restroom"<<endl;
        cout <<"People in the Restroom:" << restroom-- <<endl<<endl;
        signal(restroomcount);
        signal(mutex);
        signal(max_capacity);
        if(Womenwaiting > Menwaiting){
            signal(woman);
            }
        else{
            signal(man);
            }
        //signal(max_capacity);
        //signal(woman);
//}
}
void main()
{
    initialsem(woman,1);
    initialsem(man,1);
    initialsem(max_capacity,4);
    initialsem(mutex,1);
    initialsem(restroomcount,1);
    cobegin
    {
        Woman(); Woman(); Woman(); Woman(); Woman(); Man();  Man(); Man(); Man(); Man();
    }

}

Это приводит к следующему выводу:

Человек вошел в туалет
Люди в туалете: 1

Мужчина вышел из туалета
Люди в туалете: 0

Мужчина вошел в туалет
Люди в туалете: 1

Мужчина вышел из туалета
Люди в туалете: 0

Женщина вошла в туалет
Люди в туалете: 1

Женщина вышла из туалета
Люди в туалете: 0

Женщина вошла в туалет
Люди в туалете: 1

Женщина вышла из туалета
Люди в туалете: 0

И так далее,навсегда.

Ответы [ 4 ]

2 голосов
/ 04 октября 2010

Я думаю, у вас слишком много семафоров. Ваши семафоры мужчина / женщина одновременно открывают 1 человеку. Попробуйте использовать некоторые переменные состояния, защищенные мьютексами (текущий пол в ванной, количество людей в ванной), а не так много разных семафоров.

Поддерживаете ли вы порядок строк или люди могут пропустить в зависимости от текущего пола в туалете? Например, если у вас есть женщина, женщина, женщина, мужчина, женщина, разрешено ли 4-й женщине пропускать мужчину и входить в туалет или делать выход из 3 женщин, тогда мужчина входит / выходит, тогда женщина может войти ? Это более простая проблема, чем пропустить.

2 голосов
/ 07 октября 2010

является ли обязательным использование семафоров? например, в псевдокоде "c ++" реализация будет выглядеть следующим образом:

Сначала давайте создадим объект состояния и функцию, которая проверяет переходы между состояниями

struct BathRoomState
{
   int women;
   int men;

   BathRoomState( int w , int m ) : women(w) , men(m) {}

   bool hasWomen()
   { 
      if (women > 0 && men == 0)
         return true;
      return false;
   }

   bool isEmpty()
   {
      return (women + men == 0);
   }

   static bool isValidTransition( BathRoomState* a , BathRoomState* b )
   {
      if (a->HasWomen())
      {
        if ( (abs( a->women - b->women ) == 1) && (a->men == b->men) )
           return true;
        else false;
      } else if (a->isEmpty())
      {
          if ((b->women == 1 && b->men == 0)
               || (b->women == 0 && b->men == 1))
             return true else false;
      } else //a has men
      {
          if ((abs( a->men - b->men ) == 1) && ( a->women == b->women))
            return true else false;
      }
   }
}

Позволяет также создать глобальную ссылку на текущее состояние и функцию для обновления текущего состояния на основе некоторого следующего требуемого состояния

BathRoomState* currentBathroomState = 0;
bool TryToChangeState(BathRoomState* newState)
{ 
  BathRoomState* existingState = currentBathroomState;
  if (BathRoomState::isValidTransition( existingState , newState ))
  {
     //this atomic operation depends on library support
     bool success = CompareAndSwapAtomically( currentBathroomState , existingState , newState );
     return success;
  }
}

затем мы создаем глобальный вектор для хранения состояний и функцию, представляющую женскую нить, пытающуюся пойти в ванную

std::vector< BathRoomState* > noGCinThisExample;
//thread functtion
void women()
{
   BathRoomState* existingState = currentBathroomState;
   BathRoomState* newState = new BathRoomState( existingState.women+1 , existingState.men );
   while (!TryToChangeState(newState))
   {
     //yield or sleep from time to time here to let other threads progress
     existingState = currentBathroomState;
     newState.women = existingState.women + 1;
     newState.men = existingState.men;
   }
   noGCinThisExample.push_back( newState ); //no GC in this example
   //the woman is in the bathroom now. lets give her some time
   delayForWomen();
   //lets try to get her out

   BathRoomState* exitState = new BathRoomState( existingState.women-1 , existingState.men );
   while (!TryToChangeState(exitState ))
   {
     //yield or sleep from time to time here to let other threads progress
     existingState = currentBathroomState;
     exitState.women = existingState.women - 1;
     exitState.men = existingState.men;
   } 
   noGCinThisExample.push_back( exitState); //no GC in this example
}

//homework: do a similar function for men

и основная функция с логикой и инициализацией цикла процесса

void main()
{
  BathRoomState* initialState = new BathRoomState( 0 , 0);
  noGCinThisExample.push_back( initialState );
  currentBathroomState = initialState;
  while(some_condition)
  {
   if (random() > 0.5)
     thread( women() );
   else
     thread( men() );
  }
};

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

Обратите внимание, что я не использую семафоры мьютекса или блокировку, единственный используемый мной блокирующий примитив - это CAS (адрес, old_value, new_value) (сравнивать и менять местами). Этот примитив атомарно сравнивает указатель (адрес) и, если он все еще содержит (old_value), тогда он присваивает ему new_value и успешно, в противном случае он терпит неудачу. Кроме того, вам все еще нужна глобальная блокировка для std :: vector, хранящая состояния, которые я не включил в код (вы также можете просто утечь их, но я храню их где-то, чтобы вы могли подумать, что они должны быть удалены, как только вы узнаете, как можно заставить работать GC в этих случаях)

Поскольку все мои промежуточные состояния являются неизменяемыми (неизменяемость в стиле lisp / clojure), конкуренция (и, следовательно, голодание) потоков значительно улучшается. В вашем примере набор состояний мал (всего несколько человек), и это не так уж плохо, что мы не удаляем используемые состояния.

однако, даже с упомянутыми мною проблемами, я думаю, вы согласитесь с тем, что логика происходящего гораздо яснее и понятнее.

0 голосов
/ 02 ноября 2010

Проблемы с вопросом
Оригинальный код не очень ОО.

Обработка очереди ванной должна быть отделена от генерации людей в очереди - если не выполняется отдельный поток, по крайней мере, после заполнения очереди.

Предполагается, что в основном существуют отдельные очереди мужчин и женщин - не смешанные в каком-то фиксированном порядке, в противном случае проблема не имеет смысла использовать семафор.

Проблема не описывает, сколько людей может войти, когда условие правильное, мужской туалет с большим количеством мужчин, вы заполняете его до 4 или только до тех пор, пока число мужчин снова не станет меньше, чем у женщин?

Несмотря на то, что описанная выше проблема (и основанная на примере кода без потоков), на мой взгляд, не очень хорошо работает с семафором, главная проблема заключается в том, что семафор не дает счетчик легко и успешное изменение ожидания счет.

Интересная вещь, которую я вижу в проблеме, - это неэффективность в почти равной длине очереди и обмен между запретом другого того же пола в туалет и шансом, что до того, как оставшиеся в туалете люди оставят количество того же пола снова становится больше Давайте посмотрим правде в глаза, это унисекс, и поэтому он должен позволять 4 человек, независимо от пола;)

Предлагаемое решение
Таким образом, вам нужно использовать семафор, интересная вещь о семафоре - это запись многократного использования (в отличие от мьютекса), и если свободного места нет, он, возможно, будет ждать. Однако он не делает различий между ожидающими, он только скажет, что есть свободное место.

Имейте 1 семафор и подумайте, что вы должны проверить семафор, когда человек входит в очередь или когда кто-то покидает ванную комнату.

Тогда у вас может быть по 1 «очереди» для мужчин и женщин (исходя из того, что это в основном счет). Эти очереди на самом деле не связаны или не ограничивают друг друга с точки зрения входа и поэтому не имеют никакого отношения к семафорам. Каждый из них может следовать шаблону свободной блокировки поставщика, но вам может оказаться проще использовать мьютекс для синхронизации, чтобы вы могли проверить размер очередей и манипулировать ими. Далее я просто использовал счетчик напрямую, вместо этого он должен использовать некоторую форму InterlockedIncrement и InterlockedDecrement для защиты от добавления и удаления людей из одной очереди.

В грубой, Ванная комната.ч

class Bathroom
{
public:
    Bathroom(void);
    ~Bathroom(void);

    AddMan();
    AddWoman();
    Run();
private:
    StateChange();

    int m_Menwaiting;
    int m_Womenwaiting;
    semaphore max_capacity;

    enum Users {
        NOBODY ,
        WOMEN,
        MEN
    } m_inUseBy;
};

Bathroom.cpp

Bathroom::Bathroom(void)
    : m_Menwaiting(0)
    , m_Womenwaiting(0)
    , m_inUseBy(NOBODY)
{
    initialsem(max_capacity,4);
}


Bathroom::~Bathroom(void)
{
    freesem(max_capacity);
}

Bathroom::AddMan(){
    ++m_Menwaiting;
    StateChange();
}

Bathroom::AddWoman(){
    ++m_Womenwaiting;
    StateChange();
}

Bathroom::StateChange() {

    // extra at a time
    if( m_Menwaiting > m_Womenwaiting && inUseBy != WOMEN ) {
        if( wait(max_capacity,0 delay) != timeout )
            m_Menwaiting--;
    }

    if( m_Womenwaiting > m_Menwaiting && inUseBy != MEN ) {
        if( wait(max_capacity,0 delay) != timeout )
            m_Womenwaiting--;
    }

    // all available slots
    if( m_Menwaiting > m_Womenwaiting && inUseBy != WOMEN ) {
        while( wait(max_capacity,0 delay) != timeout )
            m_Menwaiting--;
    }

    if( m_Womenwaiting > m_Menwaiting && inUseBy != MEN ) {
        while( wait(max_capacity,0 delay) != timeout )
            m_Womenwaiting--;
    }

}

Bathroom::run(){
// people leaving bathroom simulated
    while(1) {
        Delay();
        signal(max_capacity);
        StateChange();
    }
}

Program.cpp

Bathroom b1;

addPeople() {
  while(true) {
  // randomly add people
    Delay();
    b1.AddMen();
    b1.AddWomen();
  }
}

int main(){

  thread( addPeople );

  b1.run();
}
0 голосов
/ 03 октября 2010

Редактировать 5 (я понимаю, что это может быть слишком поздно или слишком поздно, так как это, вероятно, домашнее задание, но я просто подумал, как сделать это, используя только семафоры.)

хорошо, вот мой псевдокод:

//shared variables
//the # of men or women in the bathroom
int menCountIn=0;
int womenCountIn=0; 

//the # of men or women waiting
int menCountWtg=0;
int womenCountWtg=0;

//whose turn it is to go next
int turn = -1;
//-1 = anybody can go
//0 = only men can go
//1 = only women can go

#define capacity 4

//semaphores
semaphore mutex; //a sort of bathroom key
//mutex will protect menCountIn, womenCountIn, and turn
semaphore waiting; 
//waiting protects the variable count of how many people are waiting

//Female thread:
bool in = false; //a thread-specific variable telling me whether I'm in or not
//will be used in an almost-spinlocking type of way

wait(waiting);
womenWaiting++;
signal(waiting);

while(!in){
   thread.sleep(60); //to avoid constant spinlock
   wait(mutex);
   if (menCountIn ==0 && (turn == -1 || turn == 1) && womenIn < capacity)
   {
      wait(waiting);
      womenWtg---; //no longer waiting to get in
      signal(waiting);
      womenCountIn++; // a women entered
      cout << "Woman entered restroom" << endl;
      in=true;
   }
}//end while loop

thread.sleep(60);//in bathroom taking care of business!

wait(mutex);
womenIn--;
cout << "A woman left the restoom." << endl;
wait(waiting);
if(menWaiting > womenWtg)
{
   turn = 0; //men should get in at next opportunity
   cout << "It's mens' turn!" << endl;
}
else if (menWaiting == womenWtg == 0)
{
   turn = -1; //anybody should be able to get in
}
signal(waiting);
signal(mutex);

Поток "Man" должен вести себя аналогично.Имейте в виду, что семафоры ожидания и мьютекса защищают переменные как мужчин, так и женщин.


Вы сигнализируете мьютекс, прежде чем мужчина / женщина "выйдет" из ванной комнаты.Если я правильно понимаю, мьютекс таков, что в ванной может быть только один пол.Так как вы сигнализируете об этом перед выводом «Мужчина вышел из ванной», женщина может получить мьютекс и войти.

Поскольку мужчины и женщины первоначально ждут два разных семафора, понятно, что некоторые из них попадут вв этот начальный семафор.Оттуда кажется, что вы получаете мьютекс (разделенный между мужчинами и женщинами), а затем, входя в него, вы отпускаете его, прежде чем они выходят.Возможно, вы хотите обозначить здесь семафор «мужчина» или «женщина»?

Редактировать : Я думаю, суть моего ответа такова: мьютекс разделен между мужчинами и женщинами,В вашем коде, когда человек получает мьютекс, в котором, по его словам, он находится, вы уменьшаете ожидание, а затем освобождаете мьютекс.Подумайте об этом последнем шаге немного глубже.Если вы отпускаете мьютекс до того, как они уйдут, что здесь возможно?

Edit2 (в ответ на ваши комментарии) : Как выглядит ваш новый код (Отредактируйте исходное сообщение)?

Это поможет нам абстрагировать код от логики, и тогда мы сможем попытаться правильно структурировать вашу логику и сравнить то, что мы считаем правильным, с тем, что делает ваш код.

Правка3: Ладно, похоже, ты все ближе.Вот некоторые вещи, о которых стоит подумать (я не публикую полное решение, потому что это домашняя работа, и я хочу, чтобы вы учились!)

  • Что защищает мьютекс?
  • Что такое человек/ женщина защищает?
  • Что такое уборнаяКонтроль защиты?
  • Что такое защита maxCapacity?
  • В каком порядке человек должен получить эти семафоры?
  • ...ie Какие семафоры защищают, какие другие семафоры и как?

Особенно серьезно подумайте о семафоре подсчета в уборной ... (СОВЕТ: Это может быть важнее, чем просто защита переменной подсчета. Может потребоватьсязащищать освобождение других семафоров ...)

Редактировать 4: Поэтому я наконец понял, что вы пытаетесь избежать голодной смерти в этой проблеме (как указано в комментариях ниже).Хотя ваша домашняя работа очень похожа на проблему читателей / писателей, дополнительное ограничение во избежание голодания в зависимости от типа потока затрудняет реализацию.Лично я не знаю, как это сделать, не используя События для установки предпочтения (http://msdn.microsoft.com/en-us/library/dd492846.aspx),, и даже в этом случае нет никакой гарантии, что голод никогда не случится, что, если я понимаю статью из Википедии (http://en.wikipedia.org/wiki/Readers-writers_problem#The_third_readers-writers_problem) оэта тема правильно, это единственный способ сделать это.

Вам разрешено использовать события?

Я должен извиниться за то, что не полностью соблюдает это дополнительное ограничение для читателей / писателей, заключающееся в отсутствии голода. Надеюсь, кто-нибудьеще может помочь вам лучше.

...