Как справиться с изменением системных часов во время ожидания на std :: condition_variable?

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

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

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

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

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

10

Решение

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

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

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

2

Другие решения

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

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

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

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

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

1

Я столкнулся с той же проблемой. Мой коллега дал мне совет использовать определенные функции 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);
1

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

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

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

0
По вопросам рекламы [email protected]