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

回顧

上一篇博客中,我們已經(jīng)對進(jìn)程和線程有了一定的了解了列粪,那么本次博客將繼續(xù)講解宵晚!

在這里插入圖片描述

1. 線程的生命周期

在程序開發(fā)中有個名詞——生命周期寻定,我們都知道APP 有生命周期襟衰,那么線程的生命周期是什么樣子的呢?

  • 線程生命周期

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

  • 新建:通過創(chuàng)建線程的函數(shù)方法逊躁,創(chuàng)建一個新的線程谢翎。

  • 就緒:線程創(chuàng)建完成之后,調(diào)用 start方法走趋,線程這個時候處于等待狀態(tài)衅金,等待CPU時間分配執(zhí)行。

  • 運(yùn)行:當(dāng)就緒的線程被調(diào)度并獲得CPU資源時吆视,便進(jìn)入運(yùn)行狀態(tài)典挑,run方法定義了線程的操作和功能。

  • 阻塞:在運(yùn)行狀態(tài)的時候啦吧,可能因為某些原因?qū)е逻\(yùn)行狀態(tài)的線程變成了阻塞狀態(tài)您觉,比如sleep等待同步鎖授滓,線程就從可調(diào)度線程池移出琳水,處于了阻塞狀態(tài),這個時候sleep到時般堆、獲取同步鎖在孝,此時會重新添加到可調(diào)度線程池。喚醒的線程不會立刻執(zhí)行run方法淮摔,它們要再次等待CPU分配資源進(jìn)入運(yùn)行狀態(tài)私沮。

  • 銷毀:如果線程正常執(zhí)行完畢后或線程被提前強(qiáng)制性的終止或出現(xiàn)異常導(dǎo)致結(jié)束,那么線程就要被銷毀和橙,釋放資源仔燕。

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

線程生命周期
  • 線程狀態(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);
        
        //可以設(shè)置彈框 ---> 這里直接制空
        [self.p_thread cancel];
        self.p_thread = nil;
    }
}

2. 線程池的運(yùn)行策略

線程池運(yùn)行策略

線程的工作執(zhí)行,也是有一定的策略的魔招,線程池的運(yùn)行策略見下圖:

線程池運(yùn)行策略

隊列滿了且正在運(yùn)行的線程數(shù)量晰搀,小于最大線程數(shù),則新進(jìn)來的任務(wù)办斑,會直接創(chuàng)建非核心線程來完成工作外恕。

  • 線程池剛創(chuàng)建時,里面沒有一個線程。任務(wù)隊列是作為參數(shù)傳進(jìn)來的鳞疲。不過罪郊,就算隊列里面有任務(wù),線程池也不會馬上執(zhí)行它們建丧。

當(dāng)有任務(wù)時排龄,線程池會做如下判斷:

  • 如果正在運(yùn)行的線程數(shù)量小于corePoolSize(核心線程數(shù)),那么馬上創(chuàng)建核心線程運(yùn)行這個任務(wù)翎朱。
    如果正在運(yùn)行的線程數(shù)量大于或等于corePoolSize橄维,那么將這個任務(wù)放入隊列。
  • 如果這時候隊列滿了拴曲,而且正在運(yùn)行的線程數(shù)量小于maximumPoolSize(最大線程數(shù))争舞,那么還是要創(chuàng)建非核心線程立刻運(yùn)行這個任務(wù)。
  • 如果隊列滿了澈灼,而且正在運(yùn)行的線程數(shù)量大于或等于maximumPoolSize竞川,那么線程池飽和策略將進(jìn)行處理。
  • 當(dāng)一個線程完成任務(wù)時叁熔,它會從隊列中取下一個任務(wù)來執(zhí)行委乌。
  • 當(dāng)一個線程無事可做,超過一定的時間(超時)時荣回,線程池會判斷遭贸,如果當(dāng)前運(yùn)行的線程數(shù)大于corePoolSize,那么這個線程就被停掉心软。所以線程池的所有任務(wù)完成后壕吹,它最終會收縮到corePoolSize的大小。

飽和策略

如果線程池中的隊列滿了删铃,并且正在運(yùn)行的線程數(shù)量已經(jīng)大于等于當(dāng)前線程池的最大線程數(shù)耳贬,則進(jìn)行飽和策略的處理。

  • AbortPolicy直接拋出RejectedExecutionExeception異常來阻?系統(tǒng)正常運(yùn)?
  • CallerRunsPolicy將任務(wù)回退到調(diào)?者
  • DisOldestPolicy丟掉等待最久的任務(wù)
  • DisCardPolicy直接丟棄任務(wù)

