Ubuntu: sem_timed, не просыпается (C) - PullRequest
3 голосов
/ 28 мая 2010

У меня есть 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

Ответы [ 6 ]

2 голосов
/ 25 июня 2010

(Извините, что даю второй ответ, но этот будет слишком грязным, чтобы его можно было почистить только при редактировании)

Ответ, я думаю, уже в оригинальном сообщении на вопрос.

Итак, мой вопрос, почему sem3 тайм-аут, хотя семафор был запущен, и значение ясно 1? Я бы никогда не ожидал увидеть строка 08 на выходе. Если время истекло (потому что, скажем, поток 2 потерпел крах или занимает слишком много времени), значение должно быть 0. И почему он работает нормально для 3 или 4 часа, прежде чем попасть в это состояние?

Итак, сценарий:

  1. поток 2 занимает слишком много времени
  2. раз в 3 раза sem_timedwait
  3. поток 3 отменен или что-то еще требуется, чтобы достичь sem_getvalue
  4. нить 2 просыпается и делает свое sem_post на sem3
  5. поток 3 выдает sem_getvalue и видит 1
  6. поток 3 ветвится в неправильном ветвь и не делает его sem_post на sem1

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

Наличие потока управления в зависимости от sem_getvalue, как правило, плохая идея. Единственный атомарный неблокирующий доступ к sem_t - через sem_post и sem_trywait.

Итак, этот пример кода из вопроса имеет условие гонки. Это не означает, что исходный код проблемы, который был у gillez, действительно имеет то же самое состояние гонки. Возможно, пример слишком упрощен и все еще показывает то же самое для него.

Полагаю, в его первоначальной проблеме было незащищенным sem_wait. Это sem_wait, который проверяется только на его возвращаемое значение, а не на errno в случае сбоя. EINTR s происходят на sem_wait вполне естественно, если процесс имеет некоторый ввод-вывод. Вы только что сделали do - while с проверкой и сбросом errno, если встретите EINTR.

1 голос
/ 07 декабря 2011

Кажется, проблема в передаче неверного аргумента тайм-аута.

По крайней мере, на моей машине первый сбой не ETIMEDOUT, а:

!!!!!! sem2 sem_timedwait не удалось: недопустимый аргумент, val теперь 0

Теперь, если я напишу:

  if (ts.tv_nsec >= 1000000)

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

1 голос
/ 24 июня 2010

Не вините в этом Ubuntu или любой другой дистрибутив :-) Что, безусловно, важнее, так это версия gcc, которую вы используете, 32 или 64 бит и т. Д., Сколько ядер в вашей системе. Поэтому, пожалуйста, дайте немного больше информации. Но, просматривая ваш код, я нашел несколько мест, которые могут привести вас к неожиданному поведению:

  • начинается со старта, кастинг int в void* туда и обратно вы ищу неприятности. Используйте uintptr_t для этого, если вы должны, но здесь вы нет оправдания, чтобы просто передать реальный указатели на значения. &(int){ 1 }, и некоторые более разумные заклинания сделали бы трюк для C99.

  • ts.tv_nsec = (tv.tv_usec + 500000) - еще одна проблема. Правая сторона может иметь ширину, отличную от левой. Do

    ts.tv_nsec = tv.tv_usec; ​​

    ts.tv_nsec + = 500000;

  • Семейство функций sem небезопасно при прерывании. Такие прерывания могут, например, инициироваться IO, поскольку вы выполняете printf и т. Д. Проверка возвращаемого значения для -1 или около того недостаточна, но в этом случае вам следует проверить errno и решить, хотите ли вы повторить попытку. Тогда вам нужно будет пересчитать оставшееся время и все в таком духе, если вы хотите быть точным. Тогда на странице man для sem_timedwait есть список кодов ошибок, которые могут возникнуть, и причины их возникновения.

  • Вы также заключаете вещи из ценностей, которые Вы получаете через sem_getvalue. В многопоточность / многопроцессорность / многопроцессорность среда ваша нить может иметь был внеплановым между возвращением от sem_timedwait и sem_getvalue. В основном вы не можете вывести что-нибудь из этого, переменная просто случайно по значению что вы наблюдаете. Не делайте из этого выводов.

1 голос
/ 05 июня 2010

Это очень интересно. Хотя я не нашел источник ошибки (все еще ищу), я проверил это на Ubuntu 9.04 под управлением Linux 2.6.34.

1 голос
/ 30 мая 2010

Я не имею ни малейшего представления о том, что идет не так, и код выглядит хорошо для меня. Вот некоторые вещи, которые вы могли бы сделать, чтобы получить дополнительную информацию.

  • используйте другой тайм-аут, короче и длиннее, и посмотрите, не возникла ли ваша проблема.
  • используйте не синхронизированную версию и посмотрите, зависает ли программа.
  • попробуйте изменить поведение вашего планировщика ядра, например, используя параметры командной строки ядра или procfs или sysfs.

Как указал Йенс, есть две расы:

Первый - когда при оценке значения семафора после вызова sem_timedwait. Это не меняет поток управления с семафором. Вне зависимости от того, истек ли тайм-аут потока, он все равно проходит через блок «должен ли я запустить следующий поток».

Вторая часть находится в части «Должен ли я пробудить следующий поток». У нас могут быть следующие события:

  1. Threads n вызывает sem_getvalue(trigger) и получает 1
  2. Поток n + 1 возвращается из sem_timedwait и семафор переходит к 0
  3. Тема n решает не публиковать, и семафор остается на 0

Теперь я не вижу, как это может вызвать наблюдаемое поведение. В конце концов, поскольку поток n + 1 в любом случае активируется, он, в свою очередь, активирует поток n + 2, который активирует поток n и т. Д ...

Хотя можно получить глюки, я не понимаю, как это может привести к систематическому тайм-ауту потока.

0 голосов
/ 28 июня 2010

Я сделал снимок программы на моей машине с Ubuntu 10.04 x86_64 Core i7.

При работе с usleep (40000) программа работала нормально в течение получаса или чего-то скучного.

При работе с usleep (40) программа работала нормально еще полчаса, а может и больше, прежде чем моя машина зависла. Х умер. Контроль + alt + F1-7 умер. Я не смог войти в систему. (К сожалению, на этой глупой клавиатуре Apple нет клавиши sysrq. Мне нравится печатать на ней, но мне точно не нужны f13, f14 или f15. Я бы сделал ужасные вещи, чтобы получить правильный ключ sysrq.)

И самое лучшее: НИЧЕГО в моих журналах не говорит мне, что произошло.

$ uname -a
Linux haig 2.6.32-22-generic #36-Ubuntu SMP Thu Jun 3 19:31:57 UTC 2010 x86_64 GNU/Linux

В то же время я также играл в Java-игру в браузере (которую написал другой пользователь stackoverflow, ищущий обратную связь, интересное развлечение :) - так что вполне возможно, что jvm отвечает за то, что что-то щекочет, чтобы заморозить мою машину. .

...