该文章参考于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状态),则进程运行结束后,自动释放所有资源。