Pthread_cond_timedwait: проблема, решение, дискуссия

от автора

Здравствуйте, уважаемые Хабраюзеры!

Продолжая серию постов по многопоточному программированию, хочется коснуться одной фундаментальной проблемы использования сигнальных переменных в Linux, к сожалению, не имеющей пока красивого универсального решения (а может оно просто неизвестно мне). Многие, к сожалению, даже не догадываются о том, что такая проблема имеет место быть.

Рассмотрим простой пример использования сигнальной переменной:

struct timeval now; struct timespec timeout; gettimeofday(&now, 0); timeout.tv_sec = now.tv_sec + 2;      // 2 sec timeout.tv_nsec = now.tv_usec * 1000; // nsec  retval=0; pthread_mutex_lock(&mutex); while(!somethingHappens() && retval==0) {     retval=pthread_cond_timedwait(&condition, &mutex, &timeout); } pthread_mutex_unlock(&mutex); 

Смысл использования pthread_cond_timedwait состоит в том, что мы либо дожидаемся получения сигнала (pthread_cond_signal или pthread_cond_broadcast) в качестве уведомления о том, что somethingHappens(), или же прекращаем ожидание по истечении заданного нами таймаута. Во второй части фразы и кроется та самая потенциальная проблема! Обратите внимание, что время, передаваемое в качестве третьего параметра в pthread_cond_timedwait, задается в абсолютном виде! А что, если время будет переведено назад(!) после того, как мы получим текущее время (gettimeofday) и до того, как уснем в ожидании на pthread_cond_timedwait?

Каково будет поведение pthread_cond_timedwait, если наш процесс уже спит на этом вызове? Здесь все чисто! На всех платформах, на которых я проводил эксперимент с переводом времени назад, изменение просто игнорировалось, т.е. реально внутри вызова время все-таки преобразуется из абсолютного к относительному значению. Интересно, почему это не вынесено в интерфейс функции? Это бы решило все проблемы!

Критики могут возразить, что это какая-то пренебрежимо маловероятная ситуация, чтобы перевод системного времени попал именно в этот ничтожно малый кусок кода. Позвольте не согласиться. С одной стороны, если вероятность какого-либо события не равна нулю, то оно обязательно случится (принято называть это «генеральским эффектом»), а с другой стороны все сильно зависит от конкретной программы. Мы столкнулись с этой проблемой при разработке системы видеонаблюдения, а это десятки потоков (thread-ов), в каждом из которых делается pthread_cond_timedwait раз эдак 25 в секунду, а перевод времени на час назад приводил к тому, что с вероятностью, близкой к 100%, какой-нибудь поток да и заснет на этот час плюс 1/25 секунды!

Что делать?

Как я уже сказал в начале своего повествования, красивого решения данной проблемы нет, но и не решать ее вовсе, нельзя! В нашей системе мы организовали отдельный поток, назовем его «потоком мониторинга системного времени», который отслеживает «переводы времени назад» и в случае их выявления «будит» все сигнальные переменные. Т.е. по сути, решение предполагает наличие в системе некоторого выделенного менеджера, в котором необходимо зарегистрировать все используемые сигнальные переменные. Получилось что-то такое:

class SystemTimeManager { public:     SystemTimeManager();     ~SystemTimeManager();      void registerCond(pthread_mutex_t *mutex, pthread_cond_t *cond);     void unregisterCond(pthread_cond_t *cond);  private:     static void *runnable(void *ptr);  private:     time_t _prevSystemTime;     pthread_t _thread;     bool _finish;     pthread_mutex_t _mutex;     std::map<pthread_cond_t *, pthread_mutex_t *> _container; };  SystemTimeManager::SystemTimeManager ():     _prevSystemTime(time(0)),     _finish(false) {     pthread_mutex_create(&_mutex, 0);     pthread_create(&_thread, 0, runnable, this); }  SystemTimeManager::~SystemTimeManager() {     _finish=true;     pthread_join(_thread, 0);     pthread_mutex_destroy(&_mutex); }  void SystemTimeManager::registerCond(pthread_mutex_t *mutex, pthread_cond_t *cond) {     pthread_mutex_lock(&_mutex);     _container.insert(std::make_pair(cond, mutex));     pthread_mutex_unlock(&_mutex); }  void SystemTimeManager::unregisterCond(pthread_cond_t *cond) {     pthread_mutex_lock(&_mutex);     std::map<pthread_cond_t *, pthread_mutex_t *> it=_container.find(cond);     if(it!=_container.end())         _container->erase(it);     pthread_mutex_unlock(&_mutex); }  void * SystemTimeManager::runnable(void *ptr) {     SystemTimeManager *me=reinterpret_cast< SystemTimeManager *>(ptr);     while(!_finish)     {         If(time(0)<_prevSystemTime)         {             pthread_mutex_lock(&me->_mutex);             for(std::map<pthread_cond_t *, pthread_mutex_t *> it=_container.begin();                  it!=_container.end(); ++it)             {                  pthread_mutex_lock(it->second);                  pthread_cond_broadcast(it->first);                  pthread_mutex_unlock(it->second);             }             pthread_mutex_unlock(&me->_mutex);         }         _prevSystemTime=time(0);     } } 

Теперь нам достаточно создать экземпляр класса SystemTimeManager и не забыть зарегистрировать в нем все используемые нами сигнальные переменные.

В заключение хотелось бы обратить внимание уважаемого сообщества на тему данной статьи «проблема, решение, дискуссия». Проблему, я надеюсь, описал достаточно понятно. Решение описанной проблемы, пусть и не самое элегантное, я привел – надеюсь, оно кому-нибудь будет полезно. Однако последнее – дискуссию — я никак не могу сделать без вас, уважаемые Хабраюзеры. Может быть, у кого-то есть какие-то другие, более элегантные решения данной проблемы?

ссылка на оригинал статьи http://habrahabr.ru/company/nordavind/blog/175821/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *