实验一 进程和线程

设计 - 多线程实现单词统计工具
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张图片

参考链接
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() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。

你可能感兴趣的:(操作系统课程设计)