实验一 进程和线程

设计 - 多线程实现单词统计工具
1、实验说明
设有两个文本文件 file1.txt、file2.txt,统计两个文件中单词的总数。
2、解决方案
区分单词原则:凡是一个非字母或数字的字符跟在字母或数字的后面,那么这个字母或数字就是单词的结尾。允许线程使用互斥锁来修改临界资源,确保线程间的同步与协作。如果两个线程需要安全地共享一个公共计数器,需要把公共计数器加锁。线程需要访问称为互斥锁的变量,它可以使线程间很好地合作,避免对于资源的访问冲突。
代码

#include #include #include #include //互斥信号量 pthread_mutexattr_t counter_clock; //公共计数器 int total_word = 0; //线程函数,即统计一个文件的单词个数函数 void *count_words(void *f) { char *filename = (char *)f; FILE *fp; int c; int prevc = '\0'; if((fp=fopen(filename,"r"))!=NULL) { while((c=getc(fp))!=EOF) { //isalnum()用来判断一个字符是否为数字或者字母,也就是说判断一个字符是否属于a~z||A~Z||0~9,是返回非0。 if(!isalnum(c)&&isalnum(prevc)) { pthread_mutex_lock(&counter_clock); //线程加锁 total_word++; pthread_mutex_unlock(&counter_clock); //线程解锁 } prevc = c; } fclose(fp); } //打开失败 else { perror(filename); } return NULL; } /*argcargc = argument count :表示传入main函数的数组元素个数,为int类型, 而 argv = argument vector :表示传入main函数的指针数组,为char*类型。 第一个数组元素argv[0]是程序名称,并且包含程序所在的完整路径。argc至少为1,即 argv数组至少包含程序名。*/ //本个程序,由于是两个文件,main函数需要传入两个参数,由于计入本身程序,所以argc=3,int main(int argc,char*argv[]) { //调试格式, if(argc!=3) { printf("Usage:%s file1 file2\n",argv[0]); exit(1); } //线程t1,t2 pthread_t t1,t2; int res; //初始化临界区 res = pthread_mutex_init(&counter_clock,NULL); if(res!=0) { perror("Mutex initialization failed\n"); exit(EXIT_FAILURE); } /*int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg); 创建线程1 第一个参数为指向线程标识符的指针。 第二个参数用来设置线程属性。 第三个参数是线程运行函数的起始地址。 最后一个参数是运行函数的参数。*/ res = pthread_create(&t1,NULL,count_words,argv[1]); printf(" Thread 01 creating...\n"); if(res!=0) { perror("Thread create failly\n"); exit(EXIT_FAILURE); } else { printf(" Thread 01 create successfully!\n"); } //创建线程2 res = pthread_create(&t2,NULL,count_words,argv[2]); printf(" Thread 02 creating...\n"); if(res!=0) { perror("Thread create failly\n"); exit(EXIT_FAILURE); } else { printf(" Thread 02 create successfully!\n"); } //让线程1进入等待态 res = pthread_join(t1,NULL); printf(" Thread 01 joinning...\n"); if(res!=0) { perror("Thread join failly\n"); exit(EXIT_FAILURE); } else { printf(" Thread 01 joined successfully!\n"); } //让线程2进入等待态 res = pthread_join(t2,NULL); printf(" Thread 02 joinning...\n"); if(res!=0) { perror("Thread join failly\n"); exit(EXIT_FAILURE); } else { printf(" Thread 02 joined successfully!\n"); } //输出统计出来的单词总数 printf("There are %d words in two files\n",total_word); pthread_mutex_destroy(&counter_clock); return 0; }

运行结果:
实验一 进程和线程
文章图片

实验一 进程和线程
文章图片

【实验一 进程和线程】参考链接
1、thread_create函数详解:https://blog.csdn.net/zhou1021jian/article/details/71514738
2、thread_join函数详解:https://blog.csdn.net/yzy1103203312/article/details/80849831
pthread_join()函数原型: int pthread_join(pthread_t thread, void **retval); args: pthread_t thread: 被连接线程的线程号 void **retval: 指向一个指向被连接线程的返回码的指针的指针 return: 线程连接的状态,0是成功,非0是失败

当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前(主)线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:
1、被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
2、 一个线程只能被一个线程所连接。
3、被连接的线程必须是非分离的,否则连接会出错。

所以可以看出pthread_join()有两种作用:
  • 用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
  • 对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。

    推荐阅读