У меня есть 3 процесса, которые нужно синхронизировать. Процесс один делает что-то, затем пробуждает процесс два и спит, который что-то делает, затем пробуждает процесс три и спит, который что-то делает и пробуждает процесс один и спит. Весь цикл рассчитан на работу с частотой около 25 Гц (вызванная внешней синхронизацией в первом процессе, прежде чем он запускает второй процесс в моем «реальном» приложении). Я использую sem_post для запуска (пробуждения) каждого процесса и sem_timedwait () для ожидания запуска.
Это все успешно работает в течение нескольких часов. Однако в какое-то случайное время (обычно после двух-четырех часов) один из процессов начинает тайм-аут в sem_timedwait (), хотя я уверен, что семафор запускается с помощью sem_post (). Чтобы доказать это, я даже использую sem_getvalue () сразу после тайм-аута, и значение равно 1, поэтому время ожидания должно было быть запущено.
Пожалуйста, смотрите следующий код:
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>
sem_t trigger_sem1, trigger_sem2, trigger_sem3;
// The main thread process. Called three times with a different num arg - 1, 2 or 3.
void *thread(void *arg)
{
int num = (int) arg;
sem_t *wait, *trigger;
int val, retval;
struct timespec ts;
struct timeval tv;
switch (num)
{
case 1:
wait = &trigger_sem1;
trigger = &trigger_sem2;
break;
case 2:
wait = &trigger_sem2;
trigger = &trigger_sem3;
break;
case 3:
wait = &trigger_sem3;
trigger = &trigger_sem1;
break;
}
while (1)
{
// The first thread delays by 40ms to time the whole loop.
// This is an external sync in the real app.
if (num == 1)
usleep(40000);
// print sem value before we wait. If this is 1, sem_timedwait() will
// return immediately, otherwise it will block until sem_post() is called on this sem.
sem_getvalue(wait, &val);
printf("sem%d wait sync sem%d. val before %d\n", num, num, val);
// get current time and add half a second for timeout.
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec;
ts.tv_nsec = (tv.tv_usec + 500000); // add half a second
if (ts.tv_nsec > 1000000)
{
ts.tv_sec++;
ts.tv_nsec -= 1000000;
}
ts.tv_nsec *= 1000; /* convert to nanosecs */
retval = sem_timedwait(wait, &ts);
if (retval == -1)
{
// timed out. Print value of sem now. This should be 0, otherwise sem_timedwait
// would have woken before timeout (unless the sem_post happened between the
// timeout and this call to sem_getvalue).
sem_getvalue(wait, &val);
printf("!!!!!! sem%d sem_timedwait failed: %s, val now %d\n",
num, strerror(errno), val);
}
else
printf("sem%d wakeup.\n", num);
// get value of semaphore to trigger. If it's 1, don't post as it has already been
// triggered and sem_timedwait on this sem *should* not block.
sem_getvalue(trigger, &val);
if (val <= 0)
{
printf("sem%d send sync sem%d. val before %d\n", num, (num == 3 ? 1 : num+1), val);
sem_post(trigger);
}
else
printf("!! sem%d not sending sync, val %d\n", num, val);
}
}
int main(int argc, char *argv[])
{
pthread_t t1, t2, t3;
// create semaphores. val of sem1 is 1 to trigger straight away and start the whole ball rolling.
if (sem_init(&trigger_sem1, 0, 1) == -1)
perror("Error creating trigger_listman semaphore");
if (sem_init(&trigger_sem2, 0, 0) == -1)
perror("Error creating trigger_comms semaphore");
if (sem_init(&trigger_sem3, 0, 0) == -1)
perror("Error creating trigger_vws semaphore");
pthread_create(&t1, NULL, thread, (void *) 1);
pthread_create(&t2, NULL, thread, (void *) 2);
pthread_create(&t3, NULL, thread, (void *) 3);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
}
Следующий вывод печатается, когда программа работает правильно (в начале и в течение случайного, но долгого времени после). Значение sem1 всегда равно 1, прежде чем thread1 ждет, пока он спит в течение 40 мс, к которому время sem3 его запустило, поэтому оно сразу же просыпается. Два других потока ожидают получения семафора из предыдущего потока.
[...]
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
sem2 wakeup.
sem2 send sync sem3. val before 0
sem2 wait sync sem2. val before 0
sem3 wakeup.
sem3 send sync sem1. val before 0
sem3 wait sync sem3. val before 0
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
[...]
Однако через несколько часов один из потоков начинает время ожидания. Я могу видеть из вывода, что семафор запускается, и когда я печатаю значение после тайм-аута, оно равно 1. Так что sem_timedwait должен был проснуться задолго до тайм-аута. Я никогда не ожидал бы, что значение семафора будет равно 1 после тайм-аута, за исключением очень редкого случая (почти наверняка никогда, но это возможно), когда триггер происходит после тайм-аута, но до того, как я вызову sem_getvalue.
Кроме того, как только он начинает терпеть неудачу, каждый sem_timedwait () на этом семафоре также терпит неудачу таким же образом. Смотрите следующий вывод, который я пронумерован:
01 sem3 wait sync sem3. val before 0
02 sem1 wakeup.
03 sem1 send sync sem2. val before 0
04 sem2 wakeup.
05 sem2 send sync sem3. val before 0
06 sem2 wait sync sem2. val before 0
07 sem1 wait sync sem1. val before 0
08 !!!!!! sem3 sem_timedwait failed: Connection timed out, val now 1
09 sem3 send sync sem1. val before 0
10 sem3 wait sync sem3. val before 1
11 sem3 wakeup.
12 !! sem3 not sending sync, val 1
13 sem3 wait sync sem3. val before 0
14 sem1 wakeup.
[...]
В строке 1 поток 3 (который я смущенно назвал sem3 в printf) ожидает запуска sem3. В строке 5 thread2 вызывает sem_post для sem3. Тем не менее, строка 8 показывает тайм-аут sem3, но значение семафора равно 1. thread3 затем запускает sem1 и снова ждет (10). Однако, поскольку значение уже равно 1, оно сразу просыпается. Он не отправляет sem1 снова, поскольку все это произошло до того, как управление передается thread1, однако затем он снова ждет (val теперь 0) и sem1 активируется. Теперь это повторяется навсегда, sem3 всегда отключается и показывает, что значение равно 1.
Итак, мой вопрос: почему тайм-аут sem3, несмотря на то, что семафор сработал и значение явно 1? Я бы никогда не ожидал увидеть строку 08 в выводе. Если время ожидания истекло (например, из-за того, что поток 2 вышел из строя или занимает слишком много времени), значение должно быть равно 0. И почему сначала он хорошо работает в течение 3 или 4 часов, прежде чем войти в это состояние?
Я пытался провести аналогичный тест, используя три отдельные программы, взаимодействующие через общую память, а не три потока в одной программе. Это больше похоже на мое приложение в реальном мире. Результаты и результаты были одинаковыми. Проблема, по-видимому, связана с семафором (особенно с вызовом sem_timedwait), а не с тем, что связано с pthreads.
Я также пытался использовать более короткие и длительные задержки, а также полностью удалить задержку, с результатами, аналогичными описанным выше. Без задержки он может начать выдавать ошибку через несколько минут, а не часов. Это, конечно, означает, что проблема может быть воспроизведена намного быстрее.
Это использует Ubuntu 9.4 с ядром 2.6.28. Та же процедура работала правильно на Redhat и Fedora, но сейчас я пытаюсь портировать на Ubuntu. Я также попытался использовать Ubuntu 9.10, который не имел никакого значения.
Спасибо за любой совет,
Giles