21 pthread——pthread_create、pthread_join、互斥量、条件量

1、线程涉及的系统调用

  • pthread_create():创建一个新的线程,int pthread_create(pthread_t *thread,pthread_attr_t *attr,void func,void *arg),thread指向pthread_t类型变量的指针,attr指向pthread_attr_t类型变量的指针或者为NULL,func为新进程所运行的函数,arg为传递给func的参数,返回0成功否则失败。pthread_create函数创建一条新的执行路线,在此新的路程内调用了func(arg),新线程的属性由attr参数来指定。func是一个函数,它接收一个指针作为它的参数,并且运行结束后返回一个指针,func参数和返回值定义为类型void*的指针,以允许它们指向任何类型的值。
  • pthread_join():等待某线程终止,int pthread_join(pthread_t thread,void **retval),thread为所等待的线程,retval指向某存储线程返回值的变量,返回0成功否则失败。pthread_join使得调用线程挂起直到由thread参数指定的线程终止。如果retval不是NULL,线程的返回值就将存储在由retval指向的变量中。

2、线程间的分工与合作

多线程在一个单独的进程中运行,共享全局变量,因此线程间可以通过设置和读取这些全局变量来进行通信。这里用统计两个文件单词数多线程程序例子说明。

  • twordcount1.c:两个线程、一个计数器,创建分开的线程对每一个文件进行计算,所有的线程在检查到单词的时候对同一个计数器增值。问题:当线程对计数器同时进行增值时会导致计数器计数失败,所有线程取出同样的计数器的值,对寄存器增1,然后再恢复新的值,线程间互相干扰。
  • twordcount2.c:两个线程、一个计数器、一个互斥量,两个线程需要安全地共享一个公共的计数器,它们也需要这样一种方法把变量加锁,任何数目的线程都可以挂起等待互斥量解锁。所以如果多个线程在同一时刻修改相同的变量,它们只好使用互斥量来避免访问冲突。问题:使用互斥量使得程序运行速度变慢,对所有文件的每一个单词都需要执行检查,设置以及释放锁的操作,使得程序效率低下。
  • twordcount3.c:两个线程、两个计数器、向线程传递多个参数,字数统计程序为每个线程设置自己的计数器,从而避免了对于互斥量的使用,当线程返回之后,将这两个计数器的值加起来得到最后的结果。通过定义一个以文件名和字数为成员的结构体解决了同时传递两个参数的问题(func的arg只能传递一个参数),传递本地结构体指针的方法既避免了对互斥量额依赖,又消除了全局变量。

线程间的分工与合作总结:

进程的数据空间包含了所有属于它的变量,此进程中运行的所有线程都拥有对这些变量的访问的权限。如果这些变量值不变的话,线程可以无误地读取并使用它们的值。不过如果进程中任何线程修改了一个变量值,所有使用此变量的线程必须采用某种策略来避免访问冲突。在某时刻,只有唯一的线程可以对变量进行访问。

互斥量加锁解锁的系统调用:

  • pthread_mutex_lock():等待互斥锁解开然后再锁住互斥量,int pthread_mutex_lock(pthread_mutex_t *mutex),mutex指向互斥锁对象的指针,返回0成功否则失败。pthread_mutex_lock函数用来锁住指定的互斥量。如果互斥量是开放的,它被锁住并只能由调用线程来管理;如果此时互斥量已经被另外的线程锁住,调用线程将挂起等待此互斥量被解锁。
  • pthread_mutex_unlock():给互斥量解锁,int pthread_mutex_unlock(pthread_mutex_t *mutex),mutex指向互斥锁对象的指针,返回0成功否则失败。pthread_mutex_unlock函数给指定的互斥量解锁。如果有线程挂起等待此互斥量,其中的一个线程将获得对互斥锁的控制权。

3、线程和进程

Unix从其产生伊始就将进程作为它的重要组成部分而线程是后来才加进去的。进程和线程有根本上的不同。每个进程有其独立的数据空间、文件描述符以及进程的ID;而线程共享一个数据空间、文件描述符以及进程ID。

