iOS 底層探索之多線程(二)—線程和鎖

回顧 \color{red}{}

上一篇博客中,我們已經對進程和線程有了一定的了解了侮叮,那么本次博客將繼續(xù)講解!

1. 線程的\color{red}{生命周期}

在程序開發(fā)中有個名詞——生命周期悼瘾,我們都知道\color{red}{APP} 有生命周期囊榜,那么線程的生命周期是什么樣子的呢审胸?

  • 線程生命周期

線程生命周期大致包括 5個階段:

\color{red}{新建}:通過創(chuàng)建線程的函數方法,創(chuàng)建一個新的線程卸勺。
\color{red}{就緒}:線程創(chuàng)建完成之后砂沛,調用 start方法,線程這個時候處于等待狀態(tài)曙求,等待CPU時間分配執(zhí)行碍庵。
\color{red}{運行}:當就緒的線程被調度并獲得CPU資源時,便進入運行狀態(tài)悟狱,run方法定義了線程的操作和功能静浴。
\color{red}{阻塞}:在運行狀態(tài)的時候,可能因為某些原因導致運行狀態(tài)的線程變成了阻塞狀態(tài)芽淡,比如sleep马绝、等待同步鎖,線程就從可調度線程池移出挣菲,處于了阻塞狀態(tài)富稻,這個時候sleep到時、獲取同步鎖白胀,此時會重新添加到可調度線程池椭赋。喚醒的線程不會立刻執(zhí)行run方法,它們要再次等待CPU分配資源進入運行狀態(tài)或杠。
\color{red}{銷毀}:如果線程正常執(zhí)行完畢后或線程被提前強制性的終止或出現異常導致結束哪怔,那么線程就要被銷毀,釋放資源向抢。

線程生命周期大致流程圖如下:


  • 線程狀態(tài)演練方法
@interface ViewController ()
@property (nonatomic, strong) NSThread *p_thread;
@end

/**
 線程狀態(tài)演練方法
 */
- (void)testThreadStatus{
    NSLog(@"%d %d %d", self.p_thread.isExecuting, self.p_thread.isFinished, self.p_thread.isCancelled);
    // 生命周期
    
    if ( self.p_thread == nil || self.p_thread.isCancelled || self.p_thread.isFinished ) {
        self.p_thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        self.p_thread.name = @"跑步線程";
        [self.p_thread start];
    }else{
        NSLog(@"%@ 正在執(zhí)行",self.p_thread.name);
        
        //可以設置彈框 ---> 這里直接制空
        [self.p_thread cancel];
        self.p_thread = nil;
    }
}

2. 線程池的運行策略

線程池運行策略

線程的工作執(zhí)行认境,也是有一定的策略的,線程池的運行策略見下圖:


隊列滿了且正在運行的線程數量挟鸠,小于最大線程數叉信,則新進來的任務,會直接創(chuàng)建非核心線程來完成工作

  • 線程池剛創(chuàng)建時艘希,里面沒有一個線程硼身。任務隊列是作為參數傳進來的。不過覆享,就算隊列里面有任務佳遂,線程池也不會馬上執(zhí)行它們。

當有任務時撒顿,線程池會做如下判斷:

如果正在運行的線程數量小于corePoolSize(核心線程數)丑罪,那么馬上創(chuàng)建核心線程運行這個任務。
如果正在運行的線程數量大于或等于corePoolSize,那么將這個任務放入隊列巍糯。

如果這時候隊列滿了啸驯,而且正在運行的線程數量小于maximumPoolSize(最大線程數)客扎,那么還是要創(chuàng)建非核心線程立刻運行這個任務祟峦。

如果隊列滿了,而且正在運行的線程數量大于或等于maximumPoolSize徙鱼,那么線程池飽和策略將進行處理宅楞。
當一個線程完成任務時,它會從隊列中取下一個任務來執(zhí)行袱吆。

當一個線程無事可做厌衙,超過一定的時間(超時)時,線程池會判斷绞绒,如果當前運行的線程數大于corePoolSize婶希,那么這個線程就被停掉。所以線程池的所有任務完成后蓬衡,它最終會收縮到corePoolSize的大小喻杈。

飽和策略

如果線程池中的隊列滿了,并且正在運行的線程數量已經大于等于當前線程池的最大線程數狰晚,則進行飽和策略的處理筒饰。

  • AbortPolicy直接拋出RejectedExecutionExeception異常來阻?系統正常運?
  • CallerRunsPolicy將任務回退到調?者
  • DisOldestPolicy丟掉等待最久的任務
  • DisCardPolicy直接丟棄任務

3. 自旋鎖和互斥鎖

任務的執(zhí)行速度的影響因素:

  • CPU
  • 任務的復雜度
  • 任務的優(yōu)先級
  • 線程的狀態(tài)

