Contents
  1. 1. 某种优雅
  2. 2. TLS
    1. 2.1. TLS 的原理
    2. 2.2. __thread 关键字
    3. 2.3. TLS 在 iOS 上的应用

EarlGrey 是谷歌开源的自动化测试框架,之前公司使用它做了一段时间的自动化测试建设。一直没有仔细读过源码,偶然间得知阿里大佬们也在看这个,于是也想读读源码学习学习。

某种优雅

假设有 BaseClass,它声明了一个方法 doWork。我们有 ASubClass 和 BSubClass 继承 BaseClass,并分别重写doWork。这种情况大多发生在两条业务线上复用代码的时候,A 业务和 B 业务复用一个 View 组件,但是又需要两者某些地方不一样例如:埋点。A 的埋点肯定跟 B 得不一样,不然数据就是有问题的。由于埋点工作是放到各个子类中自己完成的,父类调用到 doWork 方法的时候自动调用相关子类的各自实现来实现差异控制。或者,doWork 是个 protocol,这样似乎是不是实现了 Java 里的抽象方法?

我们一般是这样写父类中的 doWork,但是光有注释不能很好的告诉大家方法必须让子类实现。

1
2
3
4
/// implemented by subclass
- (void)doWork {

}

EarlGrey 中有类似的地方:

1
2
3
4
5
// The perform:error: method has to be implemented by the subclass.
- (BOOL)perform:(id)element error:(__strong NSError **)errorOrNil {
[self doesNotRecognizeSelector:_cmd];
return NO;
}

但是采用了

[self doesNotRecognizeSelector:_cmd]; 这种方式来校验可能出错的情况,也可以换成断言。但我觉得前者更优雅一些。

TLS

我这里 TLS 不是 Transport Layer Security - Wikipedia 那个 TLS,而是 Thread-local storage - Wikipedia 线程局部存储。

1
2
3
4
5
6
// Resets the failure handler. Must be called from main thread otherwise behavior is undefined.
static inline void resetFailureHandler() {
assert([NSThread isMainThread]);
NSMutableDictionary *TLSDict = [[NSThread mainThread] threadDictionary];
[TLSDict setValue:[[GREYDefaultFailureHandler alloc] init] forKey:kGREYFailureHandlerKey];
}

EarlGrey 用这段代码重置全局的 Test Fail 时候的回调 handler。设置了这样一个全局变量同时又是线程安全的,在 A 函数里设置,然后隔了很久隔了很远的 B 函数,只要保证是同一个线程去取存储的数据,总是对的。

TLS 的原理

原理就是以当前 thread 为 key 往一个全局 map 中写值。每个 thread 对应一份自己的副本,看起来 thread 是独享自己的那份存储空间一样。

多线程读写共享变量还有个处理方法就是加锁,一个线程持有锁的时期另外一个线程想访问这个共享变量要么空转等待要么休眠,总之需要等待。这就是牺牲时间。TLS 多线程读写共享变量的时候不需要任何等待直接读写,因为全局的 map 是共享的,但是内部每个 k-v 并没有被共享。牺牲的是空间。

__thread 关键字

可以看到主线程的 i 并没有被其他线程的赋值所改变,再来看下指针

先在 22 行打印 i 的值和它的地址,到了第 25 行再打印地址发现报错了:

Couldn’t materialize: couldn’t get the value of variable i: No TLS data currently exists for this thread.

说明这个时候操作系统还未在当前 thread 为这个变量开辟存储空间,再往下走再打印就好了,同时看到 i 的地址并不一样。
网上看到 Windows 对 TLS 变量的实现是对标记了 TLS 的变量创建一个单独的节,操作系统载入程序的时候读取相应的节,创建对应的存储空间。当有新的线程创建的时候复制另外一份存储空间。在这里似乎也是差不多,只不过创建存储空间比较靠后,类似于懒加载的形式。

TLS 在 iOS 上的应用

sunnyxx 在他的博客中写到苹果利用 TLS 优化性能。这是一条证明 ARC 是 runtime 和编译器共同合作的证据。

在返回值身上调用objc_autoreleaseReturnValue方法时,runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里,发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。

于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。

Contents
  1. 1. 某种优雅
  2. 2. TLS
    1. 2.1. TLS 的原理
    2. 2.2. __thread 关键字
    3. 2.3. TLS 在 iOS 上的应用