在文件夹中并行搜索关键字
在文件夹中并行搜索关键字
作者: huzy
在阅读大型项目代码,经常要搜索某个标识符,找出标识符的声明、定义或引用的地方(指出在哪一行)。 本任务要求实现一个程序 idfind,其格式如下: idfind [-j n] id dirname 选项 –j 指定并行线程数目。如果省略该选项,则只启动一个线程。 id 表示要查找的标识符,dirname 表示项目所在的目录。
要点分析:
我认为该任务需要的关键技术有以下的几点:
1. 文件目录递归,筛选出文件后缀名为 .h、.cpp、 .c 的所有文件;
2. 多线程任务的分配;
3. 在文件中快速定位关键字。
我的主要思路:
用主线程去把任务压栈,子线程轮转操作,只要任务栈不空,子线程就不会停止工作。
任务轮转思想的示意图如下:
先讲讲为什么我要采用栈来保存任务?
栈是后进先出的数据结构,所以也许你会质疑“为什么不用队列”?在这里因为我考虑到关键字的查找与任务的先后顺序无关,所以采用“栈”或“队列”的区别不大!
栈的数据共用一个进出口——栈顶,只需要加一把互斥锁。
我的方法:
任务压栈与出栈:
1 int push_stack(stack &st, char filename[]) 2 3 { 4 5 if(st.top == MAX-1) 6 7 { 8 9 printf("stack overflow !\n"); 10 11 return 0; 12 13 } 14 15 st.filename[++st.top] = (char*)malloc(strlen(filename)+1); 16 17 strcpy(st.filename[st.top], filename); 18 19 return 1; 20 21 } 22 23 int pop_stack(stack &st, char filename[]) 24 25 { 26 27 pthread_mutex_lock(&mutex); // 互斥锁 28 29 if(st.top <= -1) 30 31 { 32 33 pthread_mutex_unlock(&mutex); // 互斥锁 34 35 return 0; 36 37 } 38 39 strcpy(filename, st.filename[st.top]); 40 41 free(st.filename[st.top]); 42 43 st.top --; 44 45 pthread_mutex_unlock(&mutex); // 互斥锁 46 47 return 1; 48 49 }
开始写程序时,我将创建线程放在判断栈空的后面,完成任务则退出。
后来发现存在一个很严重的效率问题:只要任务栈不空,就会使得“线程频繁的创建与退出”,这样的开销是非常大的,
所以改进后,我将创建线程提前,并且同时考虑“任务的规模”和“用户的需求”。
流程图如下:
我的方法:
主函数:
int main(int argc,char *argv[]) { …… pthread_t *pth[PMAX]; // 线程指针数组 …… //------------------------------------------------------------------------- init_stack(stk); // 初始化栈, 【 global 】 directory(path, stk); // 遍历文件夹,并压栈文件全路径名 KMP_next(key, strlen(key)); // 索引化关键字 //-------------------------------------------------------------------------- ts_num = stack_size(stk); if(th_num > ts_num) // 如果指定的线程数目比任务还要多 op_num = ts_num; else op_num = th_num; //-------------------------------------------------------------------------- for(i=0; i<op_num; i++) // 动态创建线程 { pth[i] = (pthread_t *)malloc(sizeof(pthread_t)); if(pthread_create(pth[i], NULL, thread_function, NULL)) { printf("error creating thread."); abort(); } } //-------------------------------------------------------------------------- for(i=0; i<op_num; i++) { if(pthread_join(*pth[i],NULL)) // 等待所有线程结束 { printf("error joining thread...\n"); abort(); } } //-------------------------------------------------------------------------- // 释放内存 destroy_stack(stk);
return 0; }
拓展:
当然后来经过反复思量和与老师的探讨,我认为采用“队列”的效率会更高!
因为:队列的数据有两个口,一个进数据(队尾),一个出数据(队首),需要两把互斥锁,这就使得数据的“进入”与数据的“弹出”不会因为冲突而等待。
这样缩短了阻塞时间,使得并行效率更高。
在我看来,任务分配的合理性只是提高效率的一部分。
因为该程序主要是针对“大型项目代码”的搜索,每当一个子线程完成一个任务,它就去“领取”下一个任务,为了避免同一个任务被不同的子线程重复“领取”,
在出栈的地方加上了互斥锁,然而每个代码文件的大小并不是很大,这就存在“频繁切换线程”的现象,也就是说子线程被阻塞的时间总和会随着线程数量的
增加而增加,而且每个线程的创建与回收需要较多的时间。
所以,我认为提高关键字定位的算法是至关重要的!
字符串定位的常规方法是采用暴力的“BF算法”,BF算法的优点是浅显易懂,但是它的最大效率制约问题是存在“主串指针的回溯”现象。
所以我采用的是“KMP算法”,KMP算法是对“BF算法”的改进,通过对关键字建立索引来避免“主串指针的回溯”。
完整代码:
1 #include <pthread.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <unistd.h> 5 #include <stdlib.h> 6 #include <stdio.h> 7 #include <string.h> 8 #include <dirent.h> 9 #include <malloc.h> 10 11 #define MAX 100000 // 最多能处理文件个数 12 #define PMAX 20 // 最多允许线程数目 13 #define KEY_LEN 20 // 关键字的最大长度 14 #define READ_LEN 256 // 每次读取一行的最大长度 15 16 char key[KEY_LEN]; // 要查找的关键字 17 int Next[KEY_LEN]; // kmp 索引 18 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥 19 20 21 //============================================================================= 22 // 自定义的栈 23 //============================================================================= 24 typedef struct _stack // 自定义栈 25 { 26 char *filename[MAX]; 27 int top; 28 }stack; 29 30 stack stk; // 全局栈 31 32 void init_stack(stack &st) { st.top = -1; } 33 34 int stack_is_empty(stack &st) { return st.top == -1 ? 1:0; } 35 36 int stack_size(stack &st) { return st.top+1; } 37 38 int push_stack(stack &st, char filename[]) 39 { 40 if(st.top == MAX-1) 41 { 42 printf("stack overflow !\n"); 43 return 0; 44 } 45 st.filename[++st.top] = (char*)malloc(strlen(filename)+1); 46 strcpy(st.filename[st.top], filename); 47 return 1; 48 } 49 50 int pop_stack(stack &st, char filename[]) 51 { 52 pthread_mutex_lock(&mutex); // 互斥锁 53 if(st.top <= -1) 54 { 55 pthread_mutex_unlock(&mutex); // 互斥锁 56 //printf("stack is empty ...\n"); 57 return 0; 58 } 59 strcpy(filename, st.filename[st.top]); 60 free(st.filename[st.top]); 61 st.top --; 62 pthread_mutex_unlock(&mutex); // 互斥锁 63 return 1; 64 } 65 66 void destroy_stack(stack &st) 67 { 68 int i; 69 for(i=0; i<=st.top; i++) 70 free(st.filename[i]); 71 st.top = -1; 72 } 73 74 void print_stack(stack &st) 75 { 76 int i; 77 for(i=0; i<=st.top; i++) 78 printf("%s\n",st.filename[i]); 79 } 80 81 //============================================================================= 82 // 目录递归遍历 83 //============================================================================= 84 /*判断文件是否是个目录文件,是目录返回 1 ,不是目录返回 0*/ 85 int testdir(char *path) 86 { 87 struct stat buf; 88 if(lstat(path,&buf)<0) 89 { 90 return 0; 91 } 92 if(S_ISDIR(buf.st_mode)) 93 { 94 return 1; //directory 95 } 96 return 0; 97 } 98 99 //============================================================================= 100 /*遍历目录*/ 101 int directory(char *path, stack &st) 102 { 103 DIR *db; /*打开目录类型文件信息的 结构体*/ 104 char filename[256]; 105 struct dirent *dir_info; /*目录类型文件属性信息的 结构体*/ 106 int len; 107 108 db=opendir(path); 109 if(db==NULL) 110 { 111 printf("Open dir error !\n"); 112 return 0; 113 } 114 memset(filename,0,256); 115 116 while ((dir_info=readdir(db))) 117 { 118 if((strcmp(dir_info->d_name,".")==0) 119 ||(strcmp(dir_info->d_name,"..")==0)) 120 continue; 121 else 122 { 123 sprintf(filename,"%s/%s",path,dir_info->d_name); 124 if(testdir(filename)) 125 directory(filename, st); /* 递归 */ 126 else 127 { 128 len = strlen(filename); 129 if(strcmp(&filename[len-2],".h") == 0 // .h文件 130 || strcmp(&filename[len-2],".c") == 0 // .c文件 131 || strcmp(&filename[len-4],".cpp") == 0 ) // .cpp文件 132 { 133 //printf("%s\n",filename); 134 if(0 == push_stack(st, filename)) 135 return 0; // 如果栈溢出 136 } 137 } 138 } 139 memset(filename,0,256); 140 } 141 closedir(db); 142 return 0; 143 } 144 145 //============================================================================= 146 // kmp 快速定位 147 //============================================================================= 148 // 索引化 t 串 149 150 void KMP_next(char str[], int len) 151 { 152 int k=-1; 153 int j=0; 154 Next[0]=-1; 155 while(j<len-1) // 156 { 157 if(k==-1 || str[k]==str[j]) 158 { 159 k++; 160 j++; 161 Next[j]=k; 162 } 163 else 164 k=Next[k]; 165 } 166 } 167 168 //============================================================================= 169 // 在 s 串中快速定位 t 串 170 171 int KMP_index(char s_str[], int s_len, char t_str[], int t_len) 172 { 173 int i,j,v; 174 i=0; 175 j=0; 176 177 if(s_str[0] == '\n') // 空行 178 return -1; 179 180 while(i<s_len && j<t_len) 181 { 182 if(j==-1 || s_str[i]==t_str[j]) 183 { 184 j++; 185 i++; 186 187 if(s_str[i] == '\n') 188 break; 189 } 190 else 191 j=Next[j]; 192 } 193 if(j>=t_len) 194 v=i-t_len+1; 195 else 196 v=-1; 197 return v; 198 } 199 200 //============================================================================= 201 // 多线程创建以及处理 202 //============================================================================= 203 204 void process(char filename[]) 205 { 206 int key_len = strlen(key); // 关键字长度 207 int index; // 关键字位置 208 int rw = 0; // 行 209 210 // 查找 key 值 211 //printf("find key ...\n"); 212 213 FILE *fp = fopen(filename,"r"); 214 if(!fp) return; 215 216 char buf[READ_LEN]; 217 while ( fgets(buf, READ_LEN, fp) != NULL ) 218 { 219 rw ++; 220 index = KMP_index(buf, READ_LEN, key, key_len); 221 if(index != -1) // 找到 222 { 223 printf("%s --> row:%d\n", filename,rw); 224 } 225 //memset(buf, 0, READ_LEN); 226 } 227 fclose(fp); 228 } 229 230 //============================================================================= 231 // 线程创建函数 232 void *thread_function(void *arg) 233 { 234 char filename[256]; 235 //printf("> create thread .\n"); 236 237 while(1) 238 { 239 //pthread_mutex_lock(&mutex); // 互斥锁 240 if(0 == pop_stack(stk, filename)) // 栈空 241 { 242 //pthread_mutex_unlock(&mutex); // 互斥锁 243 break; 244 } 245 //else 246 //pthread_mutex_unlock(&mutex); // 互斥锁 247 248 process(filename); // 查找操作 249 } 250 return NULL; 251 } 252 253 //============================================================================= 254 // main 函数 255 int main(int argc,char *argv[]) 256 { 257 int i; 258 int th_num; // 指定线程数 259 int ts_num; // 任务数 260 int op_num; // 实际线程数 261 char path[256]; // 要遍历的目录 262 263 pthread_t *pth[PMAX]; // 线程指针数组 264 265 if(argc < 3 || argc > 4) 266 { 267 printf("参数错误!\n"); 268 return 0; 269 } 270 else if(argc == 3) 271 { 272 th_num = 1; 273 strcpy(key, argv[1]); 274 strcpy(path, argv[2]); 275 } 276 else 277 { 278 sscanf(argv[1], "-%d", &th_num); 279 printf("%d\n",th_num); 280 strcpy(key, argv[2]); 281 strcpy(path, argv[3]); 282 } 283 284 //------------------------------------------------------ 285 //strcpy(path, "/home/huzy/Desktop"); // /linux011VC 286 287 init_stack(stk); // 初始化栈, 【 global 】 288 directory(path, stk); // 遍历文件夹,并压栈文件全路径名 289 //print_stack(stk); // 输出测试 290 //printf("file num: %d\n", stk.top); 291 //------------------------------------------------------ 292 //strcpy(key, "main"); // 【 global 】 293 294 KMP_next(key, strlen(key)); // 索引化关键字 295 //------------------------------------------------------ 296 //th_num = 5; 297 298 ts_num = stack_size(stk); 299 if(th_num > ts_num) // 如果指定的线程数目比任务还要多 300 op_num = ts_num; 301 else 302 op_num = th_num; 303 //------------------------------------------------------ 304 for(i=0; i<op_num; i++) // 动态创建线程 305 { 306 pth[i] = (pthread_t *)malloc(sizeof(pthread_t)); 307 if(pthread_create(pth[i], NULL, thread_function, NULL)) 308 { 309 printf("error creating thread."); 310 abort(); 311 } 312 } 313 //------------------------------------------------------ 314 for(i=0; i<op_num; i++) 315 { 316 if(pthread_join(*pth[i],NULL)) // 等待所有线程结束 317 { 318 printf("error joining thread...\n"); 319 abort(); 320 } 321 } 322 //------------------------------------------------------ 323 // 释放内存 324 destroy_stack(stk); 325 printf("end!\n"); 326 //------------------------------------------------------ 327 return 0; 328 }