優(yōu)先級翻轉:

  • IO 密集型(頻繁的等待線程)
  • CPU密集型(很少等待)
  • IO 比CPU更容易得到優(yōu)先級的提升
  • 餓死:一直等不到執(zhí)行,就丟棄了
  • 調度:優(yōu)先級和CPU的調度還有關系

優(yōu)先級因素:

  • 用戶指定優(yōu)先級 --> threadPriority
   // 主線程 512K
    NSLog(@"%@ %zd K %d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
    NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(eat) object:nil];
    // 1. name - 在應用程序中壁晒,收集錯誤日志瓷们,能夠記錄工作的線程!
    // 否則不好判斷具體哪一個線程出的問題秒咐!
    t.name = @"吃飯線程";
    //This value must be in bytes and a multiple of 4KB.
    t.stackSize = 1024*1024;
    t.threadPriority = 1;
    [t start];

threadPriority

threadPriority 替換成qualityOfService(NSQualityOfService)

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

  • 等待的頻繁度
  • 長時間不執(zhí)行(也會提升優(yōu)先級)

    下圖是:一個線程的經典案例

車票售賣系統谬晕,是線程工作執(zhí)行的經典案例,如果是多個窗口賣票携取,會出現資源搶奪的情況攒钳,如果 A窗口賣了一張票,B窗口不知道歹茶,或者同一時間 AB窗口賣同一張票夕玩,這樣就會出現問題,所有鎖的意義重大了惊豺。

自旋鎖

是一種用于保護多線程\color{red}{共享資源}的鎖燎孟,與一般\color{red}{互斥鎖(mutex)}不同之處在于當\color{red}{}自旋鎖嘗試獲取鎖時,以忙等待\color{red}{(busy waiting)}的形式不斷地循環(huán)檢查鎖是否可用尸昧;當上一個線程的任務沒有執(zhí)行完畢的時候(\color{red}{被鎖住})揩页,那么下一個線程會一直\color{red}{等待}(不會睡眠);當上一個線程的任務執(zhí)行完畢烹俗,下一個線程會立即執(zhí)行爆侣。

在多\color{red}{CPU}的環(huán)境中萍程,對持有鎖較短的程序來說,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能兔仰。

\color{red}{自旋鎖}:OSSpinLock茫负、dispatch_semaphore_t

互斥鎖

當上一個線程的任務沒有執(zhí)行完畢的時候(\color{red}{被鎖住}),那么下一個線程會進入\color{red}{睡眠狀態(tài)}等待任務執(zhí)行完畢乎赴,當上一個線程的任務執(zhí)行完畢忍法,下一個線程會自動喚醒然后執(zhí)行任務,該任務也不會立刻執(zhí)行榕吼,而是成為可執(zhí)行狀態(tài)(\color{red}{就緒})饿序。
\color{red}{互斥鎖}:pthread_mutex、@ synchronized羹蚣、NSLock原探、NSConditionLock、NSCondition顽素、NSRecursiveLock

自旋鎖和互斥鎖的特點

\color{red}{自旋鎖}會忙等咽弦,所謂忙等,即在訪問被鎖資源時戈抄,調用者線程不會休眠离唬,而是不停循環(huán)在那里,直到被鎖資源釋放鎖划鸽。
\color{red}{互斥鎖}會休眠输莺,所謂休眠,即在訪問被鎖資源時裸诽,調用者線程會休眠嫂用,此時cpu可以調度其他線程工作,直到被鎖資源釋放鎖丈冬。此時會喚醒休眠線程嘱函。

\color{red}{自旋鎖優(yōu)缺點}

  • 優(yōu)點在于,因為自旋鎖不會引起調用者睡眠埂蕊,所以不會進行線程調度往弓,CPU時間片輪轉等耗時操作。所有如果能在很短的時間內獲得鎖蓄氧,自旋鎖的效率遠高于互斥鎖函似。
  • 缺點在于,自旋鎖一直占用CPU喉童,他在未獲得鎖的情況下撇寞,一直運行自旋,所以占用著CPU,如果不能在很短的時間內獲得鎖蔑担,這無疑會使CPU效率降低牌废。自旋鎖不能實現遞歸調用。

原子屬性和非原子屬性

\color{red}{OC}在定義屬性時有\color{red}{nonatomic}\color{red}{atomic}兩種選擇啤握,默認為\color{red}{atomic}屬性

  • \color{red}{atomic}:原子屬性鸟缕,為setter方法加自旋鎖(即為單寫多讀)
  • \color{red}{nonatomic}:非原子屬性,不會為setter方法加鎖

\color{red}{nonatomic}\color{red}{atomic}的對比

  • \color{red}{atomic}:線程安全恨统,需要消耗大量的資源叁扫;
  • \color{red}{nonatomic}:非線程安全三妈,適合內存小的移動設備畜埋。

