【问题标题】:Implementing a binary semaphore class in C++在 C++ 中实现二进制信号量类
【发布时间】:2012-03-24 18:45:13
【问题描述】:

所以,我正在我的一个课程中使用调度程序。基本上,我们假设一次只能执行一个线程。我们应该使用一个信号量类来让这些线程自己阻塞来模拟等待CPU的线程。

问题是,线程似乎在错误的时间阻塞并在错误的时间执行。我想知道我是否缺少对信号量的一些概念性理解以及如何实现它。我想知道是否可以就我的实施获得一些反馈。导师提供了这个头文件,我没有以任何方式修改过:

class Semaphore {
private:
  int             value;
  pthread_mutex_t m;
  pthread_cond_t  c;

public:

  /* -- CONSTRUCTOR/DESTRUCTOR */

  Semaphore(int _val);

  //~Semaphore();

  /* -- SEMAPHORE OPERATIONS */

  int P();
  int V();
};

这是我使用 posix 东西的实现:

Semaphore::Semaphore(int _val){
    value = _val;
    c = PTHREAD_COND_INITIALIZER;
    m = PTHREAD_MUTEX_INITIALIZER;
}

int Semaphore::P(){
    if(value <= 0){
        pthread_cond_wait(&c, &m);
    }
    value--;
}

int Semaphore::V(){
    value++;
    if(value > 0){
        pthread_cond_signal(&c);
    }
}

【问题讨论】:

    标签: c++ pthreads semaphore


    【解决方案1】:

    您忽略了锁定互斥锁。

    其次,这里的信号量是计数信号量,而不是二进制信号量。二进制信号量只有两种状态,所以bool 变量是合适的:

    class Semaphore {
    private:
      bool            signaled;   // <- changed
      pthread_mutex_t m;
      pthread_cond_t  c;
    
      void Lock() { pthread_mutex_lock(&m); }          // <- helper inlines added
      void Unlock() { pthread_mutex_unlock(&m); }
    public:
    
      /* -- CONSTRUCTOR/DESTRUCTOR */
    
      Semaphore(bool);
    
      //~Semaphore();
    
      /* -- SEMAPHORE OPERATIONS */
    
      void P();   // changed to void: you don't return anything
      void V();
    };
    

    实施:

    // consider using C++ constructor initializer syntax.
    
    Semaphore::Semaphore(bool s){        // don't use leading underscores on identifiers
        signaled = s;
        c = PTHREAD_COND_INITIALIZER;    // Not sure you can use the initializers this way!
        m = PTHREAD_MUTEX_INITIALIZER;   // they are for static objects.
    
        // pthread_mutex_init(&m); // look, this is shorter!
    }
    
    void Semaphore::P(){
        Lock();              // added
        while (!signaled){   // this must be a loop, not if!
            pthread_cond_wait(&c, &m);
        }
        signaled = false;
        Unlock();
    }
    
    void Semaphore::V(){
        bool previously_signaled;
        Lock();
        previusly_signaled = signaled; 
        signaled = true;
        Unlock();  // always release the mutex before signaling
        if (!previously_signaled)
          pthread_cond_signal(&c); // this may be an expensive kernel op, so don't hold mutex
    }
    

    【讨论】:

    • “老师提供了这个头文件,我没有以任何方式修改过”。
    • 可能是这样,但没有必要因此而修改我的答案。事实是,这里没有二进制信号量,我正确地指出了这一点。也许任务是将其实现为二进制信号量而不更改标头;这是可以做到的。
    • 谢谢!看到你的实现给了我一个“啊哈!”时刻。我认为讲师故意让界面保持开放式,以便我们可以决定使用哪种信号量。无论如何,我最终还是实现了一个计数信号量,以便更轻松地针对不同的调度算法修改程序。
    【解决方案2】:

    您的计数信号量算法缺少一个 while 循环,并发出不必要的信号量信号。

    原始逻辑,加了锁(见其他答案):

    int Semaphore::P(){
        Lock();
        if(value <= 0){
            pthread_cond_wait(&c, &m);
        }
        value--;
        Unlock();
    }
    
    int Semaphore::V(){
        Lock();
        value++;
        if(value > 0){
           pthread_cond_signal(&c);
        }
        Unlock(); 
    }
    

    正确方法:

    int Semaphore::P(){
        Lock();
        while (value <= 0){   // not if
            pthread_cond_wait(&c, &m);
        }
        // value is now > 0, guaranteed by while loop
        value--;
        // value is now >= 0
        Unlock();
    }
    
    int Semaphore::V(){
        Lock();
        int prior_value = value++;
        Unlock();
    
        // E.g. if prior_value is 50, should we signal? Why?
    
        if (prior_value == 0) // was not signaled previously, now is.
            pthread_cond_signal(&c);
    }
    

    为了提高效率,收集有关是否在互斥锁内发出信号的信息,然后在互斥锁外发出信号。互斥锁应尽可能少地保留机器指令,因为它们会增加争用,降低并发性。信号操作可能需要数百个周期(前往内核执行等待队列操作)。

    在等待条件变量时必须使用循环,因为可能会出现虚假唤醒。此外,如果您在互斥锁之外发出信号,则条件信号并不总是转到“预期”线程。在unlocksignal 之间,一些线程可以潜入并调用P 并减少互斥量。然后在条件下唤醒的必须重新评估测试,否则将错误地继续。

    【讨论】: