Как справиться с изменением системных часов во время ожидания на std :: condition_variable? - PullRequest
0 голосов
/ 24 июня 2018

Я пытаюсь реализовать кроссплатформенный код на C ++ 11.Часть этого кода реализует объект семафора, используя std :: condition_variable .Когда мне нужно сделать время ожидания семафора, я использую wait_until или wait_for.

Проблема, с которой я сталкиваюсь, заключается в том, что похоже, что стандартная реализация condition_variable в системах на основе POSIX полагается на системные часы, а не на монотонные часы (см. Также: эта проблема со спецификацией POSIX )

Это означает, что если системные часы будут изменены на некоторое время в прошлом, моя переменная условия будет блокироваться гораздо дольше, чем я ожидаю.Например, если я хочу, чтобы моя condition_variable истекала через 1 секунду, если кто-то переводит часы на 10 минут назад во время ожидания, condition_variable блокируется на 10 минут + 1 секунду.Я подтвердил, что это поведение в системе Ubuntu 14.04 LTS.

Мне нужно положиться на этот тайм-аут, чтобы он был хотя бы несколько точным (т. Е. Он может быть неточным с некоторой погрешностью, но все равно должен выполняться, если системные часы меняются).Похоже, мне нужно написать собственную версию condition_variable, которая использует функции POSIX и реализует тот же интерфейс, используя монотонные часы.

Это звучит как много работы - и вроде беспорядка.Есть ли другой способ обойти эту проблему?

Ответы [ 4 ]

0 голосов
/ 28 августа 2018

Я столкнулся с той же проблемой.Мой коллега дал мне совет использовать некоторые функции C вместо <pthread.h>, и это сработало для меня.

Например, у меня было:

std::mutex m_dataAccessMutex;
std::condition_variable m_dataAvailableCondition;

с егостандартное использование:

std::unique_lock<std::mutex> _(m_dataAccessMutex);
// ...
m_dataAvailableCondition.notify_all();
// ...
m_dataAvailableCondition.wait_for(...); 

Выше можно заменить с помощью pthread_mutex_t и pthread_cond_t.Преимущество заключается в том, что вы можете указать, чтобы часы были монотонными.Краткий пример использования:

#include <pthread.h>

// Declare the necessary variables
pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_condattr_t m_attr;
pthread_cond_t m_cond;

// Set clock to monotonic
pthread_condattr_init(&m_attr);
pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);
pthread_cond_init(&m_cond, &m_attr); 

// Wait on data
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_sec += timout_in_seconds; 
pthread_mutex_lock(&m_mutex);
int rc = pthread_cond_timedwait(&m_cond, &m_mutex, &ts);
if (rc != ETIMEDOUT)
    ; // do things with data
else 
    ; // error: timeout
// ...
pthread_mutex_unlock(&m_mutex); // have to do it manually to unlock
// ...

// Notify the data is ready
pthread_cond_broadcast(&m_cond);
0 голосов
/ 26 июня 2018

Централизованно регистрируйте свои активные переменные состояния.

Сделайте некоторое усилие, чтобы обнаружить ошибку синхронизации, даже если она является спин-блокировкой потока на текущих часах (ick) или каким-либо другим способом.

Когда вы обнаружите ошибку часов, нажмите переменную условия.

Теперь оберните переменные условия в тонкую оболочку, которая также поддерживает обнаружение проскальзывания часов.Он вызывает wait_until, но заменяет предикат на тот, который обнаруживает проскальзывание часов, и когда это происходит, выходит из ожидания.

Когда ваша реализация нарушена, вы должны делать то, что должны.

0 голосов
/ 05 июля 2018

После рассмотрения возможных решений этой проблемы наиболее разумным представляется запрет на использование std :: condition_variable (или, по крайней мере, разъяснение предостережения о том, что он всегда будет использовать системные часы),Затем я должен сам по себе заново реализовать условную переменную стандартной библиотеки, с учетом выбора часов.

Поскольку мне приходится поддерживать несколько платформ (Bionic, POSIX, Windows и, в конечном итоге, MacOS), это означает, что я собираюсь поддерживать несколько версий этого кода.

Хотя это неприятно,кажется, что альтернативы еще более противны.

0 голосов
/ 26 июня 2018

Возможно, это не лучшее решение или отличное решение, но вы сказали «обойти», а не «много работы», поэтому:

  • Используйте возможности вашего дистрибутива для мониторингаизменения системных часов (я не совсем уверен, что это за средства; в худшем случае вы можете запускать задание cron каждые 5 минут, чтобы проверить, что часы приближаются к ожидаемому значению).
  • При обнаружениименяются системные часы, сообщают что-то процессу, в котором у вас есть ожидающие / спящие потоки.Вы можете использовать сигнал;или труба;или сокет unix-домена;или даже некоторую общую память.
  • На стороне процесса убедитесь, что вы это получили (то есть напишите обработчик сигнала или имеете поток, выполняющий блокировку ввода-вывода на канале, или опросите общую память, используя не-1008 * sleep -соответственно)
  • Получив уведомление об изменении системных часов, встряхните все в своем процессе, разбудите спящие потоки и переоцените то, что нужно сделать, основываясь на измененном времени.Может быть, это точно так же, как и раньше, и в этом случае ваши потоки просто используют переменную условия снова.

Не очень элегантно, и есть много накладных расходов - но это имеет смысл, ине какой-то сумасшедший взлом.

...