线程

来源:互联网 发布:ubuntu卸载搜狗 编辑:程序博客网 时间:2024/06/06 09:44

该文章参考于http://blog.csdn.net/yx_l128125/article/details/7697211和http://blog.csdn.net/harry_lyc/article/details/6055734,在此先感谢这两位高人。其中该文章中间加入自己理解和验证。

一、线程理论基础

     1、使用多线程的理由(即优点):

       (1)和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给他独立的地址空间,建立众多的数据表来为维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。

       (2)线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间的通信方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

       归纳为:节省空间、节省时间

   2、多线程

       Linux系统下的多线程遵循POSIX线程接口,称为pthread.

       编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a

  3、线程执行

        线程何时执行?  执行的条件是:当建立该线程的进程出现阻塞时,执行该进程。(下面会做验证)

二、多线程的程序设计

2.1创建线程

      头文件:#include <pthread.h>

      函数原型:int  pthread_create(pthread_t * tidp, const pthread_attr_t *attr, void *(*start_rtn)(void), void *arg);

      参数说明:tidp : 线程id(该值通过成功创建线程后获取的)

                      attr : 线程属性(通常为空)

                      start_rth : 线程要执行的函数

                      arg : 运行函数的参数,例如start_rtn

           返回值:  成功返回0,失败返回错误编号。

        编译:因为pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread

                   # gcc filename.c -lpthread   -o  filename

              

线程创建及多线程执行顺序的源码

/*************************************************  试验功能: 1、测试线程与创建该线程的进程的 执行顺序 **             2、测试多个线程的  执行顺序 **  测试方法:通过程序不同位置处加sleep()函数实现 **  测试日期: 2013年12月17日**  测试人员: hailin    ************************************************/#include<stdio.h>   #include<pthread.h>   #include<stdlib.h>   #include<unistd.h>   #include<signal.h>   static void thread_one(char* msg);  static void thread_two(char* msg);  int  main(int argc, char** argv)  {      pthread_t th_one,th_two;      char * msg="thread";      printf("thread_one starting/n");      if(pthread_create(&th_one,NULL,(void*)&thread_one,msg)!=0)      {          exit(EXIT_FAILURE);      }      sleep(1);// @1       printf("thread_two starting/n");      if(pthread_create(&th_two,NULL,(void*)&thread_two,msg)!=0)      {          exit(EXIT_FAILURE);      }      printf("Main thread will sleep 1 S/n");      sleep(1);//@2       return 0;        }  static void thread_one(char* msg)  {      int i=0;      while(i<6)      {          printf("I am one. loop %d/n",i);          i++;          //sleep(1); //@3     }  }  static void thread_two(char* msg)  {      int i=0;      while(i<6)      {          printf("I am two. loop %d/n",i);          i++;          //sleep(1);  //@4     }  } 

疑问:1、主进程同进程,如何执行?

            2、在主进程中创建多个线程,多个线程执行顺序如何确定?

