相互排斥和信号量

时间:2010-10-03 15:58:53

标签: c++ mutex semaphore

我正在写一个模拟男女皆宜的浴室的程序(用于家庭作业)。一次只允许4个人,如果其他性别已经在使用浴室,男女不能进入。我的问题是允许最多4人在浴室。从输出中可以看出,一次只有一个人进入洗手间。这是我的代码:

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 个答案:

答案 0 :(得分:2)

我认为你有太多的信号量。你的男人/女人信号量一次给一个人控制。考虑使用一些受互斥体保护的状态变量(卫生间的当前性别,浴室中的人数),而不是那么多不同的信号量。

您是否维持线路排序,或者人们可以根据当前的洗手间性别跳过?例如,如果你有女人,女人,女人,男人,女人,是第4个允许跳过男人进入洗手间的女人,或者是3个女人退出,然后男人进出,那么女人就可以进入?这比允许跳过更容易。

答案 1 :(得分:2)

是信号量的使用要求吗?例如,在“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样式inmutabilitity),线程的争用(因此,饥饿)大大改善。在你的例子中,状态集很小(只是一堆人),它不是太糟糕,我们不删除使用的状态。

然而,即使我提到的问题,我认为你会同意所发生的事情的逻辑更明确和可读。

答案 2 :(得分:0)

编辑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”线程的行为应该类似。请记住,等待和互斥信号量可以保护男性和女性变量。


在男人/女人“退出”浴室之前,你正在发出互斥信号。如果我理解正确,互斥体是这样的,任何时候只有一个性别在浴室。因为你在输出“A Man已退出浴室”之前发出信号,女人可以获得互联网并进入。

因为男人和女人最初都在等待两个不同的信号量。有些人会进入最初的信号量是可以理解的。从那里看起来你正在获得互斥(在男人和女人之间共享),然后在你进入之后释放它然后退出。也许你的意思是在这里发出“男人”或“女人”信号量的信号?

编辑:我想我的答案的要点是:男性和女性之间共享互斥锁。在你的代码中,当一个人获得互斥锁时,他们说他们在,你减少他们在等待,然后你释放互斥锁。更深入地考虑最后一步。如果你在他们离开之前释放互斥锁,那么这里有什么可能?

Edit2(响应您的评论):您的新代码是什么样的(编辑您的原始帖子)?

这将帮助我们将代码抽象出逻辑,然后我们可以尝试正确地构建逻辑并比较我们认为正确的代码正在做什么。

编辑3:好的,看起来你越来越近了。这里有一些需要考虑的事情(我没有发布完整的解决方案,因为这是家庭作业,我希望你学习!)

  • 什么是互斥保护?
  • 什么是男人/女人保护?
  • 什么是restroomCount保护?
  • 什么是maxCapacity保护?
  • 一个人应该以什么顺序获得 这些信号量?
  • ...即。哪个信号量保护哪个 其他信号量以及如何?

特别注意洗手间计数信号量......(提示:它可能比简单地保护计数变量更重要。它可能需要保护释放其他信号量......)

编辑4:所以我终于意识到你在这个问题上试图避免饥饿(如下面的评论所示)。虽然您的作业与读者/作者问题非常相似,但是通过任一线程类型避免饥饿的附加约束使得难以实现。我个人不知道如何在不使用事件设置首选项(http://msdn.microsoft.com/en-us/library/dd492846.aspx)的情况下执行此操作,即使这样,也无法保证饥饿永远不会发生,如果我理解维基百科(http://en.wikipedia.org/wiki/Readers-writers_problem#The_third_readers-writers_problem)文章关于这个话题,这是唯一的方法。

你被允许使用活动吗?

我必须为没有完全了解这个额外的读者/作家没有饥饿的约束而道歉。希望其他人可以更好地帮助你。

答案 3 :(得分:0)

问题的问题
原始代码不是很好。

浴室队列的处理应该与队列中人员的生成分开 - 如果没有在队列填满后再运行单独的线程。

假设男女基本上是分开的队列 - 不是以某种固定的顺序混合在一起,否则问题对使用信号量没有任何意义。

这个问题并没有描述当病情正确时有多少人进入,男性盥洗室男性更多,男性盥洗室有4个或者只有男性排队的人数少于女性?

即使如此,我认为所描述的问题(并且基于没有线程的示例代码)在信号量方面也不能很好地工作,主要的问题是信号量不会轻易产生计数并且成功的等待变化伯爵。

我在问题中看到的有趣的事情是,在一个接近相等的队列长度的低效率和不允许同性进入厕所之间的交易以及在厕所中的其余人员离开同一性别的数量之前的机会再次变大。让我们面对它,它是男女皆宜的,所以它应该允许4个人,不论性别;)

提议的解决方案
所以你需要使用一个信号量,关于信号量的有趣的事情是多次使用的记录(不像互斥体),如果没有自由空间,那么它可能会等待。然而,在等待的人之间并没有区别,它只能说明没有空间。

拥有1个信号量,并认为当一个人进入队列或有人离开浴室时你应该检查信号量。

然后你可以为男性和女性分别拥有1个'队列'(从给定这基本上是一个计数)。这些队列在输入方面并没有真正相关或相互限制,因此与信号量无关。每个都可以遵循无锁定提供程序模式,但您可能会发现使用互斥锁进行同步更容易,以便您可以检查队列的大小并对其进行操作。在下面我直接使用了count,而不是使用某种形式的InterlockedIncrement和InterlockedDecrement来防止在同一队列中添加和删除人员。

在粗略的,Bathroom.h

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();
}