3. 自旋鎖和互斥鎖

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

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

優(yōu)先級翻轉(zhuǎn):

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

優(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 - 在應(yīng)用程序中咒劲,收集錯誤日志,能夠記錄工作的線程诫隅!
    // 否則不好判斷具體哪一個線程出的問題缎患!
    t.name = @"吃飯線程";
    //This value must be in bytes and a multiple of 4KB.
    t.stackSize = 1024*1024;
    t.threadPriority = 1;
    [t start];
    

threadPriority

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)先級)

下圖是:一個線程的經(jīng)典案例


線程經(jīng)典案例

車票售賣系統(tǒng),是線程工作執(zhí)行的經(jīng)典案例阎肝,如果是多個窗口賣票,會出現(xiàn)資源搶奪的情況肮街,如果 A窗口賣了一張票风题,B窗口不知道,或者同一時間 AB窗口賣同一張票,這樣就會出現(xiàn)問題沛硅,所有鎖的意義重大了眼刃。

自旋鎖

是一種用于保護(hù)多線程共享資源的鎖,與一般互斥鎖mutex)不同之處在于當(dāng)自旋鎖嘗試獲取鎖時摇肌,以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用擂红;當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖住),那么下一個線程會一直等待(不會睡眠)围小;當(dāng)上一個線程的任務(wù)執(zhí)行完畢昵骤,下一個線程會立即執(zhí)行。

在多CPU的環(huán)境中肯适,對持有鎖較短的程序來說变秦,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。

自旋鎖OSSpinLock框舔、dispatch_semaphore_t

互斥鎖

當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖住)蹦玫,那么下一個線程會進(jìn)入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢,當(dāng)上一個線程的任務(wù)執(zhí)行完畢刘绣,下一個線程會自動喚醒然后執(zhí)行任務(wù)樱溉,該任務(wù)也不會立刻執(zhí)行,而是成為可執(zhí)行狀態(tài)(就緒)纬凤。
互斥鎖pthread_mutex福贞、@ synchronized、NSLock移斩、NSConditionLock肚医、NSCondition、NSRecursiveLock

自旋鎖和互斥鎖的特點

  • 自旋鎖會忙等向瓷,所謂忙等肠套,即在訪問被鎖資源時,調(diào)用者線程不會休眠猖任,而是不停循環(huán)在那里你稚,直到被鎖資源釋放鎖。

  • 互斥鎖會休眠朱躺,所謂休眠刁赖,即在訪問被鎖資源時,調(diào)用者線程會休眠长搀,此時cpu可以調(diào)度其他線程工作宇弛,直到被鎖資源釋放鎖。此時會喚醒休眠線程源请。

自旋鎖優(yōu)缺點

  • 優(yōu)點在于枪芒,因為自旋鎖不會引起調(diào)用者睡眠彻况,所以不會進(jìn)行線程調(diào)度,CPU時間片輪轉(zhuǎn)等耗時操作舅踪。所有如果能在很短的時間內(nèi)獲得鎖纽甘,自旋鎖的效率遠(yuǎn)高于互斥鎖。
  • 缺點在于抽碌,自旋鎖一直占用CPU悍赢,他在未獲得鎖的情況下,一直運(yùn)行自旋货徙,所以占用著CPU左权,如果不能在很短的時間內(nèi)獲得鎖,這無疑會使CPU效率降低破婆。自旋鎖不能實現(xiàn)遞歸調(diào)用涮总。

原子屬性和非原子屬性

OC在定義屬性時有nonatomicatomic兩種選擇,默認(rèn)為atomic屬性

  • atomic:原子屬性祷舀,為setter方法加自旋鎖(即為單寫多讀)
  • nonatomic:非原子屬性瀑梗,不會為setter方法加鎖

nonatomicatomic的對比

  • atomic:線程安全,需要消耗大量的資源裳扯;
  • nonatomic:非線程安全抛丽,適合內(nèi)存小的移動設(shè)備。

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

  • 如非需搶占資源的屬性(如購票饰豺,充值)亿鲜,所有屬性都聲明為nonatomic
  • 盡量避免多線程搶奪同一塊資源冤吨。
  • 盡量將加鎖蒿柳、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力漩蟆。

atomic底層實現(xiàn)自旋鎖

