CLOCK_MONOTONIC and pthread_mutex_timedlock / pthread_cond_t

CLOCK_MONOTONIC and pthread_mutex_timedlock / pthread_cond_timedwait

本文关键字:pthread cond timedlock mutex and CLOCK MONOTONIC      更新时间:2023-10-16

pthread_mutex_timmedlock文档说abs_timeout接受一个CLOCK_REALTIME。然而,我们都知道,它不适合为特定的持续时间计时(由于系统时间调整)。

是否有一种方法可以使pthread锁定超时在CLOCK_MONOTONIC上是可移植的?pthread_cond_timedwait也是如此。

看了文档和pthread.h之后,我找不到让pthread_mutex_timmedlock使用CLOCK_MONOTONIC的方法,所以我认为这(目前)是不可能的。但是,对于pthread_cond_timedwait,可以使用如下代码:

pthread_condattr_t attr;
pthread_cond_t cond;
/* ... */
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&cond, &attr);

为了清楚起见,我省略了错误代码检查,但当然你应该这样做。

我假设使用CLOCK_REALTIME是因为它总是可用的,而原则上CLOCK_MONOTONIC是可选的。另外,我想知道设置绝对超时是否可以在系统调用被信号等中断后更容易恢复。

然而,它似乎相当不一致,时钟可以设置在某些情况下,而不是其他-真的应该有一个pthread_mutexattr_setclock(),但唉,似乎没有一个。我猜你只能祈祷没人定钟了!

在OS X和FreeBSD上,你可以使用kqueuekevent。看我的答案在这里:https://stackoverflow.com/a/31174803/432

在GLIBC中还没有办法改变pthread_mutex_timmedlock的时钟。可能是由于向后兼容的原因,MONOTONIC时钟比REALTIME引入得晚,所以有很多软件都在使用这个功能,更换clock可能会对这个有影响。

Linux的解决方案:

  • 你可以在这里找到pthread_mutex_timelock的源代码,它是基于FUTEX的系统调用,默认使用MONOTONIC。因此,您可以使用本文实现自己的互斥锁,这可能是一个很好的健壮的解决方案。

今天我发现了另一个可能的选择(对于Cpp),使用timed_mutex这样的:

timed_mutex m_wait;
if (m_wait.try_lock_for(std::chrono::milliseconds(tout))) return 0;
  return -1; // timeout
https://cplusplus.com/reference/mutex/timed_mutex/try_lock_for/

我想我解决了这个问题,用一个变通的代码。我在linux (c++)中使用了这个代码

myMutex.cpp

struct myMutexW_s {
  std::mutex m_wait;
  std::mutex m_thw;
  pthread_t tWId = 0;
  uint32_t tout = 1;
};
void Aux_Millisleep(int ms) {
  if (ms > 0) {
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = ms * 1000;
    select(0, NULL, NULL, NULL, &tv);
  }
}
static void* myWait_ThFcn(void* arg) {
  myMutexW_ts* m_ = (myMutexW_ts*)arg;
  pthread_detach(pthread_self());
  int s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  if (s != 0) { printf("pthread_setcancelstate: FAIL %d, %Xn", s, m_); }
  s = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  if (s != 0) { printf("pthread_setcanceltype: FAIL %d, %Xn", s, m_); }
  m_->m_thw.lock();
  Aux_Millisleep(m_->tout);
  m_->m_wait.unlock();
  m_->tWId = 0;
  return nullptr;
}
int myMutexTryLock(myMutexW_ts* m_) {
  return m_->m_wait.try_lock();
}
int myMutexWaitms(uint32_t tms, bool CrlI, myMutexW_ts* m_) {
  if (tms <= 0) return -1; // "timeout"
  if (m_ == nullptr) return -10;
  if (CrlI) m_->m_wait.try_lock();
  m_->m_thw.try_lock();
  m_->tout = tms;
  if ((ret = pthread_create(&m_->tWId, nullptr, &myWait_ThFcn, m_)) == 0) {
    pthread_yield();
  } else {
    printf("myMutexWaitms: FAIL %X, r:%d, th:%d, to:%dn", 
             m_, ret, m_->tWId, m_->tout);
    Aux_Millisleep(m_->tout); // fake timeout
    return -20; // !!!!!!
  }
  m_->m_thw.unlock();
  m_->m_wait.lock();
  if (m_->tWId) {
    pthread_cancel(m_->tWId);
    return 0; // unlocked before timeout
  }
  return -1; // timeout
}
void myMutexUnlock(myMutexW_ts* m_) {
  if (m_ == nullptr) return;
  m_->m_wait.unlock();
}
myMutexW_ts* myMutexNew(void) {
  return new myMutexW_ts;
}
void myMutexDel(myMutexW_ts** m_) {
  if (m_) {
    if (*m_) {
      if ((*m_)->tWId) {
        pthread_cancel((*m_)->tWId);
        (*m_)->tWId = 0;
        (*m_)->m_wait.unlock();
      }
      delete (*m_);
    }
    (*m_) = nullptr;
  }
}

myMutex.h

#include <stdint.h>
#include <stdbool.h>
typedef struct myMutexW_s myMutexW_ts;
#ifdef __cplusplus
extern "C" {
#endif
int myMutexTryLock(myMutexW_ts* m_) ;
int myMutexWaitms(uint32_t tms, bool CrlI, myMutexW_ts* m_);
void myMutexUnlock(myMutexW_ts* m_) ;
myMutexW_ts* myMutexNew(void);
void myMutexDel(myMutexW_ts** m_);
#ifdef __cplusplus
}
#endif

用法与普通互斥锁类似。

我还在测试它的性能,但显然它表现得很好。