壹:RunLoop和線程的關係
壹:RunLoop和線程的關係
這裏的RunLoop的源碼參考了兩個地址的源碼,一個是GitHub上的https://github.com/apple/swift-corelibs-foundation中的runloop源碼,一個是https://opensource.apple.com/source/CF/中的runloop源碼,主要是以後者為準,因為後者猜測更接近為iOS上的版本,前者應該為其他平台上的實現版本。
RunLoop
RunLoop是和線程相關的基礎架構的一部分。一般來説,一個線程執行完任務之後就會退出,但是這在Cocoa Touch中是行不通的,比如main thread,需要不停地去處理點擊事件等等。而Runloop的目的就在於此,它讓線程在有任務時工作,在無任務時休息。這種模型一般稱為Event Loop,即事件循環,通常邏輯如下:
swift
void CFRunLoopRun(void) {
int result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
KCFRunLoopRunLoopDefaultMode, ...);
} while (KCFRunLoopRunStopped != result && KCFRunLoopRunFinished != result);
}
Event Loop是在程序中等待和派發事件或者消息的一種設計模式。當Event Loop形成一個程序的中央控制流時,通常情況下,稱之為主循環。Event Loop在很多程序中都有實際的應用,比如Windows程序中的消息循環,MacOS中的事件循環等等。
而在iOS中RunLoop是一個對象,這個對象管理了需要處理的事件,並且提供了一個入口函數來處理。
swift
let runloop = RunLoop.current()
runloop.add(Port.init(), forMode: .common)
runloop.run()
線程在執行了這個函數之後,就會一直處於“接受消息 -> 等待 -> 處理”的循環中,直到循環結束,函數返回。
在iOS中有兩個類來管理RunLoop,一個是RunLoop類,一個是CFRunLoopRef。CFRunLoopRef是CoreFoundation框架封裝了,提供了面向對象的API,所有這些API都是線程安全的。RunLoop是基於CFRunLoop的封裝,提供了面向對象的API,但是這些API並不是線程安全的。
同時要注意RunLoop實例調用的run()方法並不是直接調用的該方法,而是封裝的以下方法:
```swift extension RunLoop { public func run() { while run(mode: .default, before: Date.distantFuture) { } }
public func run(until limitDate: Date) {
while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
}
......
} ```
RunLoop和線程
iOS中的線程,我們一般是使用Thread以及pthread_t 來管理的。通過開源的源碼可以看到,Thread是封裝了pthread_t的,而pthread_t是直接包裝最底層的mach thread。同時Thread和pthread_t是一一相關的。
在Swift的Runloop中,Runloop對象是對CFRunLoop的封裝,而CFRunLoop是基於pthread_t來進行管理的。
以下是RunLoop的部分源碼:
```c typedef pthread_mutex_t CFLock_t;
/// 全局的dictionary:key是pthread_t, value是CFRunLoopRef static CFMutableDictionaryRef loopsDic = NULL; // 訪問loopsDic的鎖 static CFLock_t loopLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
// ** 這個方法只能通過Foundation來調用 // 比如Swift的Runloop封裝CFRunloop,調用了_CFRunLoopGet2這個API,這個方法內部調用了_CFRunloopGet0 // 獲取一個pthread對應的Runloop CFRunLoopRef _CFRunloopGet0(pthread_t thread) { // 1、首先添加互斥鎖 pthread_mutex_lock(&loopLock);
//2、第一次進入,初始化全局Dic,並先為主線程創建一個RunLoop
if (!loopDic) {
loopsDic = CFDictionaryCreateMutable();
CFRunloopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_up());
CFDictionarySetValue(loopsDic, pthread_main_thread_up(), mainLoop);
}
//3、直接從全局Dic中獲取
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(loopsDic, thread);
//4、如果取不到,那就創建一個
if (!loop) {
loop = __CFRunLoopCreate(thread);
CFDictionarySetValue(loopDic, thread, loop);
}
//5、保證線程安全
// 註冊回調關聯:當thread被銷燬時,這個runloop也會被銷燬!
if (pthread_equal(t, thread_self())) {
//TSD:Thread-share data 線程共享數據
//這裏當前thread會持有tsdTable,同時tsdtable->data[__CFTSDKeyRunLoop]的值設為loop
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunloopCntr)) {
_CFSetTSD(_CFTSDKeyRunLoopCntr, __CFFinalizeRunLoop);
}
}
return loop;
} ```
線程和RunLoop之間是一一對應的,保存在了一個全局的Dictionary中。線程創建的時候是沒有RunLoop的,如果不主動獲取,那它一直都不會有。RunLoop的創建是發生在第一次獲取時,RunLoop的銷燬是發生線程結束時。線程結束時會調用 __CFFinalizeRunLoop 方法,這個方法中會銷燬全局的Dictionary中的Key-Value對。
除了全局的Dictionary之外,兩者之間還有互相持有的關係,其一是CFRunLoop的結構體中持有pthread_t, 其二是pthread_t的TSD數據中也持有runloop。
``` // runLoop中持有pthread struct __CFRunLoop { ... _CFThreadRef _pthread; CFMutableSetRef _modes; ... }
// pthread中持有runloop static void __CFSDSetSpecific(void *arg) { pthread_setspecific(_CFTSDIndexKey, arg); }
static __CFTSDTable __CFTSDGetTable(const Boolean create) { ... __CFTSDTable table = (__CFTSDTable *)__CFTSDGetSpecific(); __CFTSDSetSpecific(table); ... return table; }
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); ```
TSD
什麼是pthread_t的TSD數據呢?
TSD的全稱為:Thread-Specific Data即線程特有數據。TSD由具體的每一個線程來維護。TSD採用了一鍵多值的技術,即一個鍵對應多個不同的值,每個線程訪問數據時通過訪問該鍵來得到對應的數據
線程特有數據可以看作是一個二維的數組,key作為行的索引,而線程id作為列的索引。一個線程特有數據的key是一個不透明的pthread_key_t
數據類型。在一個進程中的所有線程都可以使用這個key。即使所有的線程使用了一樣的key,它們通過這個key訪問到的或者修改的線程特有數據也是不同的。
| Keys | T1 Thread | T2 Thread | T3 Thread | T4 Thread | | --- | --- | --- | --- | --- | | K1(__CFTSDKeyRunLoop) | 6 | 56 | 4 | 3 | | K2 | 87 | 21 | 0 | 9 | | K3 | 23 | 12 | 61 | 2 | | K4 | 11 | 76 | 47 | 88 |
以上表為例,線程T2使用的K3對應的數據是12,而線程T3使用的K3對應的數據是61。
對應到我們的代碼中上述代碼中的最後一行:_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
key就是_CFTSDKeyRunLoop,而value就是當前的loop。也就是説線程都會將當前的loop存儲在對應的線程特有數據中。
總結
以上介紹了RunLoop和線程的關係,嚴格來講它們除了互相持有之外,還有一個全局的哈希表來存儲它們的對應關係,這個哈希表的Key是線程,Value是RunLoop對象。
- 我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿。