線程
在多線程OS中键痛,線程是能獨立運行的基本單位,因而也是獨立調(diào)度和分派的基本單位匾七。由于線程很“輕”絮短,故線程的切換非常迅速且開銷小(在同一進程中的)
在一個進程中的多個線程之間昨忆,可以并發(fā)執(zhí)行丁频,甚至允許在一個進程中所有線程都能并發(fā)執(zhí)行;同樣邑贴,不同進程中的線程也能并發(fā)執(zhí)行席里,充分利用和發(fā)揮了處理機與外圍設(shè)備并行工作的能力。
在同一進程中的各個線程拢驾,都可以共享該進程所擁有的資源奖磁,這首先表現(xiàn)在:所有線程都具有相同的地址空間(進程的地址空間),這意味著繁疤,線程可以訪問該地址空間的每一個虛地址咖为;此外,還可以訪問進程所擁有的已打開文件稠腊、定時器躁染、信號量機構(gòu)等。由于同一個進程內(nèi)的線程共享內(nèi)存和文件架忌,所以線程之間互相通信不必調(diào)用內(nèi)核褐啡。
自動釋放池
App啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer鳖昌,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()备畦。
第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池许昨。其 order 是-2147483647懂盐,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前糕档。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池莉恼;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647速那,優(yōu)先級最低俐银,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼端仰,通常是寫在諸如事件回調(diào)捶惜、Timer回調(diào)內(nèi)的。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著荔烧,所以不會出現(xiàn)內(nèi)存泄漏吱七,開發(fā)者也不必顯示創(chuàng)建 Pool 了汽久。
自動釋放池的實現(xiàn):
- 一個線程的自動釋放池是一個指針堆棧
- 每一個指針或者指向被釋放的對象,或者是自動釋放池的POOL_BOUNDARY踊餐,POOL_BOUNDARY 是自動釋放池的邊界景醇。
- 一個池子的 token 是指向池子 POOL_BOUNDARY 的指針。當(dāng)池子被出棧的時候吝岭,每一個高于標(biāo)準(zhǔn)的對象都會被釋放掉三痰。
- 堆棧被分成一個頁面的雙向鏈表。頁面按照需要添加或者刪除窜管。
- 本地線程存放著指向當(dāng)前頁的指針酒觅,在這里存放著新創(chuàng)建的自動釋放的對象。
自動釋放池是由 AutoreleasePoolPage 以雙向鏈表的方式實現(xiàn)的微峰,當(dāng)對象調(diào)用 autorelease 方法時舷丹,會將對象加入 AutoreleasePoolPage 的棧中,調(diào)用 AutoreleasePoolPage::pop 方法會向棧中的對象發(fā)送 release 消息.
AutoreleasePoolPage 的結(jié)構(gòu)
AutoreleasePoolPage 是一個 C++ 中的類:
它在 NSObject.mm 中的定義是這樣的:
class AutoreleasePoolPage {
...
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
...
};
- magic 用于對當(dāng)前 AutoreleasePoolPage 完整性的校驗
- thread 保存了當(dāng)前頁所在的線程
每一個自動釋放池都是由一系列的 AutoreleasePoolPage 組成的蜓肆,并且每一個 AutoreleasePoolPage 的大小都是 4096 字節(jié)(16 進制 0x1000):
#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
AutoreleasepoolPage 通過壓棧的方式來存儲每個需要自動釋放的對象:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
RunLoop概念
蘋果對run loop 的解釋:
A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
RunLoop 實際上是一個對象颜凯,這個對象管理了其需要處理的事件和消息,并提供了一個入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯仗扬。線程執(zhí)行了這個函數(shù)后症概,函數(shù)會一直處于 "接受消息->等待->處理" 的循環(huán)中,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息)早芭。
CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的彼城,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的退个。CFRunLoopRef代碼是開源的募壕,https://opensource.apple.com/tarballs/CF/可以下載最新的源碼。
NSRunLoop 是基于 CFRunLoopRef 的封裝语盈,提供了面向?qū)ο蟮?API舱馅,但是這些 API 不是線程安全的。
RunLoop與線程的關(guān)系
獲取 main thread 的方法: pthread_main_thread_np() 或 [NSThread mainThread]刀荒,獲取當(dāng)前線程的方法:pthread_self() 或 [NSThread currentThread] 代嗤。
通過這兩個方法CFRunLoopGetMain() 和 CFRunLoopGetCurrent()來獲取RunLoop:
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
從上面的代碼可以看出,獲取RunLoop時需要傳pthread_t t作為參數(shù)缠借,線程和 RunLoop 之間是一一對應(yīng)的干毅,其關(guān)系是保存在一個全局的 Dictionary 里。線程剛創(chuàng)建時并沒有 RunLoop泼返,如果你不主動獲取硝逢,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時趴捅。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)垫毙。
總結(jié)
- 線程是獨立調(diào)度和分派的基本單位霹疫,RunLoop和自動釋放池為線程服務(wù)拱绑;
- RunLoop是一個事件循環(huán),讓線程休眠和線程崩鲂活成為了可能猎拨,線程休眠可以節(jié)省CPU資源;
- 自動釋放池一定存在于線程之中屠阻,解決了資源的釋放問題红省。