线程

该文章参考于http://blog.csdn.net/yx_l128125/article/details/7697211http://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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值