一、线程的本质
Linux线程又称轻量进程(LWP),也就说线程本质是用进程之间共享用户空间模拟实现的。
二、线程模型的引入
线程模型引入是为了数据共享,为什么又引入线程私有数据?有时候想让基于进程的接口适应多线程环境,这时候就需要为每个线程维护一份私有数据了,最典型的就是errno了。
在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID作为数组的索引来实现访问。
1. 系统生成的线程ID不能保证是一个小而连续的整数
2. 用数组实现的时候容易出现越界读写的情况
鉴于这两个问题,我们可以借助线程的私有数据(TSD)来解决这个问题。
三、线程特定数据
线程私有数据(Thread Specific Data),是存储和查询与某个线程相关的数据的一种机制。把这种数据称为线程私有数据或线程特定数据的原因是,希望每个线程可以独立地访问数据副本,从而不需要考虑多线程同步问题。
提示:进程中的所有线程都可以访问进程的整个地址空间。除了使用寄存器以外(一个线程真正拥有的唯一私有存储是处理器的寄存器),线程没有办法阻止其他线程访问它的数据,线程私有数据也不例外。虽然底层的实现部分并不能阻止这种访问能力,但管理线程私有数据的函数可以提高线程间的数据独立性。
四、关键函数说明
int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));
返回值:若成功则返回0,否则返回错误编号
功能:创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。
说明:创建一个线程私有数据键,必须保证对于每个
Pthread_key_t
变量仅仅被调用一次,因为如果一个键被创建两次,其实是在创建两个不同的键。第二个键将覆盖第一个键,第一个键以及任何线程可能与其关联的线程私有数据值将丢失。解决这种竞争的办法是使用pthread_once
。pthread_once_t initflag = PTHREAD_ONCE_INIT
;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
返回值:若成功则返回0,否则返回错误编号
说明:initflag必须是一个非本地变量(即全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。当线程调用
pthread_exit
或者线程执行返回,正常退出时,如果私有数据不为空且注册了析构函数,析构函数就会被调用,但如果线程调用了exit
、_exit
、_Exit
、abort
或出现其他非正常的退出时就不会调用析构函数注1 。- 线程可以为线程私有数据分配多个键注2,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。
- 线程退出时,线程私有数据的析构函数将按照操作系统实现中定义的顺序被调用。析构函数可能会调用另一个函数,该函数可能会创建新的线程私有数据而且把这个数据与当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否还有非null的线程私有数据值与键关联,如果有的话,再次调用析构函数。这个过程会一直重复直到线程所有的键都为null值线程私有数据,或者已经做了最大次数的尝试注3。
- 创建新键时,每个线程的数据地址设为NULL。
int pthread_key_delete(pthread_key_t *keyp);
返回值:若成功则返回0,否则返回错误编号
功能:取消键与线程私有数据值之间的关联关系。
说明:- 调用
pthread_delete
不会激活与键关联的析构函数,容易造成内存泄露。要释放任何与键对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。当删除线程私有数据键的时候,不会影响任何线程对该键设置的线程私有数据值,甚至不影响调用线程当前键值。建议最后才删除线程私有数据键,尤其当一些线程仍然持有该键的值时,就更不该释放该键。使用已经
- 调用