线程
来源:互联网 发布: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_push和pthread_cleanup_pop函数用于自动释放资源。
使用时将待处理的线程代码放在pthread_cleanup_push和pthread_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状态),则进程运行结束后,自动释放所有资源。
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- 线程
- CodeForces Round 218 E Subway Innovation
- loadrunner 监控linux资源
- HRBUST 1908 A+B+C 高精度加法
- uikit 类图
- 进程控制理论<五>--进程通信方式对比
- 线程
- Java的不定长度参数
- Spring mvc 注释 + ibatis 整合
- POJ 1751 Highways (ZOJ 2048 ) MST
- QTabWidget实现类似QQ聊天窗口
- Python的包管理工具Pip
- android listView 点击无响应的解决方法
- DNP3执行漏洞
- 1999~2012年全国百优博士论文 (计算机类)