Parellel обрабатывает, используя fork () с параметрами командной строки в C - PullRequest
2 голосов
/ 13 февраля 2020

Я пытаюсь создать программу, которая принимает в командной строке несколько подсчетов и выполняет их параллельно.

У меня есть один счетчик. c файл, который используется для подсчета:

int main(int argc, char** argv)
{
   assert(argc>1);

   int length= atoi(argv[1]); 
   assert(length>0);

   int pid = getpid(); 

   printf("%d : %s\n", pid, "start");

    for (unsigned int i=length; i>0; i--)
    {
      printf("%d : %d\n", pid, i);

      sleep(1); 
    }

    printf("%d : %s\n", pid, "done");


    return 0;
}

, поэтому, если я введу ". / count 5 " в bash, программа считает от 5 до 1.

У меня есть еще один , кратный. c файл:

int main(int argc, char** argv)
{
   assert(argc>2);
   unsigned int i;

   char * nameExec = (char*) malloc(sizeof(char)*(strlen(argv[1])-1));

   char * time;

   int number = argc-2; // number of counting to do

    // name of programm
    for (i=2; i<strlen(argv[1]); i++)
    {
      nameExec[i-2]=argv[1][i];
    }
    nameExec[i-2]='\0';


    if(number==1) //  one counting there is no fork needed
    {
      execl(argv[1], nameExec, argv[2], NULL);
    } else
    {
      for (unsigned int i=2; i<number+1; i++) // if we have 2 counts to do, then we need 1 fork, if there is 3 counts to do then there is 2 forks...
      {
        if(fork()==0) // child process
        {
          time = argv[i];
        } else
        {
          time = argv[i+1];
          wait(NULL); // father process waits for child
        }

      }

      execl(argv[1], nameExec, time, NULL);

    }

    return 0;
}

что я хочу сделать с этой программой, так это то, что я ввожу в командную строку, которую я ввожу, например, " ./multiple ./count 5 4 3"и что он запускает 3 счета параллельно (3 параллельных процесса).

Проведенные мною проверки: Если я введу ./multiple ./count 5 4 выполняет два счета, один начинается с 5, а другой с 4, но не одновременно, один за другим . Если я введу . / Множественный ./count 5 4 3 , он сделает 4 счета, один, начиная с 4, затем один, начиная с 3, затем еще один, начиная с 4, и еще один, начиная с 3.

Я действительно не понимаю этого поведения , насколько я понимаю, fork () используется для дублирования процесса, и execl прекращает текущий процесс и начинает выполнение другого.

Пожалуйста, помогите!

(Кроме того, я пытаюсь понять использование fork () и execl (), поэтому я хотел бы найти способ ответить на мою проблему с помощью этих двух функций).

Ответы [ 3 ]

2 голосов
/ 14 февраля 2020

Ваш исходный код запускает дочерние процессы в последовательности, а не одновременно, потому что у вас есть вызов wait() внутри l oop.

Вам не нужно копировать имя программы. Вы можете либо использовать argv[1] напрямую (или просто присвоить его nameExec), либо пропустить первые пару символов, используя nameExec = &argv[1][2];.

Очень сложно понять, как работает l oop в вашем коде; это заставило меня несколько раз закричать, когда я пытался обернуть это вокруг себя. Я собираюсь просто написать код с нуля - в двух вариантах.

Вариант 1

В более простом для понимания варианте родительский (начальный) процесс запускает по одному дочернему элементу на каждый счетчик, а затем он ждет, пока не останется детей. Он сообщает PID и статус выхода детей при их выходе; было бы целесообразно просто собрать трупы, не печатая «в память».

