Проблема с синхронизацией pThread - PullRequest
3 голосов
/ 28 апреля 2011

Я столкнулся с проблемой синхронизации с pthread.threadWaitFunction1, является функцией ожидания потока.Я ожидаю, что строка нет.247 flag = 1 должен быть выполнен только после окончания 243-246.Но я нахожу странным, что иногда он переходит прямо к 247 до того, как 243-246 закончится.

Пожалуйста, помогите мне.

Заранее спасибо.

236   struct timespec timeToWait;
237   static void* threadWaitFunction1(void *timeToWaitPtr)
238   {
239       cout << "Setting flag =0 inside threadWaitFunction1\n";
240       
241       cout << "Inside threadWaitFunction\n";
242       struct timespec *ptr = (struct timespec*) timeToWaitPtr;
243       pthread_mutex_lock(&timerMutex);
          flag = 0;
244       pthread_cond_timedwait(&timerCond, &timerMutex, ptr);
          flag=1;
245       pthread_mutex_unlock(&timerMutex);
246       cout << "Setting flag =1 inside threadWaitFunction1\n";
247       
248
249    }

поток, который создает и вызывает вышеупомянутый поток:

263  static void timer_trackStartTime ()
264  {
265       struct timeval now;
266       pthread_t thread;
267       
268       printf("Inside trackStartTime: flag = %d\n",flag);
269 
270      /* Setting timer expiration */
271       timeToWait.tv_sec = lt_leak_start_sec;;  // First expiry after 1 sec
272       timeToWait.tv_nsec = lt_leak_start_nsec;
273       pthread_create(&thread, NULL, threadWaitFunction1, &timeToWait);
274       pthread_join(thread, NULL);
275       //pthread_kill(thread, SIGKILL); // Destroying the thread to ensure no leaks
276 
.
.
283       }

Если я защищаю всю функцию с помощью pthread_mutex_lock, но проблема все еще остается.Как обеспечить аккуратное исполнение?Кто-нибудь может помочь?

РЕДАКТИРОВАТЬ: now.tv_sec и now.tv_nsec удалены из кода. * EDIT: Изменены флаги внутри мьютекса (все еще не работает) *

Ответы [ 5 ]

5 голосов
/ 01 мая 2011

Так что на самом деле это не порядок выполнения (что наиболее вероятно правильно), а время, которое делает вас несчастными.И в разделе «он переходит непосредственно к 247 до того, как 243-246 закончил», вы имеете в виду «я наблюдал, как он выполняет 247 до того, как истечет время ожидания в 244».Правильно?

Тогда, я подозреваю, что это проблема ложного пробуждения : поток может быть разбужен, даже если никакой другой поток не сигнализировал переменную условия. Спецификация pthread_cond_timedwait() говорит о том, что «могут произойти ложные пробуждения от функций pthread_cond_timedwait () или pthread_cond_wait ()».

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

ДОБАВЛЕНО: Поскольку вы, кажется, довольны usleep() и только спросили, почему pthread_cond_timedwait() не работает, как вы ожидали, я решил не публиковать код.Если вам это нужно, вы можете использовать ответ @ Hasturkun.


ADDED-2: вывод в комментариях ниже (полученный после применения решения Hasturkun) предполагает, что ожидающий поток не завершает работуцикл, который, вероятно, означает, что pthread_cond_timedwait() возвращает что-то отличное от ETIMEDOUT.Вы видели комментарий @nos к вашему сообщению (я установил количество наносек для вычитания):

Убедитесь (now.tv_usec * 1000) + lt_leak_start_nsec;не переполняетсяВы можете установить tv_nsec только на максимум 999999999, если выражение больше этого, вы должны вычесть 1000000000 из tv_nsec и увеличить tv_sec на 1. Если ваш timeToWaitPtr содержит недопустимый tv_nsec (больше, чем 999999999), pthread_cond_timedwait потерпит неудачу (вы должны проверитьего возвращаемое значение тоже.) - nos 28 апреля в 19: 04

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


