线程及线程的创建
时间:2010-10-20 来源:osullishuai80
一、线程的理论基础
(1)在Linux中,进程(process)是操作系统分配资源的最小单位,线程(thread)是操作系统调度的最小单位.
(2)使用多线程的理由:
<1>与进程相比,线程是一种非常节俭的多任务操作方式.
在Linux中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种昂贵的多任务工作方式.而一个线程与它的父线程共享这些资源,这样就大大节省了系统资源.
<2>线程间方便的通信机制.对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的某种机制进行,这种方式不仅费时,而且很不方便.线程则不然,由于同一进程下的线程之间共享父进程的数据段、代码段和堆栈段等,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便.比如在一个线程中修改的变量值,可以在另一个线程中直接应用,不必再占用新的资源.比起进程间通信的那些机制(管道、FIFO、消息队列等)显得非常方便.
<3>多线程程序作为一种多任务、并发的工作方式,使多CPU系统更加有效,也可以改善程序结构.比如操作系统会保证线程数不大于CPU数目时,不同的线程运行不同的CPU上.又如一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样程序会利于理解和修改.
(3)Linux系统下的多线程遵循POSIX线程接口,称作pthread.这些接口被封装在pthread库内(不在标准c库内).因此,编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a
(4)所有进程至少有一个线程,多线程的意义在于一个进程中有多个执行部分同时执行.每个线程处理各自独立的任务.
(5)多任务程序设计:
一个进程可能要处理不同的应用,要求处理多种任务.如果开发不同的进程来处理,系统开销很大,数据共享、程序结构都不方便,这时可使用多线程编程方法.
(6)并发程序设计:
一个任务可能分不同的步骤,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥、同步并发完成.这样可以为不同的任务步骤建立线程.
(7)就像每个进程都有一个ID一样,每个线程也有一个ID.进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效. 二、多线程程序设计
线程的相关函数都被封装在libthread.a库中,头文件为pthread.h.由于pthread的库不是Linux系统的库,所以在进行编译时需要加上"-lpthread",即#gcc filename -lpthread.如果编译时不使用该库,则Linux提示:"undefined reference to 'pthread_create'",即找不到该函数.
(1)线程的创建
#include <pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void),void *arg)
函数功能:创建进程
参数tidp:线程ID
参数attr:线程属性,通常设置为空(NULL)
参数start_rtn:线程要执行的函数
参数arg:函数start_rtn的参数
返回值:
Attention!!!
1.在线程创建后,就开始运行相关的线程函数,该哈数运行完后,该线程也就结束了.
2.编译程序时,对于标准C库不需要指定库名,而对于数据库、线程库都需要指定库名.
3.创建线程时,线程处理函数的参数必须是void *类型,返回值也必须是*void型.
4.这两个线程谁先执行并不一定,由操作系统的算法决定.一般来说,先创建的线程先被执行.
5.进程的ID可由函数getpid()返回值得到,线程的ID可由函数pthread_self()返回值得到.若用户需要打印某线程的ID,需要在线程处理函数中作如下处理:printf("the thread ID is %u\n",(unsigned int)pthread_self());
6.关于线程处理函数的返回值.
若需要该返回值,假定返回值为ret,则写成: return ((void *)set);若不需要返回值,则直接写成: return NULL;
(2)线程的终止
若进程中任何一个线程中调用exit或_exit,则整个进程(包括进程所属线程)都会终止.线程有3种退出方式:
1、线程从启动例程中正常返回.比如线程函数执行到return NULL.
2、线程可以被另一个进程终止.
3、线程自己调用pthread_exit()函数.
#include <pthread.h>
void pthread_exit(void *rval_ptr)
函数功能:终止调用线程
参数rval_ptr:线程退出返回值.进程中的其它线程可以通过pthread_join()访问到该指针.
例程:
void *create(void *arg)
{
printf("new thread is created ... \n");
//return (void *)8;
//pthread_exit((void *)8);
//exit(0);
} int main(int argc,char *argv[])
{
pthread_t tid;
int error;
void *temp; error = pthread_create(&tid, NULL, create, NULL);
printf("main thread!\n"); if( error )
{
printf("thread is not created ... \n");
return -1;
}
error = pthread_join(tid, &temp); if( error )
{
printf("thread is not exit ... \n");
return -2;
}
printf("thread is exit code %d \n", (int )temp);
return 0;
}
执行结果:
第1种情况: thread is exit code 8 //正常退出线程
第2种情况: thread is exit code 8 //正常退出线程
第3种情况: 无法打印出"thread is exit code %d \n",这是由于exit(0)直接退出了进程.
(3)线程的等待
#include <pthread.h>
int pthread_join(pthread_t tid,void **rval_ptr)
函数功能:阻塞线程调用,直到指定的线程终止.调用该函数的线程将被挂起,将一直等待到所等待线程结束为止.
参数1:等待退出的线程的ID
参数2:线程退出时的返回值的指针.
(4)线程的终止
#include <pthread.h>
void pthread_cancel(pthread_t tid)
函数功能:线程可以通过该函数来请求取消同一进程中的其它线程.该函数会发送终止信号给线程号为tid的线程,然后发送成功并不意味着tid线程就一定能终止.它可以选择忽略或自己控制取消的方式.
返回值:如果成功则返回0,不成功则返回非0.
(5)线程的清理
线程的终止有两种情况:正常终止和非正常终止.
当线程主动调用pthread_exit()或从线程函数中return都将使线程正常退出,这是可预见的退出方式.
当线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而推出,这是不可预见的退出方式.
无论是哪种方式推出,都会存在资源释放的问题.如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑的问题.比如在一个线程中malloc一段内存空间,如果在线程退出时不释放这段内存,就有可能导致内存泄露.由于用户不清楚该线程什么时候被非正常终止(非预知性),也就不知道在线程处理函数的哪里来释放掉这段内存,因此,必须有一种机制来处理该问题.
在Linux中,使用成对出现的pthread_cleanup_push和pthread_cleanup_pop来处理该问题.从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push所指定的清理函数.
在Linux中,线程可以建立多个清理处理程序.处理程序在栈中,即它们的执行顺序与它们压入栈内时的顺序相反.
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *),void *arg)
函数功能:将清除函数压入清除栈
参数1:指向清除函数的指针
参数2:清除函数的参数
#include <pthread.h>
void pthread_cleanup_pop(int execute)
函数功能:将清除函数弹出清除栈
参数:execute执行到pthread_cleanup_pop时,是否在弹出清除函数的同时执行该函数,非0:执行;0:不执行.
Attention!!!
1.清理函数是为了给子线程处理函数擦屁股.
2.包括正常退出和非正常退出都可以使用清理函数,具体清理函数是否执行是由函数pthread_cleanup_pop()的参数决定的.比如在子线程中:
pthread_cleanup_push(cleanup1,NULL);
pthread_cleanup_push(cleanup2,NULL);
此时,这样清理函数cleanup2在栈顶,cleanup1在栈底.若只使用清理函数cleanup1,可以这样来:
pthread_cleanup_pop(0); //cleanup2被弹出,却不执行
pthread_cleanup_pop(1); //cleanup1被弹出,执行
例程:
char *p=NULL;
void cleanup(void *arg)
{
printf("clean up\n");
free(p);
} void *thread1_fun(void *arg)
{
pthread_cleanup_push(cleanup,NULL);
printf("this is new thread\n");
p=(char *)malloc(100);
sleep(5);
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
} int main(int argc,char *agrv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1_fun,NULL);
sleep(1);
pthread_cancel(tid);
while(1);
return 0;
}
执行结果:
this is new thread
clean up
程序分析:
在main()函数中,在主线程内创建了一子线程.但CPU会先执行主线程,该例程中创建了子线程后主线程sleep(1),因此CPU会首先执行子线程.当子线程执行到sleep(5)时,子线程让出CPU的占有权,CPU去执行了主线程.主线程终止了子线程,由于在子线程处理函数中pthread_cleanup_pop的参数为非0,因此非正常退出前要执行清理函数cleanup().
(6)综合例程
thread.c
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <pthread.h>
struct menber
{
int a;
char *s;
};
void *thread1_fun(void *arg)
{
int *j;
j=(int *)arg; //必须进行强制类型转换
while(1)
{
printf("this is thread_1\n");
sleep(1);
}
printf("arg=%d\n",*j);
return NULL;
}
void *thread2_fun(void *arg)
{
char *temp;
temp=(char *)arg; //必须进行强制类型转换
while(1)
{
printf("this is thread_2\n");
sleep(1);
}
printf("temp=%s\n",temp);
return NULL;
}
void *thread3_fun(void *arg)
{
struct menber *p;
p=(struct menber *)arg;
printf("menber->a = %d \n",p->a);
printf("menber->s = %s \n",p->s);
return (void *)0;
} int main(int argc,char *argv[])
{
int i=100;
int err;
pthread_t thread_1,thread_2,thread_3;
void *tret1,*tret2,*tret3;
char *str="hello world i love china!\n";
struct menber b;
b.a = 4;
b.s = "hello embeded"; err = pthread_create(&thread_1,NULL,thread1_fun,&i); //创建线程,并指定到函数thread1_fun()来处理
if(err!=0)
printf("cannot create thread 1:%s\n",strerror(err));
err = pthread_create(&thread_2,NULL,thread2_fun,str);
if(err!=0)
printf("cannot create thread 2:%s\n",strerror(err));
err = pthread_create(&thread_3,NULL,thread3_fun,&b);
if(err!=0)
printf("cannot create thread 3:%s\n",strerror(err));
//sleep(5); //线程必须依赖于进程,因此sleep(5)目的是使进程等待线程的结束.推荐使用pthread_join()函数来实现
//进程在5s后结束,因此也必须保证线程在5s后也结束
err=pthread_join(thread_1,&tret1); //tret1保存着线程1结束后的返回值.若不需要线程1的返回值,则err=pthread_join(thread_1,NULL);
//当进程创建线程后,系统会首先运行进程.
//阻塞函数.该函数可以使进程等待线程1运行完后,再运行进程.
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret1);
err=pthread_join(thread_2,&tret2); ] //tret2保存着线程2结束后的返回值
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret2);
err=pthread_join(thread_3,&tret3); //tret3保存着线程3结束后的返回值
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret3);
if(pthread_cancel(thread_1)!=0) //在进程中终止线程1
perror("phread cancel");
return 0;
}
[root@localhost lishuai]# gcc thread.c -o thread -Wall -O2 -g -lpthread
[root@localhost lishuai]# ./thread 三、线程同步
线程的同步, 发生在多个线程共享相同内存的时候, 这时要保证每个线程在每个时刻看到的共享数据是一致的. 如果每个线程使用的变量都是其他线程不会使用的(read & write), 或者变量是只读的, 就不存在一致性问题. 但如果两个或两个以上的线程可以read/write一个变量时, 就需要对线程进行同步, 以确保它们在访问该变量时, 不会得到无效的值, 同时也可以唯一地修改该变量并使它生效.
在linux下常用的线程同步方法有互斥锁和信号量.
(1)互斥锁
互斥锁有两种状态,分别是lock和unlock,它确保同一时刻只有一个线程可以访问数据,它用一种使用加锁方法来控制对共享资源的存取.同一时刻只能有一个线程掌握某个互斥资源的锁,该线程能对共享资源进行操作.若其它线程想上锁一个已经上了锁的互斥资源,则该线程就会挂起,直到上锁的线程是释放那个互斥锁为止.
互斥锁分静态分配的互斥锁和动态分配的互斥锁.
当使用宏定义PTHREAD_MUTEX_INITIALIZER时,采用的是静态分配的互斥锁.
当使用用户自定义的pthread_mutex_t类型的变量时,采用的是动态分配的互斥锁.
1、互斥锁的初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_init *mutex,const pthread_mutexattr)
函数功能:初始化一个互斥锁
参数1:互斥锁变量,且该变量是pthread_mutex_init数据类型的.
参数2:互斥锁变量的属性.若以默认属性初始化互斥锁,则该参数为NULL.
2、清除互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
函数功能:清除用户自定义的互斥锁
参数:互斥锁变量
3、对互斥锁进行加锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
函数功能:对一个已经存在的互斥锁进行加锁.互斥锁不加锁并不能阻止其它线程的访问.互斥锁被加锁后,其它线程将会阻塞至该函数处.
参数:互斥锁变量
4、对互斥锁进行尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex)
函数功能:对互斥锁进行尝试加锁
参数:互斥锁变量
Attention!!!
该函数与pthread_mutex_lock()的区别在于后者对互斥锁进行尝试加锁,若此时其它线程已经加锁,则该线程将无法加锁,但该线程并不会阻塞在该函数处.
5、对互斥锁进行解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
函数功能:对互斥锁进行解锁
参数:互斥锁变量
例程:模拟智能打印机
若一台打印机同一时刻有N(N>1)个人同时需要打印,系统会创建N个线程分别处理,且CPU会给每个线程分配一定的时间,这样,CPU会分配一点时间给线程1,再分配一点时间给线程2...,最终结果是每个人打印一点,这样的结果并不是我们想要的.而采用互斥锁可以避免,因为若第1个线程率先被执行,该线程会锁住打印机,系统会阻止其它线程访问打印机,且阻塞在函数pthread_mutex_lock()处,直到第一个线程执行完毕,解锁后,第二个线程才可以被执行... mutex_printer.c
pthread_mutex_t mutex1;
void PRINT(char *str) //哪个线程先执行,则哪个线程先加锁
{
pthread_mutex_lock(&mutex1); //线程2会阻塞在此,等待线程1的解锁
char * temp=str;
while(*temp!='\0')
{
printf("%c",*temp);
fflush(stdout);
sleep(1);
temp++;
}
pthread_mutex_unlock(&mutex1);
}
//若线程1率先被执行,则该线程会锁住打印机,阻止线程2的访问,且线程2会阻塞在函数pthread_mutex_lock()处.
//直到线程1执行完毕,打开互斥锁,这样线程2才可以访问打印机. void *thr_fun1(void * arg)
{
PRINT("hello world\n");
return NULL;
}
void *thr_fun2(void * arg)
{
PRINT("i love China!!!\n");
return NULL;
} int main(int argc, char *rgv[])
{
int err;
pthread_t tid1,tid2;
err=pthread_mutex_init(&mutex1,NULL);
if(err!=0)
perror("pthread_mutex_init");
err=pthread_create(&tid1,NULL,thr_fun1,NULL);
if(err!=0)
perror("pthread_create tid1");
err=pthread_create(&tid2,NULL,thr_fun2,NULL);
if(err!=0)
perror("pthread_create tid2");
err=pthread_join(tid1,NULL);
if(err!=0)
perror("pthread_join tid1");
err=pthread_join(tid2,NULL);
if(err!=0)
perror("pthread_join tid2 ");
err=pthread_mutex_destroy(&mutex1); //清除用户自定义的互斥锁
if(err!=0)
perror("pthread_mutex_destroy ");
return 0;
} [root@localhost lishuai]# gcc mutex_printer.c -o mutex_printer -Wall -O2 -g -lpthread
(2)信号量
信号量详见博客"信号量".
(1)在Linux中,进程(process)是操作系统分配资源的最小单位,线程(thread)是操作系统调度的最小单位.
(2)使用多线程的理由:
<1>与进程相比,线程是一种非常节俭的多任务操作方式.
在Linux中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种昂贵的多任务工作方式.而一个线程与它的父线程共享这些资源,这样就大大节省了系统资源.
<2>线程间方便的通信机制.对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的某种机制进行,这种方式不仅费时,而且很不方便.线程则不然,由于同一进程下的线程之间共享父进程的数据段、代码段和堆栈段等,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便.比如在一个线程中修改的变量值,可以在另一个线程中直接应用,不必再占用新的资源.比起进程间通信的那些机制(管道、FIFO、消息队列等)显得非常方便.
<3>多线程程序作为一种多任务、并发的工作方式,使多CPU系统更加有效,也可以改善程序结构.比如操作系统会保证线程数不大于CPU数目时,不同的线程运行不同的CPU上.又如一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样程序会利于理解和修改.
(3)Linux系统下的多线程遵循POSIX线程接口,称作pthread.这些接口被封装在pthread库内(不在标准c库内).因此,编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a
(4)所有进程至少有一个线程,多线程的意义在于一个进程中有多个执行部分同时执行.每个线程处理各自独立的任务.
(5)多任务程序设计:
一个进程可能要处理不同的应用,要求处理多种任务.如果开发不同的进程来处理,系统开销很大,数据共享、程序结构都不方便,这时可使用多线程编程方法.
(6)并发程序设计:
一个任务可能分不同的步骤,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥、同步并发完成.这样可以为不同的任务步骤建立线程.
(7)就像每个进程都有一个ID一样,每个线程也有一个ID.进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效. 二、多线程程序设计
线程的相关函数都被封装在libthread.a库中,头文件为pthread.h.由于pthread的库不是Linux系统的库,所以在进行编译时需要加上"-lpthread",即#gcc filename -lpthread.如果编译时不使用该库,则Linux提示:"undefined reference to 'pthread_create'",即找不到该函数.
(1)线程的创建
#include <pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void),void *arg)
函数功能:创建进程
参数tidp:线程ID
参数attr:线程属性,通常设置为空(NULL)
参数start_rtn:线程要执行的函数
参数arg:函数start_rtn的参数
返回值:
Attention!!!
1.在线程创建后,就开始运行相关的线程函数,该哈数运行完后,该线程也就结束了.
2.编译程序时,对于标准C库不需要指定库名,而对于数据库、线程库都需要指定库名.
3.创建线程时,线程处理函数的参数必须是void *类型,返回值也必须是*void型.
4.这两个线程谁先执行并不一定,由操作系统的算法决定.一般来说,先创建的线程先被执行.
5.进程的ID可由函数getpid()返回值得到,线程的ID可由函数pthread_self()返回值得到.若用户需要打印某线程的ID,需要在线程处理函数中作如下处理:printf("the thread ID is %u\n",(unsigned int)pthread_self());
6.关于线程处理函数的返回值.
若需要该返回值,假定返回值为ret,则写成: return ((void *)set);若不需要返回值,则直接写成: return NULL;
(2)线程的终止
若进程中任何一个线程中调用exit或_exit,则整个进程(包括进程所属线程)都会终止.线程有3种退出方式:
1、线程从启动例程中正常返回.比如线程函数执行到return NULL.
2、线程可以被另一个进程终止.
3、线程自己调用pthread_exit()函数.
#include <pthread.h>
void pthread_exit(void *rval_ptr)
函数功能:终止调用线程
参数rval_ptr:线程退出返回值.进程中的其它线程可以通过pthread_join()访问到该指针.
例程:
void *create(void *arg)
{
printf("new thread is created ... \n");
//return (void *)8;
//pthread_exit((void *)8);
//exit(0);
} int main(int argc,char *argv[])
{
pthread_t tid;
int error;
void *temp; error = pthread_create(&tid, NULL, create, NULL);
printf("main thread!\n"); if( error )
{
printf("thread is not created ... \n");
return -1;
}
error = pthread_join(tid, &temp); if( error )
{
printf("thread is not exit ... \n");
return -2;
}
printf("thread is exit code %d \n", (int )temp);
return 0;
}
执行结果:
第1种情况: thread is exit code 8 //正常退出线程
第2种情况: thread is exit code 8 //正常退出线程
第3种情况: 无法打印出"thread is exit code %d \n",这是由于exit(0)直接退出了进程.
(3)线程的等待
#include <pthread.h>
int pthread_join(pthread_t tid,void **rval_ptr)
函数功能:阻塞线程调用,直到指定的线程终止.调用该函数的线程将被挂起,将一直等待到所等待线程结束为止.
参数1:等待退出的线程的ID
参数2:线程退出时的返回值的指针.
(4)线程的终止
#include <pthread.h>
void pthread_cancel(pthread_t tid)
函数功能:线程可以通过该函数来请求取消同一进程中的其它线程.该函数会发送终止信号给线程号为tid的线程,然后发送成功并不意味着tid线程就一定能终止.它可以选择忽略或自己控制取消的方式.
返回值:如果成功则返回0,不成功则返回非0.
(5)线程的清理
线程的终止有两种情况:正常终止和非正常终止.
当线程主动调用pthread_exit()或从线程函数中return都将使线程正常退出,这是可预见的退出方式.
当线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而推出,这是不可预见的退出方式.
无论是哪种方式推出,都会存在资源释放的问题.如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑的问题.比如在一个线程中malloc一段内存空间,如果在线程退出时不释放这段内存,就有可能导致内存泄露.由于用户不清楚该线程什么时候被非正常终止(非预知性),也就不知道在线程处理函数的哪里来释放掉这段内存,因此,必须有一种机制来处理该问题.
在Linux中,使用成对出现的pthread_cleanup_push和pthread_cleanup_pop来处理该问题.从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push所指定的清理函数.
在Linux中,线程可以建立多个清理处理程序.处理程序在栈中,即它们的执行顺序与它们压入栈内时的顺序相反.
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *),void *arg)
函数功能:将清除函数压入清除栈
参数1:指向清除函数的指针
参数2:清除函数的参数
#include <pthread.h>
void pthread_cleanup_pop(int execute)
函数功能:将清除函数弹出清除栈
参数:execute执行到pthread_cleanup_pop时,是否在弹出清除函数的同时执行该函数,非0:执行;0:不执行.
Attention!!!
1.清理函数是为了给子线程处理函数擦屁股.
2.包括正常退出和非正常退出都可以使用清理函数,具体清理函数是否执行是由函数pthread_cleanup_pop()的参数决定的.比如在子线程中:
pthread_cleanup_push(cleanup1,NULL);
pthread_cleanup_push(cleanup2,NULL);
此时,这样清理函数cleanup2在栈顶,cleanup1在栈底.若只使用清理函数cleanup1,可以这样来:
pthread_cleanup_pop(0); //cleanup2被弹出,却不执行
pthread_cleanup_pop(1); //cleanup1被弹出,执行
例程:
char *p=NULL;
void cleanup(void *arg)
{
printf("clean up\n");
free(p);
} void *thread1_fun(void *arg)
{
pthread_cleanup_push(cleanup,NULL);
printf("this is new thread\n");
p=(char *)malloc(100);
sleep(5);
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
} int main(int argc,char *agrv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1_fun,NULL);
sleep(1);
pthread_cancel(tid);
while(1);
return 0;
}
执行结果:
this is new thread
clean up
程序分析:
在main()函数中,在主线程内创建了一子线程.但CPU会先执行主线程,该例程中创建了子线程后主线程sleep(1),因此CPU会首先执行子线程.当子线程执行到sleep(5)时,子线程让出CPU的占有权,CPU去执行了主线程.主线程终止了子线程,由于在子线程处理函数中pthread_cleanup_pop的参数为非0,因此非正常退出前要执行清理函数cleanup().
(6)综合例程
thread.c
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <pthread.h>
struct menber
{
int a;
char *s;
};
void *thread1_fun(void *arg)
{
int *j;
j=(int *)arg; //必须进行强制类型转换
while(1)
{
printf("this is thread_1\n");
sleep(1);
}
printf("arg=%d\n",*j);
return NULL;
}
void *thread2_fun(void *arg)
{
char *temp;
temp=(char *)arg; //必须进行强制类型转换
while(1)
{
printf("this is thread_2\n");
sleep(1);
}
printf("temp=%s\n",temp);
return NULL;
}
void *thread3_fun(void *arg)
{
struct menber *p;
p=(struct menber *)arg;
printf("menber->a = %d \n",p->a);
printf("menber->s = %s \n",p->s);
return (void *)0;
} int main(int argc,char *argv[])
{
int i=100;
int err;
pthread_t thread_1,thread_2,thread_3;
void *tret1,*tret2,*tret3;
char *str="hello world i love china!\n";
struct menber b;
b.a = 4;
b.s = "hello embeded"; err = pthread_create(&thread_1,NULL,thread1_fun,&i); //创建线程,并指定到函数thread1_fun()来处理
if(err!=0)
printf("cannot create thread 1:%s\n",strerror(err));
err = pthread_create(&thread_2,NULL,thread2_fun,str);
if(err!=0)
printf("cannot create thread 2:%s\n",strerror(err));
err = pthread_create(&thread_3,NULL,thread3_fun,&b);
if(err!=0)
printf("cannot create thread 3:%s\n",strerror(err));
//sleep(5); //线程必须依赖于进程,因此sleep(5)目的是使进程等待线程的结束.推荐使用pthread_join()函数来实现
//进程在5s后结束,因此也必须保证线程在5s后也结束
err=pthread_join(thread_1,&tret1); //tret1保存着线程1结束后的返回值.若不需要线程1的返回值,则err=pthread_join(thread_1,NULL);
//当进程创建线程后,系统会首先运行进程.
//阻塞函数.该函数可以使进程等待线程1运行完后,再运行进程.
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret1);
err=pthread_join(thread_2,&tret2); ] //tret2保存着线程2结束后的返回值
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret2);
err=pthread_join(thread_3,&tret3); //tret3保存着线程3结束后的返回值
if(err!=0)
printf("cannot join with thread 1:%s\n",strerror(err));
printf("thread 1 exit code %d\n",(int)tret3);
if(pthread_cancel(thread_1)!=0) //在进程中终止线程1
perror("phread cancel");
return 0;
}
[root@localhost lishuai]# gcc thread.c -o thread -Wall -O2 -g -lpthread
[root@localhost lishuai]# ./thread 三、线程同步
线程的同步, 发生在多个线程共享相同内存的时候, 这时要保证每个线程在每个时刻看到的共享数据是一致的. 如果每个线程使用的变量都是其他线程不会使用的(read & write), 或者变量是只读的, 就不存在一致性问题. 但如果两个或两个以上的线程可以read/write一个变量时, 就需要对线程进行同步, 以确保它们在访问该变量时, 不会得到无效的值, 同时也可以唯一地修改该变量并使它生效.
在linux下常用的线程同步方法有互斥锁和信号量.
(1)互斥锁
互斥锁有两种状态,分别是lock和unlock,它确保同一时刻只有一个线程可以访问数据,它用一种使用加锁方法来控制对共享资源的存取.同一时刻只能有一个线程掌握某个互斥资源的锁,该线程能对共享资源进行操作.若其它线程想上锁一个已经上了锁的互斥资源,则该线程就会挂起,直到上锁的线程是释放那个互斥锁为止.
互斥锁分静态分配的互斥锁和动态分配的互斥锁.
当使用宏定义PTHREAD_MUTEX_INITIALIZER时,采用的是静态分配的互斥锁.
当使用用户自定义的pthread_mutex_t类型的变量时,采用的是动态分配的互斥锁.
1、互斥锁的初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_init *mutex,const pthread_mutexattr)
函数功能:初始化一个互斥锁
参数1:互斥锁变量,且该变量是pthread_mutex_init数据类型的.
参数2:互斥锁变量的属性.若以默认属性初始化互斥锁,则该参数为NULL.
2、清除互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
函数功能:清除用户自定义的互斥锁
参数:互斥锁变量
3、对互斥锁进行加锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
函数功能:对一个已经存在的互斥锁进行加锁.互斥锁不加锁并不能阻止其它线程的访问.互斥锁被加锁后,其它线程将会阻塞至该函数处.
参数:互斥锁变量
4、对互斥锁进行尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex)
函数功能:对互斥锁进行尝试加锁
参数:互斥锁变量
Attention!!!
该函数与pthread_mutex_lock()的区别在于后者对互斥锁进行尝试加锁,若此时其它线程已经加锁,则该线程将无法加锁,但该线程并不会阻塞在该函数处.
5、对互斥锁进行解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
函数功能:对互斥锁进行解锁
参数:互斥锁变量
例程:模拟智能打印机
若一台打印机同一时刻有N(N>1)个人同时需要打印,系统会创建N个线程分别处理,且CPU会给每个线程分配一定的时间,这样,CPU会分配一点时间给线程1,再分配一点时间给线程2...,最终结果是每个人打印一点,这样的结果并不是我们想要的.而采用互斥锁可以避免,因为若第1个线程率先被执行,该线程会锁住打印机,系统会阻止其它线程访问打印机,且阻塞在函数pthread_mutex_lock()处,直到第一个线程执行完毕,解锁后,第二个线程才可以被执行... mutex_printer.c
pthread_mutex_t mutex1;
void PRINT(char *str) //哪个线程先执行,则哪个线程先加锁
{
pthread_mutex_lock(&mutex1); //线程2会阻塞在此,等待线程1的解锁
char * temp=str;
while(*temp!='\0')
{
printf("%c",*temp);
fflush(stdout);
sleep(1);
temp++;
}
pthread_mutex_unlock(&mutex1);
}
//若线程1率先被执行,则该线程会锁住打印机,阻止线程2的访问,且线程2会阻塞在函数pthread_mutex_lock()处.
//直到线程1执行完毕,打开互斥锁,这样线程2才可以访问打印机. void *thr_fun1(void * arg)
{
PRINT("hello world\n");
return NULL;
}
void *thr_fun2(void * arg)
{
PRINT("i love China!!!\n");
return NULL;
} int main(int argc, char *rgv[])
{
int err;
pthread_t tid1,tid2;
err=pthread_mutex_init(&mutex1,NULL);
if(err!=0)
perror("pthread_mutex_init");
err=pthread_create(&tid1,NULL,thr_fun1,NULL);
if(err!=0)
perror("pthread_create tid1");
err=pthread_create(&tid2,NULL,thr_fun2,NULL);
if(err!=0)
perror("pthread_create tid2");
err=pthread_join(tid1,NULL);
if(err!=0)
perror("pthread_join tid1");
err=pthread_join(tid2,NULL);
if(err!=0)
perror("pthread_join tid2 ");
err=pthread_mutex_destroy(&mutex1); //清除用户自定义的互斥锁
if(err!=0)
perror("pthread_mutex_destroy ");
return 0;
} [root@localhost lishuai]# gcc mutex_printer.c -o mutex_printer -Wall -O2 -g -lpthread
(2)信号量
信号量详见博客"信号量".
相关阅读 更多 +