私有变量
进程中内的所有线程共享进程的数据空间,因此全局变量为所有线程共有,在程序设计时有时需要保存线程自己的全局变量,这种变量仅在某个线程的内部有效。这时就要用到线程的私有数据(TSD),在线程内部,线程的私有数据可以被各个函数访问,但它对其它线程是屏蔽的。
TSD池结构
static struct pthread_key_struct pthread_keys [PTHREAD_KEY_MAX] = ({0,NULL});
操作私有数据的几个函数
#include <pthread.h>
int pthread_key_create(pthread_key_t *key,void (*destr_function)(void *));
int pthread_setspecific(pthread_key_t *key,const void * pointer);
void* pthread_getspecific(pthread_key_t key);
int pthread_key_delete(pthread_key_t key);
1、pthread_key_create:创建一个键
int pthread_key_create(pthread_key_t *key,void(*destr_function) (void*));
//第二个参数是一个析构函数
首先从linux的TSD池(Thread-specific Data)中分配一项,然后将其值赋给key供以后访问使用。
2、pthread_setspecific:为指定键值设置线程私有数据
int pthread_setspecific(pthread_key_t key, const void *pointer);
该函数将指针pointer的值(指针值而非其指向的内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。
3、pthread_getspecific:从指定键读取线程的私有数据
void * pthread_getspecific(pthread_key_t key);
通过该函数得到与key相关联的数据
4、pthread_key_delete:删除一个键
void * pthread_getspecific(pthread_key_t key);
该函数用于删除一个键,功能仅仅是将该key在结构体数组pthread_keys对应的元素设置为“un_use”,与该key相关联的线程数据是不会被释放的,即是仅仅将键删除,因此线程私有数据的释放必须在释放键之前完成。
线程同步
资源的共享性,需要做到线程的同步
线程的同步几种方法:互斥锁、条件变量、异步信号
- 互斥锁
互斥锁通过锁机制来实现线程的同步,在同一时刻只允许一个线程执行关键部分的代码
#include <pthread.h>
pthread_mutex_init(pthread_mutex_t *mutec,const pthread_mutexattr_t mutexattr); //初始化一个互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex); //注销一个互斥锁
pthread_mutex_lock(pthread_mutex_t *mutex); //加锁,如果不成功,阻塞等待
pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
pthread_mutex_trylock(pthread_mutex_t *mutex); //测试加锁,如果不成功立刻返回,错误码为EBUSY
锁的初始化有两种方式,动态初始化和静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(pthread_mutex_t *mutec,const pthread_mutexattr_t mutexattr);
互斥锁的属性
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
上锁和解锁
主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个操作。
加锁时,不论哪种类型的锁,都不可能被两个不同的线程同时得到,其中一个必须等待解锁,在同一进程中的线程,如果加锁后没有解锁,则其他线程将无法再获得该锁。
解锁必须满足两个条件,一是互斥锁必须处于加锁状态,二是调用本函数的线程必须是给互斥锁加锁的线程;解锁后如果有其他线程在等待互斥锁,等待队列中的第一个线程获得互斥锁
锁的释放
锁的释放
调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
互斥锁应用的一个简单例子:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex ;
void *print_msg(void *arg){
int i=0;
pthread_mutex_lock(&mutex);
for(i=0;i<15;i++){
printf("output : %d\n",i);
usleep(100);
}
pthread_mutex_unlock(&mutex);
}
int main(int argc,char** argv){
pthread_t id1;
pthread_t id2;
pthread_mutex_init(&mutex,NULL);
pthread_create(&id1,NULL,print_msg,NULL);
pthread_create(&id2,NULL,print_msg,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
//初始化条件变量
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
//基于条件变量阻塞,无条件等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
//阻塞直到指定时间发生,计时等待
int pthread_cond_signal(pthread_cond_t *cond);
//解除特定线程的阻塞,存在多个等待线程时按入队顺序激活其中一个
int pthread_cond_broadcast(pthread_cond_t *cond);
//解除所有线程的阻塞
int pthread_cond_destory(pthread_cond_t *cond);
//消除条件变量
初始化:
静态初始法
pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER
动态初始化
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
cond_attr通常是NULL,默认情况
等待条件成立
pthread_cond_wait函数释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒
条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞
当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态
pthread_cond_timewait不同的地方在于将阻塞直到条件变量获得信号或者经过由abstime指定的时间,也就是说,在给定时刻前没有满足,则返回ETIMEOUT,结束等待
激活等待条件成立的线程
int pthread_cond_signal(pthread_cond_t *cond);//解除特定线程的阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);//解除所有线程的阻塞
4.清除条件变量
int pthread_cond_destory(pthread_cond_t *cond);
只有在没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY
异步信号
#include <pthread.h>
int pthread_kill(pthread_t threadid,int signo);
//用来向特定的线程发送信号signo
int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask);
//用来设置线程的信号屏蔽码,但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护
int sigwait(const sigset_t *set,int *sig);
//用来阻塞线程,等待set中指定的信号之一到达,并将达到的信号存入*sig中
条件变量和互斥量共用的原因
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
两个线程操作同一临界区时,通过互斥锁保护,若A线程已经加锁,B线程再加锁时候会被阻塞,直到A释放锁,B再获得锁运行,进程B必须不停的主动获得锁、检查条件、释放锁、再获得锁、再检查、再释放,一直到满足运行的条件的时候才可以(而此过程中其他线程一直在等待该线程的结束),这种方式是比较消耗系统的资源的。而条件变量同样是阻塞,还需要通知才能唤醒,线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,该线程就休眠了,应该仍阻塞在这里,等待条件满足后被唤醒,节省了线程不断运行浪费的资源。这个过程一般用while语句实现。当线程B发现被锁定的变量不满足条件时会自动的释放锁并把自身置于等待状态,让出CPU的控制权给其它线程。其它线程 此时就有机会去进行操作,当修改完成后再通知那些由于条件不满足而陷入等待状态的线程。这是一种通知模型的同步方式,大大的节省了CPU的计算资源,减少了线程之间的竞争,而且提高了线程之间的系统工作的效率。这种同步方式就是条件变量。
条件变量常和互斥锁一起使用,条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。