Parellel 使用 fork() 和 C 语言中的命令行参数进行进程



我正在尝试创建一个程序,该程序在命令行中接受许多计数并并行执行。

我有一个count.c文件用于计数:

int main(int argc, char** argv)
{
assert(argc>1);
int length= atoi(argv[1]); 
assert(length>0);
int pid = getpid(); 
printf("%d : %sn", pid, "start");
for (unsigned int i=length; i>0; i--)
{
printf("%d : %dn", pid, i);
sleep(1); 
}
printf("%d : %sn", pid, "done");

return 0;
}

因此,如果我在 bash 中输入">./count5",程序将从 5 计数到 1。

我还有另一个multiple.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]='';

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 ./count5 4,它会执行两个计数,一个从 5 开始,另一个从 4 开始,但不是同时,一个接一个。如果我输入./multiple ./count 54 3,它会执行 4 个计数,一个从 4 开始,然后一个从 3 开始,然后另一个从 4 开始,另一个从 3 开始。

我真的不明白这种行为, 据我了解,fork() 用于复制进程,execl 放弃当前进程并开始执行另一个进程。

请帮忙!

(另外,我正在尝试理解fork()和execl()的用法,所以我想找到使用这两个函数来回答我的问题的方法)。

原始代码按顺序运行子进程,而不是并发运行,因为循环中有wait()调用。

您无需复制程序名称。 您可以直接使用argv[1](或简单地将其分配给nameExec),也可以使用nameExec = &argv[1][2];跳过前几个字符。

理解代码中循环的操作是非常棘手的;当我试图用大脑包裹它时,它让我尖叫了几次。 我将简单地从头开始编写代码 - 有两种变体。

变式 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 %sn", 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%.4Xn",
(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 55 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 %sn", argv[1]);
exit(EXIT_FAILURE);
}
}
execl(argv[1], argv[1], argv[2], (char *)0);
fprintf(stderr, "failed to execute %sn", 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
$

出现空行是因为我按了回车键 — 提示出现前 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 = %sn", (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 = %dn", (int)getpid(), i, number);
pid_t kid = fork();
if (kid == 0)
{
time = argv[i];
printf("%d: i = %d; time = %s; ppid = %dn",
(int)getpid(), i, time, (int)getppid());
}
else
{
time = argv[i + 1];
printf("%d: i = %d; time = %s; waiting for %dn",
(int)getpid(), i, time, (int)kid);
int status;
int corpse = wait(&status);
printf("%d: i = %d; time = %s; PID %d exited with status 0x%.4Xn",
(int)getpid(), i, time, corpse, status);
}
}
printf("%d: name = %s; time = %sn", (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
$

追踪为什么这是输出是恶魔般的。 我开始写一个解释,但我发现我的解释与实际输出不匹配 - 再次。 但是,按照所示的路线进行仪器是我通常理解正在发生的事情的方式。 关键点之一(稍微简化)是,除了一个正在倒计时的孩子之外,一切都在等待一个孩子的死亡。 运行 1、2 或 3 次而不是 4 次的测试与此一致,但更简单(同时不那么混乱和更令人困惑)。 使用 5 倍会增加输出量,但并不能真正提供更多的启发。

警告:这被打破了,但会给你一个想法......

注意:没有子项将执行第二个循环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;
}

首先,如果你在multiple.c中分析代码,你会看到它只由一个主要if(){...}else{...}组成,在then端,你做一个 exec ifargc == 2(这是正确的,如果你只是想保存一个fork()调用,但使事情复杂化并让你犯错误)和在else部分, 在完全执行for循环后,您只有一个execl()调用。

  • 首先,在循环中执行的wait()调用之前,您永远不会执行fork(),因此所有调用都返回-1,错误为ENOCHLD
  • 您必须将fork()s 和execl()s 放在for循环中,以便为每个参数生成一个全新的进程。
  • 您在参数列表中做了很多额外的工作来处理它。 最好将程序指定为选项运行(-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 : %sn", pid, "start");
for (unsigned int i=length; i>0; i--)
{
printf("%d : %dn", pid, i);
sleep(1); 
}
printf("%d : %sn", 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: %sn"),
(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 %sn"),
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 %sn"),
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 PROGRAMn", 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
$ _

上面显示的代码可在此处下载

相关内容

  • 没有找到相关文章

最新更新