(1)共享数据空间

  • 多个线程共享数据集,如果变量不会被改变,共享这个数据空间不会导致任何问题,但如果使用malloc和free系统调用来管理内存,一个线程分配到一块空间存储字符串,当此线程做其他事情的时候,另一个线程使用free释放了这块空间,那么原来的线程中本来指向此空间的指针现在指向了一块已经被释放的空间。
  • 线程机制还会带来内存的囤积,程序员往往因为害怕影响某线程正在使用的内存空间,只分配而不释放存储区域,这直接导致内存的囤积,使用完毕也得不到释放。
  • 在单线程环境中返回指向静态局部变量的指针的函数无法兼容于多线程环境

(2)共享文件描述符

在调用fork之后,文件描述符自动地被复制,从而子进程得到了一套新的文件描述符。在多线程中,很有可能会将同一个文件描述符传递给两个不同的线程,即传递给它们的两个值指向同一个文件描述符,如果一个线程中的函数关闭了这个文件,此文件描述符对此进程中的任何线程来说都已经被关闭。然而其他线程或许仍然需要对此文件描述符的连接。

(3)fork、exec、exit、signals共享一个进程

所有的线程共享同一个进程。如果一个线程调用了exec,系统内核用一个新的程序取代当前的程序从而所有正在运行的线程都会消失,并且如果一个线程执行exit,那么整个进程都将结束运行。如果线程中的某函数调用了fork,那么其他的线程是不会被复制到新的进程,只有调用fork的线程在新的进程中运行。

4、线程间通信

在twordcount4.c中使用条件变量进行了线程间的通信,线程间通信涉及到pthread_cond_wait、pthread_cond_signal系统调用:

  • pthread_cond_wait():使线程挂起,等待某条件变量的信号,int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex),cond指向某条件变量的指针,mutex指向互斥锁对象的指针,返回0成功否则失败。pthread_cond_wait使线程挂起直到另一个线程通过条件变量发出消息,pthread_cond_wait函数总是和互斥锁在一起使用,此函数先自动释放指定的锁,然后等待条件变量的变化。
  • pthread_cond_signal():唤醒一个正在等待的线程,int pthread_cond_signal(pthread_cond_t *cond),cond指向某条件变量的指针,返回0成功否则失败。pthread_cond_signal函数通过条件变量cond发消息。若没有线程等待消息,什么都不发生;若是多个线程都在等待,只唤醒它们中的一个。

5、编写程序代码

twordcount1.c

#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
int total_words=0;
void count_words(char *f)
{
    FILE *fp;
    char c,prevc='\0';
    if((fp=fopen(f,"r"))!=NULL)
    {
        while((c=getc(fp))!=EOF)
        {
            //单词与单词之间有非字母数字间隔
            if(!isalnum(c)&&isalnum(prevc))
                total_words++;
            prevc=c;
        }
        fclose(fp);
    }
    else
        perror(f);
}
int main(int argc,char *argv[])
{
    pthread_t t1,t2;
    if(argc!=3)
    {
        printf("usage:%s file1 file2",argv[0]);
        exit(1);
    }
    total_words=0;
    pthread_create(&t1,NULL,count_words,argv[1]);
    pthread_create(&t2,NULL,count_words,argv[2]);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    printf("%d:total words\n",total_words);
    return 0;
}

twordcount2.c

#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
int total_words=0;
pthread_mutex_t counter_lock=PTHREAD_MUTEX_INITIALIZER;
void count_words(char *f)
{
    FILE *fp;
    char c,prevc='\0';
    if((fp=fopen(f,"r"))!=NULL)
    {
        while((c=getc(fp))!=EOF)
        {
            //单词与单词之间有非字母数字间隔
            if(!isalnum(c)&&isalnum(prevc))
            {
                pthread_mutex_lock(&counter_lock);
                total_words++;
                pthread_mutex_unlock(&counter_lock);
            }
            prevc=c;
        }
        fclose(fp);
    }
    else
        perror(f);
}
int main(int argc,char *argv[])
{
    pthread_t t1,t2;
    if(argc!=3)
    {
        printf("usage:%s file1 file2",argv[0]);
        exit(1);
    }
    total_words=0;
    pthread_create(&t1,NULL,count_words,argv[1]);
    pthread_create(&t2,NULL,count_words,argv[2]);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    printf("%d:total words\n",total_words);
    return 0;
}

