Избегать состояния гонки () / SIGCHLD - PullRequest
14 голосов
/ 04 декабря 2008

Обратите внимание на следующий fork() / SIGCHLD псевдокод.

  // main program excerpt
    for (;;) {
      if ( is_time_to_make_babies ) {

        pid = fork();
        if (pid == -1) {
          /* fail */
        } else if (pid == 0) {
          /* child stuff */
          print "child started"
          exit
        } else {
          /* parent stuff */
          print "parent forked new child ", pid
          children.add(pid);
        }

      }
    }

  // SIGCHLD handler
  sigchld_handler(signo) {
    while ( (pid = wait(status, WNOHANG)) > 0 ) {
      print "parent caught SIGCHLD from ", pid
      children.remove(pid);
    }
  }

В приведенном выше примере есть условие гонки. Возможно, что «/* child stuff */» завершится до запуска «/* parent stuff */», что может привести к тому, что pid ребенка будет добавлен в список детей после его выхода и никогда не будет удален. Когда придет время закрывать приложение, родитель будет бесконечно ждать окончания работы уже готового потомка.

Одним из решений, которое я могу придумать, чтобы противостоять этому, является наличие двух списков: started_children и finished_children. Я бы добавил к started_children в том же месте, которое сейчас добавляю к children. Но в обработчике сигналов вместо удаления из children я бы добавил к finished_children. Когда приложение закрывается, родитель может просто подождать, пока разница между started_children и finished_children не станет равной нулю.

Другое возможное решение, о котором я могу подумать, - это использование разделяемой памяти, например, поделитесь списком детей родителей и пусть дети .add и .remove сами? Но я не слишком много знаю об этом.

РЕДАКТИРОВАТЬ: Другое возможное решение, которое было первым, что пришло в голову, это просто добавить sleep(1) в начале /* child stuff */, но это пахнет смешно для меня, именно поэтому я пропустил его. Я также даже не уверен, что это 100% исправление.

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

Спасибо.

Ответы [ 4 ]

15 голосов
/ 04 декабря 2008

Самое простое решение - заблокировать сигнал SIGCHLD до fork() с помощью sigprocmask() и разблокировать его в родительском коде после обработки pid.

Если ребенок умер, после того, как вы разблокируете сигнал, будет вызван обработчик сигнала для SIGCHLD. Это концепция критического раздела - в вашем случае критический раздел начинается до fork() и заканчивается после children.add().

0 голосов
/ 02 апреля 2012

Может быть, оптимистичный алгоритм? Попробуйте children.remove (pid), и, если это не удастся, продолжайте жить.

Или проверьте, что pid у детей, прежде чем пытаться его удалить?

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

Если вы не можете использовать критический фрагмент, возможно, простой счетчик может сделать эту работу. +1 при добавлении, -1 при удалении, независимо от того, что произойдет первым, вы можете получить ноль, когда все будет сделано.

0 голосов
/ 04 декабря 2008

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

  // main program excerpt
    for (;;) {
      if ( is_time_to_make_babies ) {

        pid = fork();
        if (pid == -1) {
          /* fail */
        } else if (pid == 0) {
          /* child stuff */
          print "child started"
          exit
        } else {
          /* parent stuff */
          print "parent forked new child ", pid
          if (!earlyDeaths.contains(pid)) {
              children.add(pid);
          } else {
              earlyDeaths.remove(pid);
          }
        }

      }
    }

  // SIGCHLD handler
  sigchld_handler(signo) {
    while ( (pid = wait(status, WNOHANG)) > 0 ) {
      print "parent caught SIGCHLD from ", pid
      if (children.contains(pid)) {
          children.remove(pid);
      } else {
          earlyDeaths.add(pid);
      }
    }
  }

РЕДАКТИРОВАТЬ: это может быть упрощено, если ваш процесс однопоточный - earlyDeaths не должен быть контейнером, он просто должен содержать один pid.

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