壹: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物件。