/* SO 6021-0236 */
/* Variant 1: Original process forks children and waits for them to complete */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);

    /* Launch children */
    for (int i = 2; i < argc; i++)
    {
        if (fork() == 0)     // child process
        {
            execl(argv[1], argv[1], argv[i], (char *)0);
            fprintf(stderr, "failed to execute %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    }

    /* Wait for children */
    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
    {
        printf("%d: PID %d exited with status 0x%.4X\n",
               (int)getpid(), corpse, status);
    }

    return 0;
}

Я переименовал вашу программу-счетчик, чтобы исходный файл был counter23.c, а программа - counter23 и единственный другой значительные изменения удалили пробел перед двоеточием в выводе printf().

Я назвал исходный код выше multiple43.c, скомпилированный в multiple43.

$ multiple43 count23 1
54251: start
54251: 1
54251: done
54250: PID 54251 exited with status 0x0000
$ multiple43 count23 3 4 5
54261: start
54261: 5
54260: start
54260: 4
54259: start
54259: 3
54261: 4
54260: 3
54259: 2
54261: 3
54260: 2
54259: 1
54261: 2
54260: 1
54259: done
54258: PID 54259 exited with status 0x0000
54261: 1
54260: done
54258: PID 54260 exited with status 0x0000
54261: done
54258: PID 54261 exited with status 0x0000
$

В прогоне с три ребенка, вы можете видеть, что все три производят вывод одновременно.

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

Вариант 2

Другой вариант более или менее приближает ваш код (хотя это приближение не очень хорошее) в том смысле, что сам исходный процесс тоже выполняет программу счетчика. Следовательно, если исходный процесс имеет меньше циклов, чем другие, он завершается до завершения других (см. Разницу между примерами 3 4 5 и 5 4 3). Однако счетчики запускаются одновременно.

/* SO 6021-0236 */
/* Variant 2: Original process launches children, the execs itself */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);

    /* Launch children */
    for (int i = 3; i < argc; i++)
    {
        if (fork() == 0)     // child process
        {
            execl(argv[1], argv[1], argv[i], (char *)0);
            fprintf(stderr, "failed to execute %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    }

    execl(argv[1], argv[1], argv[2], (char *)0);
    fprintf(stderr, "failed to execute %s\n", argv[1]);
    return(EXIT_FAILURE);
}

Этот код был multiple53.c скомпилирован в multiple53.

$ multiple53 count23 3 4 5
54269: start
54268: start
54267: start
54269: 5
54268: 4
54267: 3
54269: 4
54268: 3
54267: 2
54268: 2
54267: 1
54269: 3
54268: 1
54267: done
54269: 2
$ 54268: done
54269: 1
54269: done

$ multiple53 count23 5 4 3
54270: start
54272: start
54270: 5
54272: 3
54271: start
54271: 4
54270: 4
54272: 2
54271: 3
54272: 1
54270: 3
54271: 2
54271: 1
54272: done
54270: 2
54270: 1
54271: done
54270: done
$

Пустая строка появилась, потому что я нажал return - приглашение появился на 3 строки раньше, но за ним последовало больше выводов из 54268 и 54269. Я считаю, что это гораздо реже, чем нужно.

Инструментальный вариант 0

Чтобы попытаться понять исходный код, Я проинструктировал его после внесения некоторых небольших изменений (сохраненных в multiple31.c и скомпилированных в multiple31):

/* SO 6021-0236 */
/* Original algorithm with instrumentation */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    assert(argc > 2);
    char *nameExec = argv[1];
    char *time;
    int number = argc - 2;

    if (number == 1)
    {
        printf("%d: name = %s; time = %s\n", (int)getpid(), nameExec, argv[2]);
        execl(argv[1], nameExec, argv[2], NULL);
    }
    else
    {
        for (int i = 2; i <= number; i++)  // Idempotent change in condition
        {
            printf("%d: i = %d; number = %d\n", (int)getpid(), i, number);
            pid_t kid = fork();
            if (kid == 0)
            {
                time = argv[i];
                printf("%d: i = %d; time = %s; ppid = %d\n",
                       (int)getpid(), i, time, (int)getppid());
            }
            else
            {
                time = argv[i + 1];
                printf("%d: i = %d; time = %s; waiting for %d\n",
                       (int)getpid(), i, time, (int)kid);
                int status;
                int corpse = wait(&status);
                printf("%d: i = %d; time = %s; PID %d exited with status 0x%.4X\n",
                       (int)getpid(), i, time, corpse, status);
            }
        }
        printf("%d: name = %s; time = %s\n", (int)getpid(), nameExec, time);
        execl(argv[1], nameExec, time, NULL);
    }

    printf("%d: this should not be reached!\n", (int)getpid());
    return 0;
}

При запуске 4 раза он выдает такие данные, как:

$ multiple31 count23 5 4 3 2
54575: i = 2; number = 4
54575: i = 2; time = 4; waiting for 54576
54576: i = 2; time = 5; ppid = 54575
54576: i = 3; number = 4
54576: i = 3; time = 3; waiting for 54577
54577: i = 3; time = 4; ppid = 54576
54577: i = 4; number = 4
54577: i = 4; time = 2; waiting for 54578
54578: i = 4; time = 3; ppid = 54577
54578: name = count23; time = 3
54578: start
54578: 3
54578: 2
54578: 1
54578: done
54577: i = 4; time = 2; PID 54578 exited with status 0x0000
54577: name = count23; time = 2
54577: start
54577: 2
54577: 1
54577: done
54576: i = 3; time = 3; PID 54577 exited with status 0x0000
54576: i = 4; number = 4
54576: i = 4; time = 2; waiting for 54579
54579: i = 4; time = 3; ppid = 54576
54579: name = count23; time = 3
54579: start
54579: 3
54579: 2
54579: 1
54579: done
54576: i = 4; time = 2; PID 54579 exited with status 0x0000
54576: name = count23; time = 2
54576: start
54576: 2
54576: 1
54576: done
54575: i = 2; time = 4; PID 54576 exited with status 0x0000
54575: i = 3; number = 4
54575: i = 3; time = 3; waiting for 54580
54580: i = 3; time = 4; ppid = 54575
54580: i = 4; number = 4
54580: i = 4; time = 2; waiting for 54581
54581: i = 4; time = 3; ppid = 54580
54581: name = count23; time = 3
54581: start
54581: 3
54581: 2
54581: 1
54581: done
54580: i = 4; time = 2; PID 54581 exited with status 0x0000
54580: name = count23; time = 2
54580: start
54580: 2
54580: 1
54580: done
54575: i = 3; time = 3; PID 54580 exited with status 0x0000
54575: i = 4; number = 4
54575: i = 4; time = 2; waiting for 54582
54582: i = 4; time = 3; ppid = 54575
54582: name = count23; time = 3
54582: start
54582: 3
54582: 2
54582: 1
54582: done
54575: i = 4; time = 2; PID 54582 exited with status 0x0000
54575: name = count23; time = 2
54575: start
54575: 2
54575: 1
54575: done
$

Отслеживание, почему это вывод fiendi sh. Я начал писать объяснение, но обнаружил, что мое объяснение не соответствует фактическому результату - еще раз. Тем не менее, инструментарий, показанный ниже, - вот как я обычно понимаю, что происходит. Один из ключевых моментов (слегка упрощенный) состоит в том, что все ожидают ребенка до d ie, за исключением одного ребенка, который выполняет обратный отсчет. Выполнение тестов с 1, 2 или 3 раза вместо 4 согласуется с этим, но проще (одновременно менее запутанным и более запутанным). Использование в 5 раз увеличивает объем вывода, но на самом деле не дает большего просветления.

0 голосов
/ 15 февраля 2020

Во-первых, если вы проанализируете код в multiple.c, вы увидите, что он состоит только из основного if(){...}else{...}, в котором со стороны then вы делаете один exe c, если argc == 2 (что правильно, если вы просто хотите сохранить вызов fork(), но усложняете ситуацию и совершаете ошибки), а в части else у вас есть только один вызов execl() после for l oop полностью выполнено.

  • Во-первых, вы никогда не делаете fork() до вызовов wait(), которые вы делаете в l oop, поэтому все они возвращают -1 с ошибка ENOCHLD.
  • , вы должны поместить fork() s и execl() s в for l oop, поэтому для каждого параметра создается новый процесс.
  • вы выполняете много дополнительной работы в списке аргументов, чтобы обработать его так, как он у вас есть. Намного лучше указать программу для запуска в качестве опции (-r может быть хорошей) и использовать программу по умолчанию, если вы ее не указали. Использование getopt(3) будет хорошим выбором и сделает вашу программу более профессиональной unix.

Ниже приведен пример того, что я говорю:

количество. c

/* YOU NEED TO POST COMPLETE, COMPILABLE CODE, DON'T TAKE OFF
 * YOUR INCLUDE FILES TO POST, AS SOME ERRORS CAN COME FROM THE
 * FACT THAT YOU HAVE FORGOTTEN TO #include THEM.  */
#include <assert.h> /* for assert */
#include <stdio.h>  /* for printf and stdio functions */
#include <stdlib.h> /* for atoi */
#include <unistd.h> /* for getpid() */

int main(int argc, char** argv)
{
   assert(argc == 2);

   int length= atoi(argv[1]); 
   assert(length>0);

   int pid = getpid(); 

   printf("%d : %s\n", pid, "start");

   for (unsigned int i=length; i>0; i--)
   {
      printf("%d : %d\n", pid, i);

      sleep(1); 
   }

   printf("%d : %s\n", pid, "done");

   return 0;
}

несколько. c

/* SAME AS BEFORE :) */

#include <errno.h> /* for errno */
#include <stdio.h> /* for printf() and others */
#include <stdlib.h> /* for many standard functions */
#include <string.h> /* for strerror() */
#include <sys/wait.h> /* for wait() */
#include <unistd.h> /* for getpid() */
#include <getopt.h> /* for getopt() */

pid_t pid;

/* create the format string for printf with a pretty header,
 * showing pid, source file, source line, source function,
 * and the provided format string. */
#define F(_fmt) "[pid=%d]:"__FILE__":%d:%s: "_fmt,pid,__LINE__,__func__

/* fatal error macro */
#define ERROR(str) do {\
                fprintf(stderr, F("%s: %s\n"),\
                        (str), strerror(errno));\
                exit(EXIT_FAILURE);\
        } while(0)

int main(int argc, char** argv)
{
        char *program = "./a.out";  /* defaults to a.out in local dir */
        int opt;

        /* getopt use.  See getopt(3) for instructions */
        while ((opt = getopt(argc, argv, "r:")) != EOF) {
                switch (opt) {
                case 'r': program = optarg; break;
                }
        }

        /* shift all the processed parameters out */
        argc -= optind; argv += optind;

        /* get our pid to use in traces */
        pid = getpid();

        if (argc == 1) {
                /* only one parameter, save the fork() call */
                printf(F("about to exec: %s %s\n"),
                        program, argv[0]);
                execlp(program,
                        program, argv[0], NULL);
                ERROR("execlp()");
                /* NOTREACHED */
        } else {
                pid_t chld;
                int i;
                for (i = 0; i < argc; i++) {
                        chld = fork();
                        if (chld < 0) {
                                ERROR("fork()");
                                /* NOTREACHED */
                        }
                        if (!chld) { /* child */
                                printf(F("about to call: %s %s\n"),
                                        program, argv[i]);
                                execlp(program,
                                        program, argv[i], NULL);
                                ERROR("execlp()");
                                /* NOTREACHED */
                        }
                }
                /* NOW, AFTER ALL FORKS/EXECS HAVE BEEN DONE,
                 * JUST DO ALL THE WAITS.  wait() gives an
                 * error if no children exist to wait for, so
                 * we wait() until we get an error here.  */
                int status;
                while ((chld = wait(&status)) > 0) {
                        /* just change this printf to a continue;
                         * if you don't want to print the result. */
                        printf(F("wait() => child %d (status = %d)\n"),
                                chld, status);
                }
        }
        printf("%d: END OF PROGRAM\n", pid);
}

Наконец, один прогон:

$ multiple -r count 3 5 2
[pid=78790]:multiple.c:61:main: about to call: count 3
[pid=78790]:multiple.c:61:main: about to call: count 2
78791 : start
78791 : 3
[pid=78790]:multiple.c:61:main: about to call: count 5
78793 : start
78793 : 2
78792 : start
78792 : 5
78791 : 2
78792 : 4
78793 : 1
78791 : 1
78792 : 3
78793 : done
[pid=78790]:multiple.c:77:main: wait() => child 78793 (status = 0)
78791 : done
78792 : 2
[pid=78790]:multiple.c:77:main: wait() => child 78791 (status = 0)
78792 : 1
78792 : done
[pid=78790]:multiple.c:77:main: wait() => child 78792 (status = 0)
78790: END OF PROGRAM
$ _

код, представленный выше, доступен для скачивания здесь

0 голосов
/ 13 февраля 2020

Предостережение: Это не работает, но даст вам идею ...

Примечание: Ни один ребенок никогда не выполнит секунду l oop, потому что execl [исполняется ребенком] обычно никогда не вернется [если не ошибка]

int
main(int argc, char **argv)
{

    --argc;
    ++argv;

    assert(argc >= 2);

    char *nameExec = *argv++;
    --argc;

    for (;  argc > 0;  --argc, ++argv) {
        char *time = *argv;
        pid_t pid = fork();
        if (pid == 0)
            execl(nameExec,time,NULL);
    }

    while (wait(NULL) >= 0);

    return 0;
}
...