平時開發(fā)需要注意

  • 如非需搶占資源的屬性(如購票,充值)畴蒲,所有屬性都聲明為\color{red}{nonatomic}悠鞍。
  • 盡量避免多線程\color{red}{搶奪}同一塊資源。
  • 盡量將\color{red}{加鎖}模燥、\color{red}{資源搶奪}的業(yè)務邏輯交給服務器端處理咖祭,減小移動客戶端的壓力。

\color{red}{atomic}底層實現自旋鎖

我們在探索類的本質時蔫骂,對于類的屬性的setter方法么翰,系統會有一層objc_setProperty的封裝(libobjc.dylib源碼)


底層會調用reallySetProperty方法,在該方法的實現中辽旋,針對原子屬性浩嫌,添加了spinlock鎖

  • objc_setProperty_atomic_copy
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}
  • reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

SpinlockLinux內核中提供的一種比較常見的鎖機制,自旋鎖是原地等待的方式解決資源沖突的补胚,即码耐,一個線程獲取了一個自旋鎖后,另外一個線程期望獲取該自旋鎖溶其,獲取不到骚腥,只能夠原地打轉(忙等待)。由于自旋鎖的這個忙等待的特性瓶逃,注定了它使用場景上的限制 —— 自旋鎖不應該被長時間的持有(消耗CPU資源)束铭。

糾正一下
atomic只是原子屬性的一個標識符,所以atomic并不是自旋鎖厢绝,底層是通過Spinlock實現自旋鎖契沫。

線程和Runloop的關系

  1. runloop與線程是一一對應的,一個runloop對應一個核心的線程代芜,為什么說是核心的埠褪,是因為runloop是可以嵌套的,但是核心的只能有一個,他們的關系保存在一個全局
    的字典里钞速。
  2. runloop是來管理線程的贷掖,當線程的runloop被開啟后,線程會在執(zhí)行完任務后進入休
    眠狀態(tài)渴语,有了任務就會被喚醒去執(zhí)行任務苹威。
  3. runloop在第一次獲取時被創(chuàng)建,在線程結束時被銷毀驾凶。
  4. 對于主線程來說牙甫,runloop在程序一啟動就默認創(chuàng)建好了。
  5. 對于子線程來說调违,runloop是懶加載的窟哺,只有當我們使用的時候才會創(chuàng)建,所以在子線程用定時器要注意:確保子線程的runloop被創(chuàng)建技肩,不然定時器不會回調且轨。

4. iOS技術方案

多線程有Pthread、NSThread虚婿、GCD旋奢、NSOperation 等方案。

iOS技術方案如下圖:

關于多線程的更多信息然痊,可以去蘋果文檔去看看
Threading Programming Guide

iOS底層探索之多線程(三)—初識GCD

更多內容持續(xù)更新

?? 喜歡就點個贊吧????
?? 覺得有收獲的至朗,可以來一波,收藏+關注剧浸,評論 + 轉發(fā)锹引,以免你下次找不到我????
?? 歡迎大家留言交流,批評指正辛蚊,互相學習??粤蝎,提升自我??
?? 作者郵件:zhangxmsy@163.com 有問題聯系。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末袋马,一起剝皮案震驚了整個濱河市初澎,隨后出現的幾起案子,更是在濱河造成了極大的恐慌虑凛,老刑警劉巖碑宴,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異桑谍,居然都是意外死亡延柠,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門锣披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贞间,“玉大人贿条,你說我怎么就攤上這事≡鋈龋” “怎么了整以?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長峻仇。 經常有香客問我公黑,道長,這世上最難降的妖魔是什么摄咆? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任凡蚜,我火速辦了婚禮,結果婚禮上吭从,老公的妹妹穿的比我還像新娘朝蜘。我一直安慰自己,他們只是感情好影锈,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布芹务。 她就那樣靜靜地躺著,像睡著了一般鸭廷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熔吗,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天辆床,我揣著相機與錄音,去河邊找鬼桅狠。 笑死讼载,一個胖子當著我的面吹牛,可吹牛的內容都是我干的中跌。 我是一名探鬼主播咨堤,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漩符!你這毒婦竟也來了一喘?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤嗜暴,失蹤者是張志新(化名)和其女友劉穎凸克,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體闷沥,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡萎战,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了舆逃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚂维。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡戳粒,死狀恐怖,靈堂內的尸體忽然破棺而出虫啥,到底是詐尸還是另有隱情享郊,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布孝鹊,位于F島的核電站炊琉,受9級特大地震影響,放射性物質發(fā)生泄漏又活。R本人自食惡果不足惜苔咪,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柳骄。 院中可真熱鬧团赏,春花似錦、人聲如沸耐薯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曲初。三九已至体谒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臼婆,已是汗流浹背抒痒。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颁褂,地道東北人故响。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像颁独,于是被迫代替她去往敵國和親彩届。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容