用C++实现一个二进制信号量类

Implementing a binary semaphore class in C++

本文关键字:一个 二进制 信号量 C++ 实现      更新时间:2023-10-16

所以,我正在我的一个类中开发一个调度器。基本上,我们假设一次只能执行一个线程。我们应该使用一个信号量类来允许这些线程阻塞自己,以模拟等待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);
    }
}

您忽略了锁定互斥对象。

其次,这里有一个计数信号量,而不是二进制信号量。二进制信号量只有两种状态,因此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();
};

Impl:

// 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
}

计数信号量算法缺少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并减少互斥。然后,在这种情况下醒来的人必须重新评估测试,否则将错误地进行测试。