壹:RunLoop和線程的關係

語言: CN / TW / HK

壹:RunLoop和線程的關係

這裏的RunLoop的源碼參考了兩個地址的源碼,一個是GitHub上的http://github.com/apple/swift-corelibs-foundation中的runloop源碼,一個是http://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對象。