为了练习,我必须编写一个程序来并行比较两个文件。 并行度由用户通过-j参数决定。
例如:
./ myprog -j 8 file1 file2
意味着将创建8个进程,每个进程将比较两个文件的1/8。
假设我们有3个文件,其中2个相等(文件1 =文件2)。
当程序比较两个不同的文件时,一切都很好(或者至少隐藏了错误)。
当我去比较两个相同的文件时(大约四分之一),程序会说“ file1 file2 different”,这显然是错误的。
有人可以帮助我了解为什么以及如何解决它吗? 谢谢!
#include <...>
int cmp(int, int, int, int);
int main(int argc, char *argv[]){
int len, status;
int fd1, fd2;
int term;
int i, j;
int opt, num;
int start, stop;
//[...] Various checks on files length and getopt()
num = atoi(optarg);
int pid[num];
for(i = 0; i < num; i++){
pid[i] = fork();
if(pid[i] == 0){
start = (len/num)*i;
stop = ((len/num)*(i+1))-1;
if(cmp(fd1, fd2, start, stop)) abort();
else exit(EXIT_SUCCESS);
}else if(pid[i] < 0){
perror(NULL);
exit(EXIT_FAILURE);
}
}
for(i = 0; i < num; i++){
term = wait(&status);
if(WIFSIGNALED(status)){
//when the error occours start or stop result gives a rondom high number like 1835627636
printf("PID-> %d START %d STOP %d\n", term, start, stop);
fprintf(stderr, "%s\n", "file1 file2 differ");
for(j = 0; j < num; j++){
if(pid[j] != term){
printf("KILL %d\n", j);
kill(pid[j], 0);
}
}
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}
int cmp(int fd1, int fd2, int start, int stop){
char buf1, buf2;
if(start > stop) return 0;
else{
fsync(fd1);
fsync(fd2);
lseek(fd1, start, SEEK_SET);
read(fd1, &buf1, sizeof(char));
lseek(fd2, start, SEEK_SET);
read(fd2, &buf2, sizeof(char));
if(buf1 != buf2) return 1;
else cmp(fd1, fd2, start+1, stop);
}
}
答案 0 :(得分:1)
没有分配fd1和fd2的代码,这只是一个猜测,但是我认为您的cmp
函数盲目地假设fsync
,lseek
和read
可以工作,不检查其返回值。如果任何一个失败(例如,由于文件已锁定),则相应的缓冲区当然将不匹配,这就是它报告的内容。
请注意,使cmp
递归非常无效且完全多余。它可以工作,但是会产生一英里长的堆栈,甚至可能使您为大文件编写程序时崩溃。一个简单的时间或时间就足够了。
答案 1 :(得分:1)
cmp
可能返回随机值(即未定义的行为),因为最后的else
是:
else cmp(fd1, fd2, start+1, stop);
应该是:
else return cmp(fd1, fd2, start+1, stop);
此外,在main
中,start/stop
仅在子进程中设置,因此它们在父进程中无效。另外,即使它们在父级中有效,也只能将其设置为 last 分支的子代的值。
此外,不能保证wait
将按顺序返回pids 。 (例如,它可能会返回pid[3]
之前 pid[2]
。因此,您尝试在不应该的情况下终止进程。您可能想使用waitpid
来代替。或者只是做:
int retval = EXIT_SUCCESS;
while (1) {
term = wait(&status);
if (term < 0)
break;
if (WIFSIGNALED(status)) {
printf(...);
retval = EXIT_FAILURE;
}
}
exit(retval);
然后,删除 kill
,因为它并不是真正需要的,并且更可能引起问题(即,它是您的一个错误的来源)。糟糕!我刚刚注意到您正在发送信号值为零的kill
。这基本上是无人操作。
更新:
通过在同一文件上启动程序并打印比较的字符,程序将打印相同的字符,但最后两个字符顺序相反,因此比较buf1!= buf2为true,cmp返回1。是种族问题吗?
是的。由于每个孩子都使用相同的文件描述符,因此一个孩子中的lseek
将影响其他所有孩子的文件位置。
在fork
联机帮助页中:
子级继承父级打开文件描述符集的副本。子文件中的每个文件描述符都引用相同的打开文件 文件描述(请参阅open(2))作为相应的文件描述符 在父母中。这意味着两个文件描述符共享打开 文件状态标志,文件偏移和信号驱动的I / O属性
关于dup
联机帮助页上共享文件偏移量的效果,这里有更多内容:
成功返回后,可以使用旧文件描述符和新文件描述符 可以互换它们引用相同的打开文件描述(请参阅 open(2)),从而共享文件偏移量和文件状态标志; 例如, 如果通过在文件之一上使用lseek(2)修改了文件偏移量 描述符,则偏移量也会更改。 (请参阅fcntl(2)中对F_SETOWN和F_SETSIG的描述)。
要解决此问题,请让每个孩子做两个文件中自己的/唯一的open
。然后,他们不会共享文件偏移,并且不会出现没有竞争条件。额外的open/close
的额外开销将很小。
还有几点...
通常为异常/意外情况(例如分段故障等)保留信号。因此,可以/应该用孩子中的abort
来传达不匹配信息,可以/应该用非零退出代码代替。
这更加清洁,并提供了更大的灵活性。 EXIT_SUCCESS/EXIT_FAILURE
是一组相对较新的定义。执行exit(code)
时,允许孩子返回任何 7位错误代码(即0-127(含0-127))。请以cmp
和rsync
的手册页为例。
在这里,您可以使用任何希望的约定来表示子错误代码,例如:0 =匹配,1 =不匹配,2 =对一个文件的短读,3 = I / O错误等。
正如Aganju所指出的那样,使cmp
函数递归将非常效率低下并使堆栈崩溃。大多数堆栈默认为〜8MB,因此这限制了您可以处理的文件大小。考虑您的8个流程示例。如果文件大小大于64MB,即使文件相等,也会溢出堆栈并出现段错误。
此外,对一个单个字节执行read
非常慢,并且完全抵消了并行处理所获得的任何速度提升。
因此,即使您将工作分散在多个流程之间,每个子流程仍将不得不遍历其职责范围内的较小块
读取时,最佳缓冲区大小并不总是最大。对于某些文件系统(例如ext4
),建议的大小IIRC为64KB。
我还建议使用一个自定义结构,该结构可以跟踪start/stop
的位置和其他各个子数据。
这是代码的重构版本,它说明了很多内容。它未经测试,但仍然有些粗糙,但是它应该可以帮助您进一步使用[请原谅免费的样式清理]。
注意/检查的事情
不同长度的文件不需要比较。他们从不匹配。这里的cmp
假定文件的长度相等。
应将进程数限制为文件大小(例如,如果您有8个进程,并且文件长度为4,则应将进程数限制为4)。
应该仔细检查范围/限制/大小的计算,因为可能会出现“一一偏离”的错误。
此外,请确保使用off_t
(它保证在任何#define
伪指令之前设置正确的#include
的64位值)以允许程序正确处理文件> 2GB < / p>
#include <...>
char *file1
char *file2;
typedef struct pidctl {
int chunknum;
off_t start;
off_t stop;
} pidctl_t;
#define CHUNKSIZE (64 * 1024)
int retcode = EXIT_SUCCESS;
int cmp(pidctl_t *ctl);
int
main(int argc, char *argv[])
{
off_t len;
int status;
int fd1;
int fd2;
int term;
int i,
j;
int opt,
num;
int start,
stop;
//[...] Various checks on files length and getopt()
num = atoi(optarg);
// process count should be clipped to number of bytes in file
if (num > filesize)
num = filesize;
pidctl_t pidlist[num];
for (i = 0; i < num; i++) {
ctl = &pidlist[i];
ctl->chunknum = i;
ctl->start = (len / num) * i;
ctl->stop = ((len / num) * (i + 1)) - 1;
// the last segment must be clipped to the file size
if (ctl->stop >= len)
ctl->stop = len - 1;
ctl->pid = fork();
if (ctl->pid == 0)
exit(cmp(ctl));
if (ctl->pid < 0) {
perror(NULL);
exit(EXIT_FAILURE);
}
}
while (1) {
term = wait(&status);
if (term < 0)
break;
for (i = 0; i < num; i++) {
ctl = &pidlist[i];
if (term == ctl->pid) {
if (WIFSIGNALED(status)) {
printf("PID-> %d START %lld STOP %lld -- signal %d\n",
ctl->pid, ctl->start, ctl->stop, WTERMSIG(status));
retcode = 2;
}
if (WIFEXITED(status)) {
int code = WEXITSTATUS(status);
printf("PID-> %d START %lld STOP %lld -- code %d -- %s\n",
ctl->pid, ctl->start, ctl->stop,
code,code ? "FAIL" : "OK");
if (code)
retcode = 1;
}
continue;
}
}
}
exit(retcode);
}
int
cmp(pidctl_t *ctl)
{
int fd1 = -1;
char buf1[CHUNKSIZE];
int fd2 = -1;
char buf2[CHUNKSIZE];
off_t pos;
off_t remain;
int rlen1;
int rlen2;
int xlen;
int code = 0;
do {
if (ctl->start > ctl->stop)
break;
fd1 = open(file1,O_RDONLY);
if (fd1 < 0) {
code = 2;
break;
}
pos = lseek(fd1, ctl->start, SEEK_SET);
if (pos != ctl->start) {
code = 3;
break;
}
fd2 = open(file2,O_RDONLY);
if (fd2 < 0) {
code = 2;
break;
}
pos = lseek(fd2, ctl->start, SEEK_SET);
if (pos != ctl->start) {
code = 3;
break;
}
remain = (ctl->stop - ctl->start) + 1;
for (; remain > 0; remain -= xlen)
xlen = CHUNKSIZE;
if (xlen > remain)
xlen = remain;
rlen1 = read(fd1, buf1, xlen);
if (rlen1 != xlen) {
code = 4;
break;
}
rlen2 = read(fd2, buf2, xlen);
if (rlen2 != xlen) {
code = 5;
break;
}
if (memcmp(buf1,buf2,xlen) != 0) {
code = 1;
break;
}
}
} while (0);
if (fd1 >= 0)
close(fd1);
if (fd2 >= 0)
close(fd2);
return code;
}