Я пытаюсь реализовать кроссплатформенный код в 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 и реализует тот же интерфейс, используя монотонные часы.
Это звучит как много работы — и вроде беспорядка. Есть ли другой способ обойти эту проблему?
После рассмотрения возможных решений этой проблемы наиболее целесообразным представляется запрет на использование std :: condition_variable (или, по крайней мере, разъяснение предостережения о том, что он всегда будет использовать системные часы). Затем я должен сам по себе заново реализовать условную переменную стандартной библиотеки, с учетом выбора часов.
Поскольку мне приходится поддерживать несколько платформ (Bionic, POSIX, Windows и, в конечном итоге, MacOS), это означает, что я собираюсь поддерживать несколько версий этого кода.
Хотя это противно, похоже, что альтернативы еще более противны.
Зарегистрируйте ваши активные переменные состояния централизованно.
Сделайте некоторое усилие, чтобы обнаружить ошибку синхронизации, даже если она является спин-блокировкой потока на текущих часах (ick) или каким-либо другим способом.
Когда вы обнаружите ошибку часов, ткните переменную условия.
Теперь оберните ваши переменные состояния в тонкую оболочку, которая также поддерживает обнаружение проскальзывания часов. Это вызывает wait_until
но заменяет предикат на тот, который обнаруживает проскальзывание часов, и когда это происходит, выходит из режима ожидания.
Когда ваша реализация нарушена, вы должны делать то, что должны делать.
Я столкнулся с той же проблемой. Мой коллега дал мне совет использовать определенные функции 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);
Возможно, это не лучшее решение или отличное решение, но вы сказали «обойти», а не «много работы», поэтому:
std::condition_variable
спать — соответственно)Не очень элегантно, и в этом есть куча накладных расходов — но это имеет смысл, а не какой-то сумасшедший взлом.