2015-05-26 1 views
2

Изучая возможность улучшения производительности Recoll с помощью vfork() вместо fork(), я столкнулся с проблемой fork(), которую я не могу объяснить.Резьба, выполняемая между fork и exec блокирует другую нить, прочитанную

Recoll неоднократно выполняет внешние команды для перевода файлов, так что это то, что делает программа-образец: он запускает потоки, которые повторно выполняют «ls» и считывают вывод.

Следующая проблема не является «реальной» в том смысле, что фактическая программа не будет делать то, что вызывает проблему. Я просто наткнулся на это, посмотрев, какие потоки были остановлены или нет между fork()/vfork() и exec().

Когда у меня есть один из потоков, занятых циклом между fork() и exec(), другой поток никогда не завершает чтение данных: последний read(), который должен указывать eof, заблокирован навсегда или до тех пор, пока другой конец цикла потока (в этот момент все возобновляется нормально, что можно увидеть, заменив бесконечный цикл на тот, который завершается). В то время как read() заблокирован, команда «ls» вышла (ps показывает < несуществующий >, зомби).

Существует случайный аспект проблемы, но образец программы «преуспевает» большую часть времени. Я тестировал с ядрами Linux 3.2.0 (Debian), 3.13.0 (Ubuntu) и 3.19 (Ubuntu). Работает на виртуальной машине, но вам нужно как минимум 2 procs, я не мог заставить ее работать с одним процессором.

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

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 
#include <memory.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <pthread.h> 
#include <iostream> 

using namespace std; 

struct thread_arg { 
    int tnum; 
    int loopcount; 
    const char *cmd; 
}; 

void* task(void *rarg) 
{ 
    struct thread_arg *arg = (struct thread_arg *)rarg; 
    const char *cmd = arg->cmd; 

    for (int i = 0; i < arg->loopcount; i++) { 
     pid_t pid; 
     int pipefd[2]; 

     if (pipe(pipefd)) { 
      perror("pipe"); 
      exit(1); 
     } 
     pid = fork(); 
     if (pid) { 
      cerr << "Thread " << arg->tnum << " parent " << endl; 
      if (pid < 0) { 
       perror("fork"); 
       exit(1); 
      } 
     } else { 
      // Child code. Either exec ls or loop (thread 1) 
      if (arg->tnum == 1) { 
       cerr << "Thread " << arg->tnum << " looping" <<endl; 
       for (;;); 
       //for (int cc = 0; cc < 1000 * 1000 * 1000; cc++); 
      } else { 
       cerr << "Thread " << arg->tnum << " child" <<endl; 
      } 

      close(pipefd[0]); 
      if (pipefd[1] != 1) { 
       dup2(pipefd[1], 1); 
       close(pipefd[1]); 
      } 
      cerr << "Thread " << arg->tnum << " child calling exec" << 
       endl; 
      execlp(cmd, cmd, NULL); 
      perror("execlp"); 
      _exit(255); 
     } 

     // Parent closes write side of pipe 
     close(pipefd[1]); 
     int ntot = 0, nread; 
     char buf[1000]; 
     while ((nread = read(pipefd[0], buf, 1000)) > 0) { 
      ntot += nread; 
      cerr << "Thread " << arg->tnum << " nread " << nread << endl; 
     } 
     cerr << "Total " << ntot << endl; 

     close(pipefd[0]); 
     int status; 
     cerr << "Thread " << arg->tnum << " waiting for process " << pid 
      << endl; 
     if (waitpid(pid, &status, 0) != -1) { 
      if (status) { 
       cerr << "Child exited with status " << status << endl; 
      } 
     } else { 
      perror("waitpid"); 
     } 
    } 

    return 0; 
} 

int main(int, char **) 
{ 
    int loopcount = 5; 
    const char *cmd = "ls"; 

    cerr << "cmd [" << cmd << "]" << " loopcount " << loopcount << endl; 

    const int nthreads = 2; 
    pthread_t threads[nthreads]; 

    for (int i = 0; i < nthreads; i++) { 
     struct thread_arg *arg = new struct thread_arg; 
     arg->tnum = i; 
     arg->loopcount = loopcount; 
     arg->cmd = cmd; 
     int err; 
     if ((err = pthread_create(&threads[i], 0, task, arg))) { 
      cerr << "pthread_create failed, err " << err << endl; 
      exit(1); 
     } 
    } 

    void *status; 
    for (int i = 0; i < nthreads; i++) { 
     pthread_join(threads[i], &status); 
     if (status) { 
      cerr << "pthread_join: " << status << endl; 
      exit(1); 
     } 
    } 
} 
+0

Мне понравилось решать вашу головоломку :) – Celada

ответ

2

Что происходит, так это то, что ваши трубы наследуются как дочерними процессами, а не только одним.

То, что вы хотите сделать, это:

  1. Создать трубу с 2 концами
  2. fork(), ребенок наследует оба конца трубы
  3. ребенок закрывает читать конец, родитель закрывает конец записи

... так что ребенок заканчивается только одним концом одной трубы, которая равна dup2() 'ed to stdout.

Но ваши темы расы друг с другом, так что может случиться, это:

  1. Thread 1 создает трубу с 2 концами
  2. Thread 0 создает трубу с 2 концами
  3. темы 1 fork() с. Детский процесс унаследовал 4 дескриптора файла, а не 2!
  4. Ребенок Thread 1 закрывает считываемый конец трубы, который открывается нитью 1, но он сохраняет ссылку на конец считывания и записывает конец трубы нити 0.

Позже нить 0 ждет навсегда, потому что она никогда не получает EOF на трубе, которую она читает, потому что конец записи этой трубки все еще удерживается дочерним элементом нити 1.

Вам нужно будет определить критическую секцию, которая начинается до того pipe(), огибает fork(), и заканчивается после того, как close() в родителю, и войти в этот критический участок от только один поток в то время, используя семафор.

+0

Ничего себе, блестящий. 30 Years of Unix, и я не видел этого ... – medoc

+0

Просто тривиальное замечание: критический раздел должен заканчиваться после закрытия родителя, а не после вилки, иначе другой ребенок все еще может наследовать fd. – medoc

+0

Вы правы в 'close()', я отредактировал свой ответ. – Celada

Смежные вопросы