下面对该疑问通过实例做出解答:

           1、主进程没有执行等待

                  在主线程没有执行等待的情况下,即@1和 @2 在注释掉的情况下。

               程序的运行如下:

                                               thread_one starting

                                               thread_two starting

                                               Main thread will sleep 1 S

               由此可见,线程的执行条件:是在创建它的主线程有空闲(即处于阻塞状态)。上面的程序主进程没有空闲,所以线程一直未执行。

 

             2、主进程加入执行等待(线程有机会执行)

                       2.1程序中@1中执行等待,而@2不执行等待

                          程序输出结果:                       

                                              thread_one starting

                                              I am one. loop 0

                                              I am one. loop 1

                                              I am one. loop 2

                                              I am one. loop 3

                                              I am one. loop 4

                                              I am one. loop 5 (此处停顿一下,直到@1处sleep(1)休眠1秒计时结束

                                              thread_two starting

                                              Main thread will sleep 1 S  

 

                        程序执行过程:第一步:主进程创建线程1后,执行@1处休眠1秒(此时主进程处于阻塞状态)

                                                    第二步:执行线程1(因为此时主进程处于阻塞)

                                                    第三步:线程1执行完后退出线程,进入进程。此时主进程继续等待 休眠1秒结束

                                                    第四步:休眠1秒结束 ,主进程继续向下执行(创建线程2 、输出、返回退出)                                  

 

                  2.2程序中@1中不执行等待,而@2执行等待

                    程序输出结果:

                                   

                             多线程的执行顺序:由进程创建的先后顺序决定。

 

                  2.3程序中@1中执行等待,而@2执行等待

                    程序输出结果:

                                        

                 3、线程中加入等待

                     3.1@1@2@3@4中个等待1秒。

                   程序输出结果:

                     

                    @1处,主线程等待了1S,让线程1执行。线程1执行一次循环,等待了1S。由于超过了主进程的等待时间,主进程在等待够 1S后,继续执行。由于线程1在子线程2显示输出时,被激活,所以子线程又循环一次后,子线程2输出结果。

                         

                        3.2@1@3@4中个等待1秒,@2中等待3

                   程序输出结果:

                   

                   在主进程等待3秒时,由于线程1和线程2都已经执行。并且执行一次循环后,等待1秒。所以,运行的结果时线程1和线程2交替运行。

 

2.2线程的数值传递

/*************************************************  试验功能: 测试线程与主进程的 数据共享  **  测试方法: 线程函数中参数实现 **  测试日期: 2013年12月17日**  测试人员: hailin    ************************************************/#include <stdio.h>#include <pthread.h>#include <unistd.h>void *create(void *arg){    int *num;    num=(int *)arg;/*强制转换为一个整型的指针*/    printf("create parameter is %d \n",*num);/*从整型指针了把整数取出来*/    return (void *)0;}int main(int argc ,char *argv[]){    pthread_t tidp;    int error;       int test=4;    int *attr=&test;/*把整型变量的地址赋给指针&test因为pthread_create()第四个参数必须是指针*/    error=pthread_create(&tidp,NULL,create,(void *)attr);    if(error)    {        printf("pthread_create is created is not created ... \n");        return -1;    }        sleep(1);    printf("pthread_create is created ...\n");    return 0;       }


程序输出:  create parameter is 4

                      pthread_create is created ...

 同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用;例程:pthread_share证明了这一点

2.3等待线程

             功能:阻塞调用线程,直到指定的线程终止,当函数返回时,被等待的线程占用的资源回收。

            头文件:#include  <pthread.h>

            函数原型:int pthread_join(pthread_ttid ,  void **rval_ptr)

            参数说明:tid : 要等待退出的线程id            rval_ptr : 线程退出的返回值的指针

            返回值:若成功返回0,否则返回错误编号

注意两点:

         (1) 进程创建线程首先是先运行进程的;

         (2)   pthread_join(pth,NULL);  //让进程等待,等到指定id线程全部运行完后,才往下运行

         (3)线程中调用getpid()获得的还是进程的id;

源码:

#include <stdio.h>#include <pthread.h>void *myThread1(void){    int i;    for (i=0; i<3; i++)    {        printf("This is the 1st pthread,created by zieckey.\n");        sleep(1);    }}int main(){    int i=0, ret=0;    pthread_t id1,id2;         /*创建线程1*/    ret = pthread_create(&id1, NULL, (void*)myThread1, NULL);/*第三个参数:线程要执行的函数*/    if (ret)    {        printf("Create pthread error!\n");        return 1;    }    /*等待线程1结束*/     pthread_join(id1, NULL);    return 0;}

程序运行输出:

                  success  Create mypthread1 id1=b7534b40 
                  This is the 1st pthread,created by zieckey.

                   延时1秒
                  This is the 1st pthread,created by zieckey.

                   延时1秒
                  This is the 1st pthread,created by zieckey.

       验证进程等待函数pthread_join(id1, NULL);  等到线程1全部执行完成,才向下执行return 0。

       但是程序中将pthread_join(id1, NULL);和sleep(1)去除,输出结果同上(除了没有延时1s)。个人比较疑惑:难道主进程执行过程有空闲时间,导致线程可以运行?

 

2.4终止线程

线程终止有两种情况:(1)正常终止(2)非正常终止

(1)正常终止:线程主动调用pthread_exit或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;

(2)非正常终止:线程在其他线程的干预下 或由于自身运行出错(比如访问非法地址)   而退出,这种退出方式是不可预见的。

注:如果进程中任何一个线程中调用exit_exit,那么整个进程都会终止。(会导致线程退出)

  函数说明:

              功能:            终止调用线程  

              头文件:          #include  <pthread.h>

              函数原型:       void pthread_exit(void *rval_ptr)

              参数说明 :      rval_ptr : 线程退出返回值的指针

              返回值:         无

 

 2.6线程的清除函数

     不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑解决的问题。

      为了解决该问题,在线程API中提供了pthread_cleanup_pushpthread_cleanup_pop函数用于自动释放资源。

       使用时将待处理的线程代码放在pthread_cleanup_pushpthread_cleanup_pop函数之间,当程序运行到它们之间发生终止动作(包括调用函数pthread_exit()和非正常退出)时,都执行pthread_cleanup_push( )所指定的清理函数进行线程清除工作。


函数说明:

     1、

将清除函数压入堆栈

              头文件:    #include <pthread.h>

              函数原型:  void pthread_cleanup_push(void(*rth)(void*),void *arg)

               参数说明:  1、

rth : 清除函数

                                    2、arg :清除函数的参数

               返回值:     无

 

      2、

将清除函数弹出堆栈

              头文件:    #include <pthread.h>

              函数原型:

void  pthread_cleanup_pop(int execute)

              参数说明:

excute 执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,

                                   excute的值为:非0:执行   0:不执行

              返回值:     无

源码:

/***********************************************************实验要求:   在程序中创建一个线程,使用线程API对该线程进行清理工作。*功能描述:   创建线程,并在其中使用函数pthread_cleanup_push和函数*           pthread_cleanup_pop,验证这两个清理函数的效果。*日    期:   2010-9-17*作    者:   国嵌**********************************************************/#include <stdio.h>#include <pthread.h>#include <unistd.h>/* * 线程清理函数 * */void *clean(void *arg){    printf("cleanup :%s\n",(char *)arg);    return (void *)0;}/* * 线程1的执行函数 * */void *thr_fn1(void *arg){    printf("thread 1 start  \n");/*将线程清理函数压入清除栈两次*/    pthread_cleanup_push( (void*)clean,"thread 1 first handler");    pthread_cleanup_push( (void*)clean,"thread 1 second hadler");    printf("thread 1 push complete  \n");    if(arg)                 {    /*线程运行到这里会结束,后面的代码不会被运行。由于是用return退出,所以不会执行线程清理函数。同时返回数值1。 */         return((void *)1);     }    pthread_cleanup_pop(0); //@ 1    pthread_cleanup_pop(0);//@2     return (void *)1;}/* * 线程2的执行函数 * */void *thr_fn2(void *arg){    printf("thread 2 start  \n");/*将线程清理函数压入清除栈两次*/    pthread_cleanup_push( (void*)clean,"thread 2 first handler");    pthread_cleanup_push( (void*)clean,"thread 2 second handler");    printf("thread 2 push complete  \n");    if(arg)    {        /*  线程运行到这里会结束,后面的代码不会被运行。由于是用pthread_exit退出,  所以会执行pthread_cleanup_push指定线程清理函数。执行的顺序是先压进栈的后执行,即后进先出。  执行完成后,将返回数值2传递给pthread_join(tid2,&tret)中tret参数。 */ pthread_exit((void *)2);    }                                             pthread_cleanup_pop(0);//    pthread_cleanup_pop(0);//    pthread_exit((void *)2);}/* * 程序入口 * */int main(void){    int err;    pthread_t tid1,tid2;    void *tret;/*创建线程1并执行线程执行函数*/    err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);    if(err!=0)    {        printf("error .... \n");        return -1;    }/*创建线程2并执行线程执行函数*/    err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);if(err!=0)    {        printf("error .... \n");        return -1;    }/*阻塞等待线程1退出,并获取线程1的返回值(传递给tret参数)*/    err=pthread_join(tid1,&tret);    if(err!=0)    {        printf("error .... \n");        return -1;    }    printf("thread 1 exit code %d  \n",(int)tret);/*阻塞等待线程2退出,并获取线程2的返回值(传递给tret参数)*/    err=pthread_join(tid2,&tret);    if(err!=0)    {        printf("error .... ");        return -1;    }printf("thread 2 exit code %d  \n",(int)tret);      return 1;}
输出结果:

                                  
  本人自己对程序理解:1、执行main程序时,创建线程1和线程2,执行pthread_join(tid1,&tret)等待线程1执行完毕并返回

                                          2、进入线程1执行两次压栈操作,然后执行return(1)退出,后面代码不执行。由于使用rerun退出,所以不会执行pthread_cleanup_push指定线程清理函数。

                                          3、因为从线程1退出,所以执行pthread_join(tid1,&tret)下面代码。执行线程2的等待执行pthread_join(tid2,&tret).

                                           4、进入线程2执行两次压栈操作,然后执行pthread_exit((void *)2);退出,后面代码不执行。

                                               个人疑惑:什么时候输出 cleanup :thread 2 second handler和cleanup :thread 2 first handler语句?

                                               分析原因

                                                a、由pthread_cleanup_pop(0);控制输出的吗?不是,因为pthread_cleanup_pop( execute)中参数execute为0时,不执行清理函数,只有非0时,才执行清理函数。

                                               b、因为执行pthread_exit((void *)2);已经退出线程,肯定不会执行pthread_cleanup_pop(0);所以自己的分析是不对的。


验证pthread_cleanup_pop(非0);情形,是否执行指定的清理函数:

先将线程1函数做些修改

void *thr_fn1(void *arg){    printf("thread 1 start  \n");/*将线程清理函数压入清除栈两次*/    pthread_cleanup_push( (void*)clean,"thread 1 first handler");    pthread_cleanup_push( (void*)clean,"thread 1 second hadler");    printf("thread 1 push complete  \n");    pthread_cleanup_pop(1); //@ 1    pthread_cleanup_pop(1);//@2     return (void *)1;}
程序输出:

                          

小结: 通过查看pthread_cleanup_push和pthread_cleanup_pop(0)用法可知

1、它们是配对使用的,在上面线程程序中pthread_cleanup_pop(0)表面上看,无任何作用。

2、执行pthread_cleanup_push( )所指定的清理函数由以下3种情形(不包括return返回情形)

     1、调用函数pthread_exit()

     2、 响应取消请求时(包括非正常退出)

     3、调用参数非0的pthread_cleanup_popop(参数)

     例如:线程1中通过return终止,没有执行pthread_cleanup_push( )所指定的清理函数进行线程清除工作。

                 线程2中通过pthread_exit((void *)2);退出,执行指定清理函数。

3、堆栈的操作:先进后出。为什么先执行cleanup :thread 2 second handler再执行cleanup :thread 2 first handler?因为:pthread_cleanup_push( (void*)clean,"thread 2 first handler"先执行先被压入堆栈,而pthread_cleanup_push( (void*)clean,"thread 2 second handler"后执行后被压入堆栈;而堆栈有“先进后出”的规则)

2.7线程标识

       功能:    获取调用线程的thread identifier(线程标识符)  

      头文件:      #include  <pthread.h>

      函数原型:   pthread_t  pthread_self (void)

      返回值:      返回线程描述符

源码:     

#include <stdio.h>#include <pthread.h>#include <unistd.h> /*getpid()*/void *create(void *arg){    printf("New thread .... \n");    /*获取线程的描述符ID,并输出*/    printf("This thread's id is %u  \n", (unsigned int)pthread_self());    /*获取进程的描述符ID,并输出*/    printf("The process pid is %d  \n",getpid());    return (void *)0;}int main(int argc,char *argv[]){    pthread_t tid;    int error;        printf("Main thread is starting ... \n");    error = pthread_create(&tid, NULL, create, NULL);    /*输出线程的描述符ID,同线程中pthread_self()比较是否一致*/    printf("Main thread's ID=%u\n",tid);    if(error)    {        printf("thread is not created ... \n");        return -1;    }/*获取并输出进程ID, 同线程中获取进程ID   比较是否一致*/    printf("The main process's pid is %d  \n",getpid());    sleep(1);    return 0;}

程序输出结果:

                      Main thread is starting ...   (主进程执行)
                      Main thread's ID=3075685184
                     The main process's pid is5111 
                     New thread ....                        (线程执行)
                    This thread's id is 3075685184 
                    The process pid is5111 

由上面运行结果可知:1、线程属于创建它进程一部分,所以获取共用一个进程的描述符ID   

                                        2、获取进程描述符函数pthread_self()的返回值 与创建进程生成描述符ID一致(即创建函数中参数tid) 

                              

总结:

1、进程是资源分配的最小单位,而线程是调度的最小单位

2、进程有独立的地址空间、拥有自己的代码段、数据段、堆栈段,而线程只有独立的堆栈段。

3、数值传递方式:进程有多种通信方式可实现数据传递,而线程只有全局变量和创建是传值(pthread_create()函数中参数arg

4、线程中资源回收问题:线程中通过pthread_join()函数来等待线程全部运行结束,回收资源。若进程运行结束而不pthread_join(),则产生类似进程中 讲述进程 造成资源浪费。

5、线程等待过程中 主进程阻塞问题:通过在线程中加入 pthread_detach(pthread_self())将线程状态设置为detached(分离状态,默认为joinable状态),则进程运行结束后,自动释放所有资源。

0 0
原创粉丝点击