概念
多个函数共享数据时,除了通过函数间的返回值传递,还可以使用全局变量。在多线程的环境下,全局变量会被各个线程共享,如果我们需要只在单个线程所执行的函数间共享数据,例如要记录每个线程所执行过的函数名称,该如何做呢?
可以通过创建 线程私有数据(TSD: thread specific data) 来解决。在线程的内部该变量可以被该线程的所有代码访问,每个线程内的私有数据都是独立的,不可被其他线程访问。TSD是一键多值的模式,既相同的key在不同线程对应不同的值。
API
创建
int pthread_key_create(pthread_key_t *, void (* _Nullable)(void *));
需要传入两个参数,第一个为 pthread_key_t变量作为key值,第二个是析构函数,用于在线程结束时释放私有数据,内部配合free()函数使用。
存取
// 取值
void* _Nullable pthread_getspecific(pthread_key_t);
// 存值
int pthread_setspecific(pthread_key_t , const void * _Nullable);
当线程需要使用私有数据时,调用pthread_setspcific()传入对应的key和值,调用pthread_getspecific()取出void* 变量再转换成对应的类型。
注销
int pthread_key_delete(pthread_key_t);
手动注销key对应的私有数据,该函数并不会调用创建时的析构函数,而是将相关数据设为NULL,并将变量释放。
示例
#import <UIKit/UIKit.h>
#include <stdio.h>
#include <pthread.h>
static pthread_key_t testPthreadKey;
void threadKeyClean(void *ptr)
{
if (ptr != NULL) {
free(ptr);
}
}
void testThread() {
pthread_key_create(&testPthreadKey, threadKeyClean);
}
struct test_data {
int i;
};
int main(int argc, char * argv[]) {
struct test_data *data = (struct test_data *)malloc(sizeof(struct test_data));
data->i = 1;
pthread_key_create(&testPthreadKey, threadKeyClean);
pthread_setspecific(testPthreadKey,&data);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
struct test_data *newData = (struct test_data *)malloc(sizeof(struct test_data));
newData->i = 100;
pthread_setspecific(testPthreadKey,&newData);
NSThread *thread = [NSThread currentThread];
NSLog(@"key=%ld, value=%d, thread=%@",testPthreadKey,newData->i, thread);
});
sleep(1);
NSThread *thread = [NSThread currentThread];
NSLog(@"key=%ld, value=%d, thread=%@",testPthreadKey,data->i, thread);
}
输出结果为
2020-02-26 11:17:44.690066+0800 TimeConsumeTool[442:162506] key=267, value=100, thread=<NSThread: 0x2830cca80>{number = 2, name = (null)}
2020-02-26 11:17:45.690508+0800 TimeConsumeTool[442:162480] key=267, value=1, thread=<NSThread: 0x283085a40>{number = 1, name = main}
可见主线程和子线程都各自维护了相同key的私有数据。
在iOS开发中,我自己在Hook Objc_msgSend时使用过TSD记录每个线程执行过的指令来计算时间开销,另外在RxSwift的scheduler和也是使用TSD来存储调度函数,在今后的开发中如果有类似的应用场景都可以使用TSD。