我們在探索類的本質(zhì)時垒探,對于類的屬性的setter方法,系統(tǒng)會有一層objc_setProperty的封裝(libobjc.dylib源碼)

objc_setProperty

底層會調(diào)用reallySetProperty方法怠李,在該方法的實現(xiàn)中圾叼,針對原子屬性,添加了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內(nèi)核中提供的一種比較常見的鎖機(jī)制捺癞,自旋鎖是原地等待的方式解決資源沖突的夷蚊,即,一個線程獲取了一個自旋鎖后髓介,另外一個線程期望獲取該自旋鎖惕鼓,獲取不到,只能夠原地打轉(zhuǎn)(忙等待)唐础。由于自旋鎖的這個忙等待的特性呜笑,注定了它使用場景上的限制 —— 自旋鎖不應(yīng)該被長時間的持有(消耗CPU資源)夫否。

糾正一下

atomic只是原子屬性的一個標(biāo)識符,所以atomic并不是自旋鎖叫胁,底層是通過Spinlock實現(xiàn)自旋鎖。

線程和Runloop的關(guān)系

  1. runloop與線程是一一對應(yīng)的汞幢,一個runloop對應(yīng)一個核心的線程驼鹅,為什么說是核心的,是因為runloop是可以嵌套的森篷,但是核心的只能有一個输钩,他們的關(guān)系保存在一個全局
    的字典里。
  2. runloop是來管理線程的仲智,當(dāng)線程的runloop被開啟后买乃,線程會在執(zhí)行完任務(wù)后進(jìn)入休
    眠狀態(tài),有了任務(wù)就會被喚醒去執(zhí)行任務(wù)钓辆。
  3. runloop在第一次獲取時被創(chuàng)建剪验,在線程結(jié)束時被銷毀。
  4. 對于主線程來說前联,runloop在程序一啟動就默認(rèn)創(chuàng)建好了功戚。
  5. 對于子線程來說,runloop是懶加載的似嗤,只有當(dāng)我們使用的時候才會創(chuàng)建啸臀,所以在子線程用定時器要注意:確保子線程的runloop被創(chuàng)建,不然定時器不會回調(diào)烁落。

4. iOS技術(shù)方案

多線程有Pthread乘粒、NSThreadGCD伤塌、NSOperation 等方案灯萍。

iOS技術(shù)方案如下圖:


iOS技術(shù)方案

關(guān)于多線程的更多信息,可以去蘋果文檔去看看
Threading Programming Guide

Threading Programming Guide

更多內(nèi)容持續(xù)更新

?? 喜歡就點個贊吧????

?? 覺得有收獲的寸谜,可以來一波竟稳,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā)熊痴,以免你下次找不到我????

??歡迎大家留言交流他爸,批評指正,互相學(xué)習(xí)??果善,提升自我??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诊笤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巾陕,更是在濱河造成了極大的恐慌讨跟,老刑警劉巖纪他,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晾匠,居然都是意外死亡茶袒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門凉馆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薪寓,“玉大人,你說我怎么就攤上這事澜共∠虿妫” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵嗦董,是天一觀的道長母谎。 經(jīng)常有香客問我,道長京革,這世上最難降的妖魔是什么奇唤? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮存崖,結(jié)果婚禮上冻记,老公的妹妹穿的比我還像新娘。我一直安慰自己来惧,他們只是感情好冗栗,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著供搀,像睡著了一般隅居。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葛虐,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天胎源,我揣著相機(jī)與錄音,去河邊找鬼屿脐。 笑死涕蚤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的的诵。 我是一名探鬼主播万栅,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼西疤!你這毒婦竟也來了烦粒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤代赁,失蹤者是張志新(化名)和其女友劉穎扰她,沒想到半個月后兽掰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徒役,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年孽尽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廉涕。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡泻云,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狐蜕,到底是詐尸還是另有隱情,我是刑警寧澤卸夕,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布层释,位于F島的核電站,受9級特大地震影響快集,放射性物質(zhì)發(fā)生泄漏贡羔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一个初、第九天 我趴在偏房一處隱蔽的房頂上張望乖寒。 院中可真熱鬧,春花似錦院溺、人聲如沸楣嘁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逐虚。三九已至,卻和暖如春谆膳,著一層夾襖步出監(jiān)牢的瞬間叭爱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工漱病, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留买雾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓杨帽,卻偏偏與公主長得像漓穿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子睦尽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容