四、私有数据
- 在多线程环境下,进程内的所有线程共享进程的数据空间,因此全局变量为所有线程共有。在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在某个线程内部有效。在线程内部,线程私有数据可以被各个函数访问,但他对其他线程是屏蔽的。线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。操作线程私有数据的函数主要有4个,分别是创建,设置,读取,删除:
#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);
- pthread_key_create:从Linuxd的TSD池中分配一项,将其值赋给key供以后访问使用,他的第一个参数key为指向键值的指针,第二个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区。key一旦被创建,所有线程都可以访问他,但各线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量,一键多值。靠的是一个关键数据结构数组,即TSD池,结构如下:
static struct pthread_key_struct pthread_keys [PTHREAD_KEYS_MAX] = { { 0, NULL } };
创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。
- pthread_setspecific:该函数将pointer的值(不是内容)与key相关联。用pthread_setspecific为一个键指定新的线程数据时,线程必须先释放原有的线程数据用以回收空间。
- pthread_getspecific:通过该函数得到与key相关联的数据。
- pthread_key_delete:该函数用来删除一个键,删除后,键所占用的内存将被释放。需要注意的是,键占用的内存被释放,与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。
创建使用线程私有数据如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_key_t key;
void * thread2(void*arg)
{
int tsd = 5;
printf("thread %ld is running\n",pthread_self());
pthread_setspecific(key, (void *) tsd);
printf("thread %ld returns %p\n",pthread_self(),pthread_getspecific(key));
}
void * thread1(void *arg)
{
int tsd = 0;
pthread_t thid2;
printf("thread %ld is running\n",pthread_self());
pthread_setspecific(key, (void *) tsd);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(5);
printf("thread %ld returns %p\n",pthread_self(),pthread_getspecific(key));
}
int main()
{
pthread_t thid1;
printf("main thread begins running\n");
pthread_key_create(&key,NULL);
pthread_create(&thid1,NULL,thread1,NULL);
sleep(3);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
运行结果如下:
main thread begins running
thread 140601797752576 is running
thread 140601789359872 is running
thread 140601789359872 returns 0x5
main thread exit
五、线程同步
-
互斥锁
通过锁机制来实现线程间的同步。在同一时刻只允许一个线程执行一个关键部分代码
函 数 | 功 能 |
pthread_mutex_init | 初始化一个互斥锁 |
pthread_mutex_destroy | 注销一个互斥锁 |
pthread_mutex_lock | 加锁,如果失败,阻塞等待 |
pthread_mutex_unlock | 解 锁 |
pthread_mutex_trylock | 测试加锁,如果失败则立即返回iEBUSY |
使用互斥锁前必须先进性初始化操作。初始化有两种方式,一种是静态赋值法,将宏结构常量PTHREAD_MUTEX_INITIALIZER赋给互斥锁,操作语句如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
另外一种方式通过pthread_mutex_init函数初始化互斥锁,该函数的原型如下:
int pthread_mutex_init (pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
函数中的参数mutexattr表示互斥锁的属性,如果为NULL则使用默认属性。互斥锁的属性及意义:
属 性 值 | |
PTHREAD_MUTEX_TIMED_NP | 普通锁:当一个线程加锁后,其余请求的线程形成等待队列,解锁后按优先级获得锁 |
PTHREAD_MUTEX_RECURSIVE_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,而不是阻塞等待。
加锁时,不论那种类型的锁,都不可能被两个不同的线程同时得到,其中一个必须等待解锁。在同一进程中的线程,如果加锁后没有解锁,则其他线程将无法获得该锁。
函数pthread_mutex_unlock用来解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex)
解锁时要满足两个条件:一是互斥锁必须处于加锁状态,二是调用本函数的线程必须是给互斥锁加锁的线程。解锁后如果有其他线程在等待互斥锁,等待队列中的第一个线程将获得互斥锁。
当一个互斥锁使用完毕后,必须进行清除。清除互斥锁使用函数pthread_mutex_destroy,该函数原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
清除一个互斥锁意味着释放他所占用的资源。清除锁时要求当前处于开放状态,若锁处于锁定状态,函数返回EBUSY,该函数成功执行时返回0,由于在Linux中,互斥锁并不占用内存,因此该函数除了解除到互斥锁的状态以外没有其他操作。
-
条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制。条件变量宏观上类似if语句,符合条件就能执行某段程序,否则只能等待条件成立。使用条件变量主要包括两个动作:一个等待使用资源的线程等待“条件变量被设置为真”;另一个线程在使用完资源后“设置条件为真”。这样就可以保证线程间的同步了。这样就存在一个关键问题,就是要保证条件变量能被正确的修改,条件变量要受到特殊的保护,实际使用中互斥锁扮演着这样一个保护者的角色。Linux提供了一系列对条件变量操作的函数:
函 数 | 功 能 |
pthread_cond_init | 初始化条件变量 |
pthread_cond_wait | 基于条件变量阻塞,无条件等待 |
pthread_cond_timedwait | 阻塞直到指定事件发生,计时等待 |
pthread_cond_signal | 解除特定线程的阻塞,存在多个等待线程时按入队顺序激活其中一个 |
pthread_cond_broadcast | 解除所有线程的阻塞 |
pthread_cond_destroy | 清除条件变量 |
与互斥锁一样,条件变量的初始化也有两种方式,一种时静态赋值法,将宏结构常量PTHREAD_COND_INITIALIZER赋予互斥锁,操作语句如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
另一种方式是用函数pthread_cond_init
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
其中,cond_attr参数是条件变量的属性,由于其并没有得到实现,所以他的值通常时NULL。
等待条件成立有两个函数:pthread_cond_wait和pthread_cond_timdwait。
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timdwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime);
pthread_cond_wait函数释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,那么线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获得一个信号,使等待条件变量的线程退出阻塞状态。
pthread_cond_timdwait函数和上面那个用法类似,差别在于该函数将阻塞一直到条件变量获得信号或者经过由abstime指定的时间,也就是说,如果在给定时刻前条件没有满足。则返回ETIMEOUT,结束等待。
线程被条件变量阻塞后,可通过函数pthread_cond_signal和pthread_cond_broadcast激活。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal()激活一个等待条件成立的线程,存在多个等待线程时,按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
当一个条件变量不再使用时,需要将其清除:
int pthread_cond_destroy(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("thread1 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(50);
pthread_exit(0);
}
运行结果片段如下:
condition variable study!
thread1 is running
thread1 applied the condition
thread2 is running
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