成电UNIX编程作业三部曲---第二曲多进程协同的词频统计
2015-09-02 17:37
363 查看
基本功能:
Usage: word_count dirname
用例:word_count ./prog,通过遍历进程(2个以上)对输入目录prog中的文件进行递归并行遍历,统计各个文本文件中的各个单词出现的数量,由汇总进程收集各个遍历进程的统计信息进行汇总,并将汇总词频统计信息打印在屏幕上。
1.源程序
2.设计方案
(1)遍历目录的设计
利用readdir()和opendir()函数对目录文件进行操作。采用递归的方案,每打开一个目录就进入这个目录中,调用readdir()函数返回一个文件,对于返回的文件进行判断,如果是目录,则进行进入目录进行递归处理。如果是普通文件的话,则进行统计处理。
对于要如何判断readdir()返回的文件是普通文件还是目录文件,这里我选用了lstat()函数,可以返回一个struct stat结构体。通过该结构体的l_mode值来判断该文件的属性。
下面我利用该设计思想,单独编写了一个递归遍历目录的程序orderdir.c,程序代码见源代码中。
运行orderdir程序:
其中./myword是我创建的目录文件,作为程序参数运行。
可以清楚地看到orderdir程序将遍历到的目录与普通文本文件打印了出来。
(2)多进程统计词频
这个模块是这道题考察的重点,采用的方法直接决定了我们的程序能否协同成功。在这里本人采用的是记录锁的方法。至于为什么要采用记录锁,主要是为了解决以下几点问题:
1. 对于遍历过的文件,如何记录遍历信息?
2. 在多进程各自遍历文件时,如何知道该文件是否被别的进程遍历过?
3. 多进程在遍历过程中如何准确获得遍历信息?
对于解决这3个问题,是解决这道题的核心。对于解决第3个问题,我们可以假设有2个进程同时遍历目录,每个进程将遍历过的文件名放在指定文件“test”中,这样可以在别的文件要被遍历时,可以先去查看test文件,看其中有没有该文件名,如果有则说明该文件已经被统计过了。对于如何解决第3个问题,我们要仔细分析,有人说,可以先让每个进程在统计文件前查看test文件,然后没有就将该文件名写入。这是个很好的思路,但是对多进程来说,里面会有很多的问题。设想两个进程同时遍历到同一个文件时,这两个进程将独自去读test文件,然后将该文件名写入。每个进程有两个操作,一个read一个write,两个进程read和write的先后完全取决于内核的调度。可能第一个进程先read,第二个进程再read,然后第二个进程在write,最后第一个进程read。在这种情况下,势必该文件被统计两次,所以如何避免多进程读写之间的干扰是关键。本人采用的记录锁方法,可以现将文件锁住再去读写,任一个时刻该文件只能被一个进程占用。这样就有效的避免了重复统计问题。
基于这种模型,本人先写了一个记录锁程序lock_test.c。第一个进程先锁文件,然后睡眠2s,然后第二个进程再去尝试锁文件,读写文件,看是否能将字符串写入文件中,最后一个进程写一个字符串到文件中。
可以看到第二个文件不能锁住test文件,不能进行读写文件操作。但第一个进程可以对文件进行写操作,这样的模型满足我们的需求。
3.总程序运行截屏
结合前面的模型测试,编写出我们的最终程序word_count.c。
运行如下:
程序宏定义了4个子进程。
看每个子进程统计的结果:
第一个进程统计:
第三个进程和第四个进程统计:(第二个一样的,就不贴了)
对比汇总进程得到的最终结果:
可以看出,汇总进程的单词统计是前几个进程统计的总和。
4.性能分析
(1)空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
对于存储算法本身所占用的存储空间,从word_count.c这个文件来说,大小为7KB,文件很小,word_count..c这个文件运行过程中生成的文件大小也是非常小,orderdir.c这个文件只有2KB,lock_test.c这个文件只有2.2KB。
对于算法的输入输出数据所占用的存储空间,算法本身输入的数据为存储在文件中的单词数据;算法的输出数据其中之一为输出到荧屏的提示信息,这样虽然在空间复杂度上作出了一点点牺牲,但是给用户带来的信息量却是很大的,带来了很大的方便;另外的输出数据为输出到文件中的数据,从上面文件大小的分析中可知,数据占用的数据量并不大。
对于算法在运行过程中临时占用的存储空间,程序编译后运行的过程中是一定会占用内存的,程序在运行过程中所占用的临时空间主要为定义变量、结构体、记录锁进程、并行进程的运行、读写操作、程序本身运行等操作所需要的内存空间,可以看出,在保证程序完整执行的情况下,运行过程并不会占用太大的内存空间。
(2)时间复杂度
可以看到程序用时非常短。
推测在多文件,大文件的目录下遍历情况会很好的说明多进程的优势。
(3)文件大小
可以看出测试目录为28K,源文件大小为6.9K。
顺利完工
Usage: word_count dirname
用例:word_count ./prog,通过遍历进程(2个以上)对输入目录prog中的文件进行递归并行遍历,统计各个文本文件中的各个单词出现的数量,由汇总进程收集各个遍历进程的统计信息进行汇总,并将汇总词频统计信息打印在屏幕上。
1.源程序
//multi-processor cooporate to count the times of words #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <string.h> #include <sys/stat.h> #include <dirent.h> #include <unistd.h> #include "apue.h" #include <sys/wait.h> #define MAXWORD 20 #define MAXCOUNT 200 #define MAXNAME 100 #define MAXCHILD 4 typedef struct StrCount *strcount; struct StrCount{ char str[MAXWORD]; int count; }s_count; int n = 0; strcount count_file(const char *filename, strcount wordcount) { int fd = -1; if ((fd = open(filename, O_RDONLY)) == -1){ printf("cannot open this file:%s\n", filename); exit(0); } if (fd != STDIN_FILENO){ dup2(fd, STDIN_FILENO); close(fd); } char str[MAXWORD] = {0}; int flag = 0; while (scanf("%s", str) != EOF){ flag = 0; for (int i = 0; i < n; i++){ if (strcmp(str, wordcount[i].str) == 0){ wordcount[i].count++; flag = 1; break; } } if (flag == 0){ strcpy(wordcount .str, str); wordcount .count++; n++; } } close(0); return wordcount; } strcount parent_count_file(const char *filename, strcount wordcount) { int fd = -1; if ((fd = open(filename, O_RDONLY)) == -1){ printf("cannot open this file:%s\n", filename); exit(0); } if (fd != STDIN_FILENO){ dup2(fd, STDIN_FILENO); close(fd); } char str[MAXWORD] = {0}; int num = 0; int flag = 0; while (scanf("%s%d", str, &num) != EOF){ flag = 0; for (int i = 0; i < n; i++){ if (strcmp(str, wordcount[i].str) == 0){ wordcount[i].count += num; flag = 1; break; } } if (flag == 0){ strcpy(wordcount .str, str); wordcount .count = num; n++; } } close(0); return wordcount; } int writelocking(int fd, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = whence; lock.l_start = offset; lock.l_len = len; return(fcntl(fd, F_SETLKW, &lock)); } int unlocking(int fd, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = whence; lock.l_start = offset; lock.l_len = len; return(fcntl(fd, F_SETLKW, &lock)); } int have_tested(int fd, char *filename) { if (fd != STDIN_FILENO){ dup2(fd, STDIN_FILENO); } char str[MAXNAME] = {0}; while (scanf("%s", str) != EOF){ if (strcmp(str, filename) == 0){ if (unlocking(fd, 0, 0, 0) == -1){ printf("unlock error\n"); exit(0); } close(0); return 1; } } strcpy(str, filename); strcat(str, "\n"); if (write(fd, str, strlen(str)) < 0){ printf("write filename to test file error\n"); exit(0); } if (unlocking(fd, 0, 0, 0) == -1){ printf("unlock error\n"); exit(0); } close(0); return 0; } strcount orderdir(const char *dirname, strcount wordcount) { DIR *dirp; struct dirent *dir; char fullname[MAXNAME] = {0}; struct stat buf; if ((dirp = opendir(dirname)) == NULL){ printf("opendir %s error\n", dirname); exit(0); } while ((dir = readdir(dirp)) != NULL){ if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")){ continue; } sprintf(fullname, "%s/%s", dirname, dir->d_name); if (lstat(fullname, &buf) == -1){ printf("lstat error\n"); continue; } if (S_ISDIR(buf.st_mode)){ wordcount = orderdir(fullname, wordcount); } else { if (S_ISREG(buf.st_mode)){ int fd = -1; if ((fd = open("test", O_RDWR|O_APPEND)) < 0){ printf("lock open test file error\n"); exit(0); } if (writelocking(fd, 0, 0, 0) == -1){ printf("write lock error\n"); exit(0); } if (have_tested(fd, fullname) == 0) wordcount = count_file(fullname, wordcount); close(fd); } } } return wordcount; } void print_word(strcount wordcount, int num) { int fd = -1; char str[MAXNAME] = {0}; sprintf(str, "child%d", num); if ((fd = open(str, O_WRONLY|O_TRUNC|O_CREAT, FILE_MODE)) < 0){ printf("the %dth child open_print error\n", num); exit(0); } for (int i = 0; i < n; i++){ sprintf(str, "%s %d\n", wordcount[i].str, wordcount[i].count); if (write(fd, str, strlen(str)) < 0){ printf("the %dth child write_print error\n", num); exit(0); } } close(fd); } void print_parent(strcount wordcount) { int fd = -1; char str[MAXNAME] = {0}; if ((fd = open("./parent", O_WRONLY|O_TRUNC|O_CREAT, FILE_MODE)) < 0){ printf("the parent open_print error\n"); exit(0); } for (int i = 0; i < n; i++){ sprintf(str, "%s %d\n", wordcount[i].str, wordcount[i].count); if (write(fd, str, strlen(str)) < 0){ printf("the parent write_print error\n"); exit(0); } } close(fd); } int main(int argc, char *argv[]) { if (argc != 2){ printf("no directory input\n"); exit(0); } int fp[2] = {0}; if (pipe(fp) < 0){ printf("creat pipe error\n"); exit(0); } pid_t pid = -1; for (int i = 0; i < MAXCHILD; i++){ if ((pid = fork()) < 0){ printf("fork the %dth child error\n", i+1); exit(0); } else if(pid == 0) { close(fp[0]); strcount wordcount; wordcount = (strcount)malloc(MAXCOUNT*sizeof(s_count)); memset(wordcount, 0, MAXCOUNT); wordcount = orderdir(argv[1], wordcount); print_word(wordcount, i+1); printf("the %dth child end\n", i+1); char c = '0'+i+1; if (write(fp[1], &c, 1) < 0){ printf("write pipe error\n"); exit(0); } exit(0); } } strcount word_sum; word_sum = (strcount)malloc(MAXCOUNT*sizeof(s_count)); char c = '0'; char filename[MAXNAME] = {0}; for (int i = 0; i < MAXCHILD; i++){ if (read(fp[0], &c, 1) < 0){ printf("parent read pipe error\n"); exit(0); } sprintf(filename, "child%d", (int)(c-'0')); word_sum = parent_count_file(filename, word_sum); } print_parent(word_sum); exit(0); }
2.设计方案
(1)遍历目录的设计
利用readdir()和opendir()函数对目录文件进行操作。采用递归的方案,每打开一个目录就进入这个目录中,调用readdir()函数返回一个文件,对于返回的文件进行判断,如果是目录,则进行进入目录进行递归处理。如果是普通文件的话,则进行统计处理。
对于要如何判断readdir()返回的文件是普通文件还是目录文件,这里我选用了lstat()函数,可以返回一个struct stat结构体。通过该结构体的l_mode值来判断该文件的属性。
下面我利用该设计思想,单独编写了一个递归遍历目录的程序orderdir.c,程序代码见源代码中。
运行orderdir程序:
其中./myword是我创建的目录文件,作为程序参数运行。
可以清楚地看到orderdir程序将遍历到的目录与普通文本文件打印了出来。
(2)多进程统计词频
这个模块是这道题考察的重点,采用的方法直接决定了我们的程序能否协同成功。在这里本人采用的是记录锁的方法。至于为什么要采用记录锁,主要是为了解决以下几点问题:
1. 对于遍历过的文件,如何记录遍历信息?
2. 在多进程各自遍历文件时,如何知道该文件是否被别的进程遍历过?
3. 多进程在遍历过程中如何准确获得遍历信息?
对于解决这3个问题,是解决这道题的核心。对于解决第3个问题,我们可以假设有2个进程同时遍历目录,每个进程将遍历过的文件名放在指定文件“test”中,这样可以在别的文件要被遍历时,可以先去查看test文件,看其中有没有该文件名,如果有则说明该文件已经被统计过了。对于如何解决第3个问题,我们要仔细分析,有人说,可以先让每个进程在统计文件前查看test文件,然后没有就将该文件名写入。这是个很好的思路,但是对多进程来说,里面会有很多的问题。设想两个进程同时遍历到同一个文件时,这两个进程将独自去读test文件,然后将该文件名写入。每个进程有两个操作,一个read一个write,两个进程read和write的先后完全取决于内核的调度。可能第一个进程先read,第二个进程再read,然后第二个进程在write,最后第一个进程read。在这种情况下,势必该文件被统计两次,所以如何避免多进程读写之间的干扰是关键。本人采用的记录锁方法,可以现将文件锁住再去读写,任一个时刻该文件只能被一个进程占用。这样就有效的避免了重复统计问题。
基于这种模型,本人先写了一个记录锁程序lock_test.c。第一个进程先锁文件,然后睡眠2s,然后第二个进程再去尝试锁文件,读写文件,看是否能将字符串写入文件中,最后一个进程写一个字符串到文件中。
可以看到第二个文件不能锁住test文件,不能进行读写文件操作。但第一个进程可以对文件进行写操作,这样的模型满足我们的需求。
3.总程序运行截屏
结合前面的模型测试,编写出我们的最终程序word_count.c。
运行如下:
程序宏定义了4个子进程。
看每个子进程统计的结果:
第一个进程统计:
第三个进程和第四个进程统计:(第二个一样的,就不贴了)
对比汇总进程得到的最终结果:
可以看出,汇总进程的单词统计是前几个进程统计的总和。
4.性能分析
(1)空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
对于存储算法本身所占用的存储空间,从word_count.c这个文件来说,大小为7KB,文件很小,word_count..c这个文件运行过程中生成的文件大小也是非常小,orderdir.c这个文件只有2KB,lock_test.c这个文件只有2.2KB。
对于算法的输入输出数据所占用的存储空间,算法本身输入的数据为存储在文件中的单词数据;算法的输出数据其中之一为输出到荧屏的提示信息,这样虽然在空间复杂度上作出了一点点牺牲,但是给用户带来的信息量却是很大的,带来了很大的方便;另外的输出数据为输出到文件中的数据,从上面文件大小的分析中可知,数据占用的数据量并不大。
对于算法在运行过程中临时占用的存储空间,程序编译后运行的过程中是一定会占用内存的,程序在运行过程中所占用的临时空间主要为定义变量、结构体、记录锁进程、并行进程的运行、读写操作、程序本身运行等操作所需要的内存空间,可以看出,在保证程序完整执行的情况下,运行过程并不会占用太大的内存空间。
(2)时间复杂度
可以看到程序用时非常短。
推测在多文件,大文件的目录下遍历情况会很好的说明多进程的优势。
(3)文件大小
可以看出测试目录为28K,源文件大小为6.9K。
顺利完工
相关文章推荐
- java利用qrcode.jar进行处理二维码,生成、解析二维码
- 程序=方案+代码
- 是财富还是陷阱?如何处理他人的代码
- Python调用C++程序备忘笔记
- PrintWriter(示例,出错代码)
- Java笔记存放站
- ASP.NET MVC cs类中根据Controller和Action生成URL
- [C++][编程风格]C++命名规则
- [Golang] 从零开始写Socket Server(5):Server的解耦—通过Router+Controller实现逻辑分发
- java1.7 try块流关闭的方面
- go-反射机制
- QT项目中的相关知识
- C# 全角符号转半角
- 在Visual Studio 2010中开发Qt程序
- thinkphp 数据库查询是id大于等于
- java复习笔记之自定义函数泛型
- 垃圾回收器GC
- Java学习之抽象类和接口区别比较
- Java并发编程(详解wait(), notify(),sleep())
- Android-使用C++实现调用本地方法返回字符串显示在界面上/NDK-JNI开发实例(八)