iOS底层探索 - 线程私有数据

2,340 阅读2分钟

概念

多个函数共享数据时,除了通过函数间的返回值传递,还可以使用全局变量。在多线程的环境下,全局变量会被各个线程共享,如果我们需要只在单个线程所执行的函数间共享数据,例如要记录每个线程所执行过的函数名称,该如何做呢?

可以通过创建 线程私有数据(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。