ADDED-3: Теперь после того, как вы изменили код в своем вопросе, чтобы передатьТайм-аут без добавления к текущему времени, у него есть другая проблема.Как указано в спецификации , время ожидания для pthread_cond_timedwait() является абсолютным, а не относительным;поэтому, когда вы передаете что-то вроде 3 секунд в качестве тайм-аута, оно интерпретируется как «3 секунды с момента отсчета системного времени».Этот момент почти наверняка пропущен некоторое время, и поэтому pthread_cond_timedwait() немедленно возвращается.
Я бы порекомендовал вам внимательно прочитать спецификацию (включая обоснование), чтобы лучше понять, как эта функция должна использоваться.

3 голосов
/ 28 апреля 2011

Пол Э. МакКенни написал книгу под названием «Трудно ли параллельное программирование, и, если да, что вы можете с этим поделать?» , в которой содержится действительно хорошая информация (и несколько хороших картинок) обарьеры памяти.

Возвращаясь к вашему вопросу, flag ничем не защищен.Хотя вы можете подумать, что pthread_mutex_lock() и pthread_mutex_unlock предоставляют некоторые строгие гарантии упорядочения и видимости, единственные гарантии, которые он предоставляет, - это доступ внутри критической области и сам мьютекс.

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

Редактировать:

По-видимому, я был неправ в отношении pthreads, они, кажется, требуют полных барьеров памяти (если вы интерпретируете синхронизацию памяти с другими потоками как требующую этого).Подробнее об этом и некоторую информацию о гарантиях, предоставленных в реальных реализациях на Изменение порядка ограничений для блокировок в стиле Pthread Хансом Бемом.

Мне также все еще интересно узнать о NPTL на IA64 1 , 2 .

1 голос
/ 01 мая 2011

Как заявил Алексей Куканов, проблема скорее всего в ложном пробуждении.Ваш код может быть исправлен в цикле до истечения времени ожидания.Обратите внимание, что я также перенес настройку флага в мьютекс.

static void* threadWaitFunction1(void *timeToWaitPtr)
{
    struct timespec *ptr = (struct timespec*) timeToWaitPtr;
    int ret;

    pthread_mutex_lock(&timerMutex);
    cout << "Setting flag =0 inside threadWaitFunction1\n";
    flag=0;
    cout << "Inside threadWaitFunction\n";
    while (pthread_cond_timedwait(&timerCond, &timerMutex, ptr) != ETIMEDOUT)
        ;
    cout << "Setting flag =1 inside threadWaitFunction1\n";
    flag=1;
    pthread_mutex_unlock(&timerMutex);
}

Чтобы быть в безопасности, вы должны проверить флаг под тем же мьютексом, чтобы установить порядок

0 голосов
/ 29 апреля 2011

Просто для всеобщей информации:

Чего я не смог достичь с помощью pthread_cond_timedwait(&timerCond, &timerMutex, ptr); Я достиг с помощью usleep( ), usleep принимает структуру timespec, где мы можем указать период ожидания, используя секунды инаносекунды, и моя цель решена.

Так в чем смысл pthread_cond_timedwait(&timerCond, &timerMutex, ptr); ??Я удивлен, так как ожидается, что этот API заставит вызывающий поток ждать выполнения этого условия, но кажется, что процессор переходит к следующей инструкции в качестве меры оптимизации и не ожидает выполнения условия.

Но, тем не менее, моя проблема остается той же, что и почему pthread_cond_timedwait(&timerCond, &timerMutex, ptr); не должен заставлять вызывающий поток ждать?

Кажется, я потратил целый день за этим API:pthread_cond_timedwait( )

0 голосов
/ 28 апреля 2011

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

Здесь - очень интересная, хотя и довольно техническая и длинная статья о том, как памятьбарьеры работают и что они делают и не делают.Он написан для Linux, но основные принципы остаются теми же.

РЕДАКТИРОВАТЬ:

Блокировка является неявным барьером памяти, по ссылке, которую я дал ранее, поэтому барьер памяти не требуется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...