线程这一章,着实不好理解,写上一篇博客时,我还没这么想,
今天做了几道关于线程的题,觉得还是不行呀,还是乖乖再来一遍吧。
- 私有数据:
- 线程同步
1、私有数据
为什么要有线程私有数据呢?
我们知道在多线程环境下,进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有,但是在程序设计的时候有时需要保存线程自己的全局变量,例如:变量errno:返回标准的错误码,errno不应该是一个局部变量,因为每个函数几乎都可以访问它,但它又不能作为一个全局变量,否则在一个线程里输出的可能是另一个线程的出错信息,那么,这种问题就可以通过创建线程的私有数据来解决。
线程私有数据:可以被此线程内的各个函数访问,但对其他线程是屏蔽的。
采用一键多值的技术,即一个键对应多个数值。
操作线程私有数据的函数主要有四个:
(1)第一个
int pthread_key_create(pthread_key_t *key,void (*destr_function) (void *));
- 此函数用于创建一个键,参数key:指向键值的指针(这里是从Linux的TSD池中分配一项,将其值赋给key),第二个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区。这里是从Linux的TSD池中分配一项,将其值赋给key,
key一旦被创建,所有的线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,就相当于提供了一个同名而不同值的全局变量,一键多值靠的是一个关键的数据结构数组,即TSD池。
即:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};
pthread_key_struct 结构体定义如下:
struct pthread_key_struct
{
uintptr_t seq;
void (*destr) (void *);
};
创建一个TSD,相当于将结构体数组的某一个元素的seq值设置为为“in_use”,并将其索引返回给*key,然后设置destr_function()为destr()。pthread_key_create创建一个新的线程私有数据key时,系统会搜索其所在进程的key结构数组,找出一个未使用的元素,将其索引赋给*key。
(2)第二个
作用:为一个键设置线程私有数据
int pthread_setspecific(pthread_key_t key, const void *pointer);
该函数将指针pointer的值(不是内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。
(3)第三个
作用:从一个键读取线程私有数据
void * pthread_getspecific(pthread_key_t key);
通过该函数得到与key相关联的数据
(4)第四个
作用:删除一个键
void * pthread_getspecific(pthread_key_t key);
该函数用于删除一个键,删除后,键所占用的内存将被释放,但是与改key相关联的线程数据所占用的内存并不会被释放的,因此线程私有数据的释放必须在释放键之前。
下面为一个创建和使用线程私有数据的程序:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
pthread_key_t key;
void * thread2(void *arg)
{
int tsd = 5;
printf("thread %d is running\n",(int)pthread_self());
pthread_setspecific(key,(void *) &tsd);
printf("thread %d returns %d\n",(int)pthread_self(),*((int *)pthread_getspecific(key)));
}
void * thread1(void *arg)
{
int tsd=0;
pthread_t thid2;
printf("thread %d is running\n",(int)pthread_self());
pthread_setspecific(key,(void *)&tsd);
pthread_create(&thid2,NULL,thread2,NULL); //thread1创建thread2
sleep(5);
printf("thread %d returns %d\n",(int)pthread_self(),*((int *)pthread_getspecific(key)));
}
int main(void)
{
pthread_t thid1;
printf("main thread begins running\n");
pthread_key_create(&key,NULL);
pthread_create(&thid1,NULL,thread1,NULL); //创建thread1
pthread_join(thid1,NULL); //等待线程id为thid1的线程结束
sleep(3);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
运行结果:
main thread begins running
thread 1373751040 is running
thread 1365358336 is running
thread 1365358336 returns 5
thread 1373751040 returns 0
main thread exit
从结果可以看出,两个线程tsd的修改互不干扰。
2、线程同步
为什么要同步?
线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
linux系统提供了多种方式处理线程间的同步问题,其中最常用的有互斥锁,条件变量和异步信号。
(1)互斥锁
互斥锁通过锁机制来实现线程间同步。在同一时刻它通常只允许一个线程执行一个关键部分的代码。
使用互斥锁钱必须先初始化一个锁,这里有两种方式,一种是静态赋值法,将宏结构常量PTHREAD_MUTEX_INITIALIZER赋给互斥锁。另外一种方式是通过pthread_mutex_init函数初始化互斥锁。
操作如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t * mutexattr);
此函数用来初始化一个锁,mutex
:互斥锁地址。类型是 pthread_mutex_t 。
mutexattr
:设置互斥量的属性,通常可采用默认属性,即可将mutexattr
设为 NULL
。
互斥锁的属性如下表:
属性值 | 意义 |
---|---|
PTHREAD_MUTEX_TIMED_NP | 普通锁:当一个线程加锁后,其余请求锁的线程形成等待队列,解锁后按优先级获得锁 |
PTHREAD_MUTEX_RECURSSIVE_NP | 嵌套锁:允许一个线程对同一个锁多次加锁,并通过多次unlock解锁,如果是不同线程请求,则在解锁时重新竞争。 |
PTHREAD_MUTEX_ERRORCHECK_NP | 检错锁:在同一个线程请求同一个锁的情况下,返回EDEADLK,否则执行的动作与类型PTHREAD_MUTEX_TIMED_NP相同 |
PTHREAD_MUTEX_ADAPTIVE_NP | 适应锁:解锁后重新竞争 |
初始化完成以后就是加锁了,加锁有两个函数:
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_trylock(pthread_mutex_t * mutex);
这里要注意它们两个的区别:
用pthread_mutex_lock()加锁时,如果mutex已经锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放。当pthread_mutex_lock函数返回时,说明互斥锁已经被当前线程成功加锁。
pthread_mutex_trylock函数则不同,如果mutex已经被加锁,它将立即返回,返回的错误码为EBUSY,而不是阻塞等待。
注意:加锁时,不论哪种类型的锁,都不可能被两个类型不同的线程同时得到,其中一个必须等待解锁,在同一进程中的线程,如果加锁后没有解锁,则其他线程将无法再获得该锁。
接下来就是解锁啦:
int pthread_mutex_unlock(pthread_mutex_t * mutex);
用此函数解锁时要注意满足两个条件:一是互斥锁必须处于加锁状态,二是调用本函数的线程必须是给互斥锁加锁的线程。
最后就是清除互斥锁的函数:
int pthread_mutex_destory(pthread_mutex_t * mutex);
清除一个互斥锁意味着释放它所占用的资源。清除锁时要求当前处于开放状态,若锁处于锁定状态,函数返回EBUSY,该函数成功执行时返回0,由于在linux中,互斥锁不占用内存,因此此函数只是解除掉斥锁的状态。
(2)条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制。使用条件变量主要包括两个动作:一个等待使用资源的线程等待“条件变量被设置为真”;另一个线程在使用完资源后“设置条件为真”,这样就可以保证线程间的同步了。这里,我们要使条件变量被正确的修改,就需要互斥锁来保护它。
- 条件变量的初始化也是两种方法:一种是静态赋值法,将宏结构常量PTHREAD_COND_INITIALIZER赋予条件变量,另一种是使用函数pthread_cond_init进行初始化。
如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t *cond_attr);
cond_attr:条件变量的属性,他的值通常是NULL。
- 有了条件变量,当然要有等待条件成立的函数,这里有两个函数如下:
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);
第一个函数释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获得一个信号,使得等待条件变量的线程退出阻塞状态。
第二个函数和第一个差不多,差别在于第二个函数将阻塞直到条件变量获得信号或者经过由abstime指定的时间,如果在给定时刻前条件没有满足,则返回ETIMEOUT
- 线程被条件变量阻塞后,可通过以下两个函数激活:
int pthread_cond_signal(pthread_cond_t * cond); //激活一个等待条件成立的函数,存在多个等待线程时,按入队顺序激活其中一个。
int pthread_cond_broadcast(pthread_cond_t * cond); //激活所有等待线程
- 当一个条件变量不再使用时,需要将其清除,就要用到以下函数:
int pthread_cond_destory(pthread_cond_t * cond)
此函数用于清除由cond指向的条件变量,`注意:只有在没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY。
下面为一个应用条件变量的例子,两个线程被启动,并等待同一个条件变量
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void * thread1(void * arg)
{
pthread_cleanup_push(pthread_mutex_unlock,&mutex);
while(1)
{
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
printf("thread applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
pthread_cleanup_pop(0);
}
void * thread2(void * arg)
{
while(1)
{
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
printf("condition variable study!\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,(void*)thread1,NULL);
pthread_create(&tid2,NULL,(void*)thread2,NULL);
do{
pthread_cond_signal(&cond);
}while(1);
sleep(5);
pthread_exit(0);
}
执行结果部分片段如下:
condition variable study!
thread1 is running
thread2 is running
thread1 applied the condition
thread2 applied the condition
thread2 is running
thread2 applied the condition
thread2 is running
thread2 applied the condition
thread2 is running
thread2 applied the condition
thread1 is running
thread1 applied the condition
代码中thread1和thread2通过条件变量同步运行,调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起前解锁。在条件满足离开pthread_cond_wait()之前,mutex将重新加锁,与进入pthread_cond_wait()前的加锁动作对应。
在线程函数thread1和thread2中可以看到条件变量使用时需要配合互斥锁使用,这样做是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.