iOS - 线程与AutoreleasePoolPage
1. AutoreleasePoolPage 结构
struct AutoreleasePoolPage {
static pthread_key_t key; // 只占用一份内存空间
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage *parent;
AutoreleasePoolPage *child;
// ...其他成员变量
}
pthread 的 TLS (Thread Local Storage) 机制通过 key-value 映射表实现了这个功能。
2. 基本原理
每个线程都维护着自己的存储空间(类似哈希表),所有线程共享同一个 key,但每个线程的 value 是独立的:
// pthread 内部实现的简化版本
struct pthread_internal_t {
// 每个线程都有自己的 specific 数组
void *specific[PTHREAD_KEYS_MAX]; // 线程特定数据数组
};
// 所有线程共享的键值数组
struct pthread_key_struct {
bool in_use; // 该 key 是否在使用
void (*destructor)(void *value); // 析构函数
} pthread_keys[PTHREAD_KEYS_MAX];
3. 关键操作
3.1 创建 key
// 在 tls_init 中创建 key
void tls_init(void) {
// pthread_key_create 会在 pthread_keys 数组中分配一个槽位
AutoreleasePoolPage::key = pthread_key_create(&autoreleasePoolDealloc);
}
3.2 设置线程特定数据
// 为当前线程设置 page
static void setHotPage(AutoreleasePoolPage *page) {
// 将 page 存储到当前线程的 specific 数组中
pthread_setspecific(AutoreleasePoolPage::key, page);
}
3.3 获取线程特定数据
static inline AutoreleasePoolPage *hotPage() {
// 从当前线程的 specific 数组中获取 page
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
pthread_getspecific(AutoreleasePoolPage::key);
return result;
}
4. 工作流程示意
// 简化的内部实现原理
pthread_key_create(key, destructor) {
// 分配一个全局唯一的 key
*key = allocate_key_index();
// 注册析构函数
pthread_keys[*key].destructor = destructor;
}
pthread_setspecific(key, value) {
// 获取当前线程
pthread_internal_t *thread = pthread_self();
// 将值存储在当前线程的特定位置
thread->specific[key] = value;
}
pthread_getspecific(key) {
// 获取当前线程
pthread_internal_t *thread = pthread_self();
// 返回当前线程存储的值
return thread->specific[key];
}
5. 实际应用示例
// 线程 1
void *thread1_func(void *arg) {
@autoreleasepool {
// pthread_getspecific(key) 返回线程1的 page
AutoreleasePoolPage *page = hotPage();
// page->thread == thread1
}
return NULL;
}
// 线程 2
void *thread2_func(void *arg) {
@autoreleasepool {
// pthread_getspecific(key) 返回线程2的 page
AutoreleasePoolPage *page = hotPage();
// page->thread == thread2
}
return NULL;
}
6. 关键点总结
1. key 的作用
- 作为索引,用于在线程的特定数据数组中定位数据
- 全局唯一,所有线程共享
2. 线程隔离
- 每个线程维护独立的存储空间
- 相同的 key 在不同线程中映射到不同的值
3. 内存管理
- 线程退出时自动调用析构函数
- 自动清理线程特定数据
4. 性能考虑
- 快速访问:O(1) 的时间复杂度
- 空间效率:每个线程只存储使用的数据
这就是为什么使用同一个 key 可以在不同线程中获取不同的 page 的原理。