CLOCK_MONOTONIC and pthread_mutex_timedlock / pthread_cond_timedwait
Asked Answered
L

5

22

The pthread_mutex_timedlock documentation says that abs_timeout takes a CLOCK_REALTIME. However, we all know that it is inappropriate for timing a specific duration (due to system time adjustments).

Is there a way to make pthread lock timeout on CLOCK_MONOTONIC that is portable? The same goes with pthread_cond_timedwait.

Laky answered 9/1, 2013 at 23:0 Comment(3)
It's not inappropriate. You just have to set your application's locale timezone to UTC.Auk
@MahmoudAl-Qudsi No. That doesn't prevent your real time clock from changing.Laky
You can use a clock watcher thread that broadcasts all registered conditioned variables should the clock jump backwards.Aragats
S
24

Having looked at the documentation and pthread.h, I can't find a way to make pthread_mutex_timedlock use CLOCK_MONOTONIC so I assume that's not (currently) possible. For pthread_cond_timedwait, however, you can use code like this:

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

I've omitted error code checking for clarity, but of course you should do that.

I assume that CLOCK_REALTIME is used because it's always available whereas in principle CLOCK_MONOTONIC is optional. Also, I wonder if setting absolute timeouts makes it easier to recover after system calls get interrupted by signals and the like.

However, it does seem quite inconsistent that the clock can be set in some cases and not others - there really should be a pthread_mutexattr_setclock(), but alas there does not seem to be one. I guess you'll just have to hope someone doesn't set the clock!

Sickler answered 18/1, 2013 at 11:23 Comment(4)
This is good on Linux. pthread_condattr_setclock is not available on OS X and I have not been able to find a solution that works on OS X.Perpetuity
Seems like other people have run into this too: github.com/nanomsg/nanomsg/issues/10Sickler
I'm about to do some experimenting, but I may have stumbled onto a solution over here: #11339399Perpetuity
Note that waiting on a cond var is different then waiting on a mutex. In many cases it might be ok, but mutex options like recursion or priority inheritance are not available! Also note that waiting on a cond-var involves waiting on a mutex, so this can not be done with 100% CLOCK_MONOTONIC.Bekha
P
2

On OS X and FreeBSD, you can use kqueue and kevent. See my answer here: https://mcmap.net/q/588863/-are-there-any-well-behaved-posix-interval-timers

Perpetuity answered 2/7, 2015 at 7:33 Comment(0)
H
1

There is no way to change clock for pthread_mutex_timedlock in GLIBC yet. Maybe it's due to backward compatibility, the MONOTONIC clock was introduced later than REALTIME, so there are a lot of software which is using such function and replacement CLOCK may affect on this.

Solution/s for Linux:

  • You can found the source code of pthread_mutex_timedlock here and it is based on syscall of FUTEX which uses MONOTONIC by default. So you can implement your own mutex by using this article and it may be a good and robust solution.
Henequen answered 5/11, 2021 at 15:0 Comment(0)
W
0

I think i solved this question, with a workaround code. I used this code in 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, %X\n", s, m_); }
  s = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  if (s != 0) { printf("pthread_setcanceltype: FAIL %d, %X\n", 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:%d\n", 
             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

Usage is like a normal mutex.

I'm still testing its performance, but apparently it's been behaving well.

Whereat answered 2/10, 2022 at 22:23 Comment(0)
W
0

Today I found another possible option (for Cpp), using timed_mutex like this:

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/

Whereat answered 6/10, 2022 at 14:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.