说明:只供学习交流,装载请注明出处
五,线程清除
线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit或线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是在其他线程的干预下,或者由于自身出错(比如访问非法地址)而退出,退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑解决的问题。
为了解决上述问题,在线程API中提供了pthread_cleanup_push和pthread_cleanup_pop()函数用于自动释放资源。使用时,只需将待处理的线程代码放在pthread_cleanup_push函数和pthread_cleanup_pop函数之间,当程序运行在它们之间发生终止动作(包括调用pthread_exit和非正常退出,但不包括return)时,都将执行pthread_cleanup_push()所指定的清理函数进行线程清除工作。
pthread_cleanup_push函数
头文件 | #include <pthread.h> |
函数原型 | void pthread_cleanup_push(void (*routine)(void *), void *arg); |
返回值 | 无 |
函数功能:将清除函数压入清除栈,标志一个线程清理段的开始。
参数说明:routine为线程清除函数,arg为线程清除函数的传入参数。
pthread_cleanup_pop函数
头文件 | #include <pthread.h> |
函数原型 | void pthread_cleanup_pop(int execute); |
返回值 | 无 |
函数功能:将清除函数弹出清除栈。
参数说明:程序执行到pthread_cleanup_pop()时在弹出清理函数的同时若execute为非0:执行清理函数, execute为0:不执行清理函数。
实例:
#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((void *)1); //线程运行到这里会结束,后面的代码不会被运行。由于是用return退出,所以不会执行线程清理函数。
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
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((void *)2);//线程运行到这里会结束,后面的代码不会被运行。由于是用pthread_exit退出,所以会执行线程清理函数。执行的顺序是先压进栈的后执行,即后进先出。
}
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的返回值*/
err=pthread_join(tid1,&tret);
if(err!=0)
{
printf("error .... \n");
return -1;
}
printf("thread 1 exit code %d \n",(int)tret);
/*阻塞等待线程2退出,并获取线程2的返回值*/
err=pthread_join(tid2,&tret);
if(err!=0)
{
printf("error .... ");
return -1;
}
printf("thread 2 exit code %d \n",(int)tret);
return 1;
}
运行结果:
[root@localhost test]# ./pthread_cleanup
thread 1 start...
thread 1 push complete...
thread 2 start...
thread 2 push complete
cleanup: thread 2 second hander
cleanup: thread 2 first hander
thread 1 exit code 1
thread 2 exit code 2
[root@localhost test]#
六,线程同步
多进程对共享资源进行访问的时候,必须保证某个时刻只有一个进程访问资源,这个问题在多线程情况下,同样是存在的。为此,Linux系统提供了互斥锁来保证某个时刻只有一个线程使用资源。
互斥锁提供了在多线程情况下相互排斥的方法。举例来说,如有两个线程,线程A和线程B,当线程A意图锁定一个互斥锁时,线程B在线程A操作之前就已经完成了锁定,这时,线程A将进入睡眠状态,直到线程B释放该锁才能进行锁定。通过对互斥锁进行锁定操作,就能实现进程对共享资源的依次访问。
互斥锁的锁定和解锁是通过pthread_mutex_lock函数和pthread_mutex_unlock函数来实现的。互斥锁一般用来保护数据结构。通过线程对互斥锁的锁定和解锁,能够实现某一时刻只有一个线程访问该数据结构。
互斥锁操作主要有3种行为,分别是加锁、解锁和测试加锁。当对互斥锁进行了加锁操作,在没有进行解锁之前,任何线程都没有办法获得互斥锁。
要创建互斥锁,可以通过两种途径来实现:一种是通过POSIX标准中定义的宏来实现对互斥锁的初始化,具体实现如下:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
另一种方法是调用pthread_mutex_init函数来实现互斥锁的初始化,该函数的具体信息如下表:
pthread_mutex_init函数
头文件 | #include <pthread.h> | ||
函数原型 | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t attr); | ||
返回值 | 成功 | 失败 | 是否设置errno |
0 | 非0值 | 是 |
说明:pthread_mutex_init函数将根据attr中给出的属性初始化互斥锁。如果attr参数为NULL,表示使用默认的属性。pthread_mutex_init函数调用成功后,互斥锁将完成初始化,并处于解锁状态。
对一个已经初始化的互斥锁再次初始化会产生不可预测的后果。
使用PTHREAD_MUTEX_INITIALIZER宏来初始化互斥锁,其效果等同于调用pthread_mutex_init函数时将attr参数指定为NULL。
错误信息:
EBUSY:试图初始化已经初始化的互斥锁。
EINVAL:参数attr中给出的参数非法。
举例:
…
pthread_mutex_t my_lock;
…
pthread_mutex_init(&my_lock, NULL);
互斥锁的加锁:
pthread_mutex_lock函数用于对互斥锁进行加锁操作,其具体信息如下表:
pthread_mutex_lock函数
头文件 | #include <pthread.h> | ||
函数原型 | int pthread_mutex_lock(pthread_mutex_t *mutex); | ||
返回值 | 成功 | 失败 | 是否设置errno |
0 | 非0值 | 是 |
说明:pthread_mutex_lock函数使用较为简单,参数mutex为要操作的互斥锁。
如果mutex的锁类型为PTHREAD_MUTEX_NORMAL,函数将不提供死锁的检测,因此,加锁操作有可能导致出现死锁。如果线程试图对一个没有加锁的互斥锁进行解锁操作,将导致不可预测的行为。
如果mutex的锁类型为PTHREAD_MUTEX_ERRORCHECK,即检错锁,该锁将提供错误检测能,当线程试图对一个已经加锁的互斥锁进行加锁操作时,会得到错误信息。
如果mutex的锁类型为PTHREAD_MUTEX_RECURSIVE,即嵌套锁,该锁充许同一个线程对锁进行多次的获取,并可以进行多次unlock解锁操作,当不同线程对该锁进行操作时,则在加锁线程解锁时重新竞争。进行一次加锁操作后,锁计数器将加1;当进行一次解锁后,计数器减1,直到锁计数器为0,其他线程才可以获得该互斥锁。
如果mutex的锁类型为PTHREAD_MUTEX_DEFAULT,即为普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁,这种策略保证了资源分配的公平性。
错误信息:
EINVAL:参数mutex未指向初始化的互斥锁。
EAGAIN:未获得互斥锁。
EDEADLK:当前线程已经拥有互斥锁。
pthread_mutex_trylock函数与pthread_mutex_lock函数功能类似,同样用于实现对互斥锁的加锁操作。
pthread_mutex_trylock函数
头文件 | #include <pthread.h> | ||
函数原型 | int pthread_mutex_trylock(pthread_mutex_t *mutex); | ||
返回值 | 成功 | 失败 | 是否设置errno |
0 | 非0值 | 是 |
说明:pthread_mutex_trylock函数用来锁住mutex所指向的互斥体,但不阻塞。如果该互斥体已经被上锁,该调用不会阻塞等待,而会返回一个错误代码。如果锁类型是嵌套锁(PTHREAD_MUTEX_RECURSIVE),且调用进程对互斥锁已经加锁,调用该函数将使得锁计数器加1。
错误信息:
EINVAL:参数mutex为指向初始化的互斥锁。
EAGAIN:为获得互斥锁。
EDEADLK:当前线程已经拥有互斥锁。
EBUSY:由于互斥锁已经加锁,无法获得该互斥锁。
互斥锁的解锁:
Pthread_mutex_unlock函数用于对互斥锁进行解锁操作,该函数的具体信息如下表:
头文件 | #include <pthread.h> | ||
函数原型 | int pthread_mutex_unlock(pthread_mutex_t *mutex); | ||
返回值 | 成功 | 失败 | 是否设置errno |
0 | 非0值 | 是 |
说明:pthread_mutex_unlock函数将释放参数mutex中指向的互斥锁,释放方式依赖于互斥锁的锁类型。
错误信息:
EINVAL:参数mutex未指向初始化的互斥锁。
EAGAIN:未获得互斥锁。
EPERM:当前线程不拥有互斥锁。
实例:
程序给出了使用互斥锁实现线程同步的实例。在程序中,两个线程对共同的全局变量g_var进行加1操作。为了保证结果的正确性,必须使得在某个时刻只有一个线程对该全局变量进行操作,在程序中通过互斥锁实现了这一点。具体代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int g_var;
pthread_mutex_t locker = PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void)
{
int i,j;
for (i = 0; i < 4; i++)
{
pthread_mutex_lock(&locker);
j = g_var;
j = j+1;
printf("---in thread ...increase one---\n");
fflush(stdout);
sleep(1);
g_var = j;
pthread_mutex_unlock(&locker);
}
return (NULL);
}
int main(void)
{
pthread_t thread_id;
int i;
if (pthread_create(&thread_id, NULL, thread_function, NULL))
{
perror("Cannot create new thread");
return (1);
}
for (i = 0; i < 4; i++)
{
pthread_mutex_lock(&locker);
g_var = g_var + 1;
pthread_mutex_unlock(&locker);
printf("...in main thread...increase one...\n");
fflush(stdout);
sleep(1);
}
if (pthread_join(thread_id, NULL))
{
perror("Cannot join thread.");
return (1);
}
printf("g_var: %d\n", g_var);
return (0);
}
运行结果:
[root@localhost test]# ./mutex
---in thread ...increase one---
---in thread ...increase one---
---in thread ...increase one---
---in thread ...increase one---
...in main thread...increase one...
...in main thread...increase one...
...in main thread...increase one...
...in main thread...increase one...
g_var: 8
[root@localhost test]#