Проблема с синхронизацией pThread
Я столкнулся с проблемой синхронизации с 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 удалены из кода. * РЕДАКТИРОВАТЬ: Изменены флаги внутри мьютекса (по-прежнему не работает) *
5 ответов
Так что на самом деле это не порядок выполнения (что наиболее вероятно правильно), а время, которое делает вас несчастными. А в разделе "он переходит непосредственно к 247 до того, как 243-246 закончил", вы имеете в виду "я наблюдал, как он выполняет 247 до того времени, которое должно ждать в 244". Правильно?
Затем, я подозреваю, что это проблема ложного пробуждения: поток может проснуться, даже если никакой другой поток не сигнализировал переменную условия. Спецификацияpthread_cond_timedwait()
говорит, что "могут произойти ложные пробуждения от функций pthread_cond_timedwait() или pthread_cond_wait()".
Обычно условная переменная связана с определенным событием в приложении, и поток, ожидающий условную переменную, фактически ожидает от другого потока сигнала о том, что произошло интересующее событие. Если у вас нет события и вы просто хотите подождать определенное время, то есть другие способы, такие как usleep()
или таймеры, более подходящие, за исключением случаев, когда вам также нужен пункт отмены.
ДОБАВЛЕНО: Так как вы, кажется, довольны usleep()
и только спросил, почему pthread_cond_timedwait()
не оправдала ваших ожиданий, я решил не размещать код. Если вам это нужно, вы можете использовать ответ @Hasturkun.
ДОБАВЛЕНО-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 потерпит неудачу (вы должны проверить его возвращаемое значение тоже.) - № 28 апреля в 19:04
В этом случае, pthread_cond_timedwait()
будет многократно возвращаться EINVAL
и никогда не выйдет из цикла. Лучше настроить время ожидания перед входом в цикл ожидания, хотя это также можно сделать в ответ на EINVAL
,
ДОБАВЛЕНО-3: Теперь, после того, как вы изменили код в своем вопросе, чтобы передать тайм-аут, не добавляя к текущему времени, у него есть другая проблема. Как указано в спецификации, время ожидания для pthread_cond_timedwait()
абсолютное время, не относительное; поэтому, когда вы передаете что-то вроде 3 секунд в качестве тайм-аута, оно интерпретируется как "3 секунды с момента отсчета системного времени". Этот момент почти наверняка прошел некоторое время, и поэтому pthread_cond_timedwait()
возвращается немедленно.
Я бы порекомендовал вам внимательно прочитать спецификацию (включая обоснование), чтобы лучше понять, как предполагается использовать эту функцию.
Пол Э. МакКенни написал книгу под названием "Трудно ли параллельное программирование, и, если да, что вы можете с этим поделать?", который имеет действительно хорошую информацию (и некоторые хорошие картинки) о барьерах памяти.
Вернемся к вашему вопросу, flag
ничем не защищен. Хотя вы можете думать, что pthread_mutex_lock()
а также pthread_mutex_unlock
обеспечивает некоторые строгие гарантии упорядочения и видимости, единственные гарантии, которые он предоставляет, - это доступ внутри критической области и сам мьютекс.
Более того, на некоторых архитектурах pthread_mutex_lock()
использует барьер приобретения и pthread_mutex_unlock()
использует барьер освобождения, что означает, что доступ до и после защищенной области мьютекса может разлиться в защищенную область мьютекса. Строгие гарантии упорядочения предоставляются между процессором, освобождающим мьютекс, и другим процессором, приобретающим тот же мьютекс, но почти все остальное не нуждается (и, возможно, не получает) в таких сильных гарантиях.
Редактировать:
Очевидно, я был неправ в отношении pthreads, они, кажется, требуют полных барьеров памяти (если вы интерпретируете синхронизацию памяти относительно других потоков как требующую этого). Подробнее об этом и некоторую информацию о гарантиях, предоставленных в реальных реализациях в разделе "Переупорядочение ограничений для блокировок в стиле Pthread" Ханса Бома.
Как заявил Алексей Куканов, проблема скорее всего в ложном пробуждении. Ваш код может быть исправлен в цикле до истечения времени ожидания. Обратите внимание, что я также перенес настройку флага на мьютекс.
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);
}
Чтобы быть в безопасности, вы должны проверить флаг под тем же мьютексом, чтобы установить порядок
Просто для всеобщей информации:
Чего я не смог добиться, используя pthread_cond_timedwait(&timerCond, &timerMutex, ptr);
я достиг с помощью usleep( )
спит берет timespec
структура, в которой мы можем указать период ожидания, используя секунды и наносекунды, и моя цель решена.
Так что же pthread_cond_timedwait(&timerCond, &timerMutex, ptr);
имеет смысл для?? Я удивлен, так как ожидается, что этот API заставит вызывающий поток ждать выполнения этого условия, но кажется, что процессор переходит к следующей инструкции в качестве меры оптимизации и не ожидает выполнения условия.
Но все же моя проблема остается той же, что и почему, pthread_cond_timedwait(&timerCond, &timerMutex, ptr);
не должен заставить вызывающий поток ждать?
Кажется, я потратил целый день за этим API: pthread_cond_timedwait( )
Это может быть потому, что компилятор оптимизировал вещи и поместил ваше присвоение вашему флагу перед мьютексом потока. Если вы хотите гарантировать порядок выполнения (что обычно не гарантируется, только при условии, что видимое поведение вашей программы не изменяется из-за оптимизации), вы используете барьер памяти, чтобы убедиться, что инструкции, которые вы хотите выполнить выполняются в том порядке, в котором вы их пишете, выполняются только в этом порядке.
Вот очень интересная, хотя и довольно техническая и длинная статья о том, как работают барьеры памяти и что они делают и не делают. Он написан для Linux, но основные принципы остаются прежними.
РЕДАКТИРОВАТЬ:
Блокировка является неявным барьером памяти, по ссылке, которую я дал ранее, поэтому барьер памяти не требуется.