twordcount3.c

#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
struct arg_set{
    char *fname;
    int count;
};
void count_words(struct arg_set *args)
{
    char *f=args->fname;
    FILE *fp;
    char c,prevc='\0';
    if((fp=fopen(f,"r"))!=NULL)
    {
        while((c=getc(fp))!=EOF)
        {
            //单词与单词之间有非字母数字间隔
            if(!isalnum(c)&&isalnum(prevc))
                args->count++;
            prevc=c;
        }
        fclose(fp);
    }
    else
        perror(f);
}
int main(int argc,char *argv[])
{
    pthread_t t1,t2;
    struct arg_set args1,args2;
    if(argc!=3)
    {
        printf("usage:%s file1 file2",argv[0]);
        exit(1);
    }
    args1.fname=argv[1];
    args1.count=0;
    args2.fname=argv[2];
    args2.count=0;
    pthread_create(&t1,NULL,count_words,&args1);
    pthread_create(&t2,NULL,count_words,&args2);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    printf("%d:total words\n",args1.count+args2.count);
    return 0;
}

twordcount4.c

#include<stdio.h>
#include<pthread.h>
#include<ctype.h>
#include<stdlib.h>
struct arg_set{
    char *fname;
    int count;
};
struct arg_set *mailbox;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t flag=PTHREAD_COND_INITIALIZER;
void count_words(struct arg_set *args)
{
    char *f=args->fname;
    FILE *fp;
    char c,prevc='\0';
    if((fp=fopen(f,"r"))!=NULL)
    {
        while((c=getc(fp))!=EOF)
        {
            //单词与单词之间有非字母数字间隔
            if(!isalnum(c)&&isalnum(prevc))
                args->count++;
            prevc=c;
        }
        fclose(fp);
    }
    else
        perror(f);
    printf("CPUNT:waiting to get lock\n");
    //代码保护区,保护lock和flag,防止线程同时操作变量
    pthread_mutex_lock(&lock);
    printf("COUNT:have lock,storing data\n");
    //只有mailbox为空时才占领mailbox,否则挂起线程等待信号
    if(mailbox!=NULL)
        pthread_cond_wait(&flag,&lock);
    mailbox=args;
    printf("COUNT:raising flag\n");
    //发送信号唤醒等待的线程
    pthread_cond_signal(&flag);
    printf("COUNT:unlocking box\n");
    pthread_mutex_unlock(&lock);
}
int main(int argc,char *argv[])
{
    pthread_t t1,t2;
    struct arg_set args1,args2;
    int reports_in=0;
    int total_words=0;
    if(argc!=3)
    {
        printf("usage:%s file1 file2",argv[0]);
        exit(1);
    }
    //代码保护区
    pthread_mutex_lock(&lock);
    args1.fname=argv[1];
    args1.count=0;
    args2.fname=argv[2];
    args2.count=0;
    pthread_create(&t1,NULL,count_words,&args1);
    pthread_create(&t2,NULL,count_words,&args2);
    while(reports_in<2)
    {
        printf("MAIN:waiting for flag to go up\n");
        //挂起线程等待信号
        pthread_cond_wait(&flag,&lock);
        printf("MAIN:flag was raised,I have the lcok\n");
        printf("%d:%s\n",mailbox->count,mailbox->fname);
        total_words+=mailbox->count;
        if(mailbox==&args1)
            pthread_join(t1,NULL);
        if(mailbox==&args2)
            pthread_join(t2,NULL);
        mailbox=NULL;
        pthread_cond_signal(&flag);
        reports_in++;
    }
    printf("%d:total words\n",args1.count+args2.count);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值