多線程相關(guān)知識點

圖1 知識結(jié)構(gòu)

一侦厚、知識結(jié)構(gòu)分析

整體知識架構(gòu)

備注:
從上到下耻陕,更加面向?qū)ο螅簿透菀资褂谩?p>

多線程之間的關(guān)系

  • pthread是POSIX線程的API

  • NSThread是Cocoa對pthread的封裝

  • GCD和NSOperationQueue是基于隊列的并發(fā)API

  • GCD是基于pthread和queue實現(xiàn)的

  • NSOperationQueue是對GCD的高級封裝

  • GCD

  • NSOperation
    AFNetWorking + SDWebImage框架多有使用

問題1:AFNetWorking為什么使用NSOperation刨沦,為什么不使用GCD诗宣?

解釋:
此道題目也可以理解為NSOperation比GCD的有點。
1已卷、NSOperation是基于GCD更高一層的封裝梧田,完全面向?qū)ο螅槐菺CD更加簡單易用侧蘸,代碼可讀性高裁眯。
2、 FIFO隊列讳癌,而NSOperationQueue中的隊列可以被重新設(shè)置優(yōu)先級穿稳,從而實現(xiàn)不同操作的執(zhí)行順序調(diào)整。CGD不具備
3晌坤、添加操作之間的依賴關(guān)系逢艘,方便的控制執(zhí)行順序;GCD不具備骤菠。
4它改、可以很方便的取消一個操作的執(zhí)行;CGD不具備商乎。
5央拖、使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改;CGD不具備鹉戚。

  • NSThread
    用于實現(xiàn)一個常駐線程

問題2:常駐線程有什么作用鲜戒?

解釋:
通常情況下,創(chuàng)建子線程抹凳,在里面執(zhí)行任務(wù)遏餐,任務(wù)完成后,子線程會立刻銷毀赢底;如果需要經(jīng)常子線程中操作任務(wù)失都,那么頻繁的創(chuàng)建和銷毀子線程會造成資源的浪費。
所以需要常駐線程幸冻。

  • 多線程和鎖
    線程同步和資源共享

二粹庞、GCD

主要結(jié)構(gòu)如下:

  • 同步/異步、串行/并發(fā)
  • dispatch_barrier_async
  • dispatch_group

2.1嘁扼、同步/異步信粮、串行/并發(fā)

  • dispatch_sync(serial_queue,^{ //任務(wù) })趁啸;

  • dispatch_async(serial_queue强缘,^{ //任務(wù) })督惰;

  • dispatch_sync(concurrent_queue,^{ //任務(wù) })

  • dispatch_async(concurrent_queue旅掂,^{ //任務(wù) })

2.1.1赏胚、同步串行

問題3:主隊列同步

圖2 主隊列同步

解釋:
隊列引起的循環(huán)等待,不是線程引起的循環(huán)等待商虐。
詳細解釋如下:

圖3 同步串行

1觉阅、ios中默認會有一個主隊列、主線程秘车。
2典勇、主隊列是一個串行隊列。
3叮趴、viewDidLoad在主隊列中割笙,可以看成一個任務(wù)1。
4眯亦、Block相當于在主隊列添加任務(wù)2伤溉。
5、viewDidLoad在主線程運行妻率。
6乱顾、dispatch_sync說明任務(wù)2也在主線程運行。
7宫静、任務(wù)1完成后才能執(zhí)行任務(wù)2走净。
但是任務(wù)1還沒有完成,就開始執(zhí)行任務(wù)2囊嘉,任務(wù)2有依賴任務(wù)1的完成温技,任務(wù)1依賴任務(wù)2的完成革为,造成死鎖扭粱。

問題4:主隊列異步

沒有問題

解釋:
雖然沒有問題,但是主隊列提交的任務(wù)震檩,無論通過同步/異步方式琢蛤,都要在主線程進行處理!E茁病博其!

問題5:串行隊列同步

圖4 串行隊列同步

解釋:
不會有問題。
詳細解釋如下:

圖3 串行隊列

1迂猴、iOS默認會有一個主隊列慕淡、主線程。
2沸毁、主隊列是一個串行隊列峰髓。
3傻寂、viewDidLoad在主隊列中,可以看成一個任務(wù)1携兵。
4疾掰、Block是在另一個串行隊列中,可以看成任務(wù)2徐紧。
5静檬、viewDidLoad在主線程運行。
6并级、dispatch_sync說明任務(wù)2也在主線程運行拂檩。
7、因為二者不是在同一個隊列嘲碧,不會存在死鎖广恢,但是任務(wù)2會延遲任務(wù)1執(zhí)行。

2.2.2呀潭、同步并發(fā)

問題6:下面代碼輸出結(jié)果是:

圖4 同步并發(fā)

解釋
1钉迷、iOS默認會有一個主隊列、主線程钠署。
2糠聪、主隊列是一個串行隊列。
3谐鼎、viewDidLoad在主隊列中舰蟆,可以看成一個任務(wù)1。
4狸棍、global_queue是全局并發(fā)隊列身害,里面有任務(wù)2和任務(wù)3
5、viewDidLoad在主線程運行草戈。
6塌鸯、dispatch_sync說明global_queue中的任務(wù)也在主線程運行(會阻斷線程,強制執(zhí)行自己的)唐片。
7丙猬、因為global_queue和主線程隊列不是同一個隊列,不會造成死鎖费韭。
8茧球、因為global_queue是全局并發(fā)隊列,一個任務(wù)不用管前面的任務(wù)是否執(zhí)行完畢星持。所以任務(wù)2未完成時抢埋,可以執(zhí)行任務(wù)3,然后執(zhí)行任務(wù)2,都是在主線程執(zhí)行揪垄。

2.2.3鲤屡、異步串行

圖5 異步串行

這段代碼是經(jīng)常使用的
代碼分析:
1、ios中默認會有一個主隊列福侈、主線程酒来。
2、主隊列是一個串行隊列肪凛。
3堰汉、viewDidLoad在主隊列中,可以看成一個任務(wù)1伟墙。
4翘鸭、Block相當于在主隊列添加任務(wù)2。
5戳葵、viewDidLoad在主線程運行就乓。
6、dispatch_async說明任務(wù)2在子線程運行拱烁,也就是不會阻擋任務(wù)1的運行生蚁。
7、任務(wù)1完成后才能執(zhí)行任務(wù)2戏自。
因為任務(wù)1在子線程運行邦投,不會阻擋任務(wù)2,所以正常使用擅笔。

2.2.4志衣、異步并發(fā)

問題7:以下代碼輸出結(jié)果:

圖6 異步并發(fā)

解釋
1、global_queue是全局隊列猛们,采用dispatch_async念脯,所以會開辟一個子線程。
2弯淘、子線程的runLoop默認是不開啟的绿店,而performSelector:withObject:afterDelay是在沒有runloop的情況下會失效,所以此方法不執(zhí)行耳胎。
3惯吕、打印結(jié)果13惕它。

2.3怕午、dispatch_barrier_async()

2.3.1、場景

問題8:怎樣利用CGD實現(xiàn)多讀單寫淹魄?

利用CGD提供的柵欄函數(shù)
解析:

多讀單寫模型

  • 讀者郁惜、讀者并發(fā)
  • 讀者、寫者互斥
  • 寫者、寫者互斥

可以理解為:
1兆蕉、讀處理之間是并發(fā)的羽戒,肯定要用并發(fā)隊列
因為讀取操作虎韵,往往需要立刻返回結(jié)果易稠,故采用同步
這些讀處理允許在多個子線程包蓝。
2驶社、寫處理時候,其余操作都不能執(zhí)行测萎。利用柵欄函數(shù)亡电,異步操作。利用柵欄函數(shù)異步操作的原因:柵欄函數(shù)同步操作會阻塞當前線程硅瞧,如果當前線程還有其它操作份乒,則會影響用戶體驗。

核心代碼如下:

@interface UserCenter()
{
    // 定義一個并發(fā)隊列
    dispatch_queue_t concurrent_queue;
    
    // 用戶數(shù)據(jù)中心, 可能多個線程需要數(shù)據(jù)訪問
    NSMutableDictionary *userCenterDic;
}

@end

// 多讀單寫模型
@implementation UserCenter

- (id)init
{
    self = [super init];
    if (self) {
        // 通過宏定義 DISPATCH_QUEUE_CONCURRENT 創(chuàng)建一個并發(fā)隊列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 創(chuàng)建數(shù)據(jù)容器
        userCenterDic = [NSMutableDictionary dictionary];
    }
    
    return self;
}

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步讀取指定數(shù)據(jù)
    dispatch_sync(concurrent_queue, ^{
        obj = [userCenterDic objectForKey:key];
    });
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 異步柵欄調(diào)用設(shè)置數(shù)據(jù)
    dispatch_barrier_async(concurrent_queue, ^{
        [userCenterDic setObject:obj forKey:key];
    });
}

2.3.2腕唧、dispatch_barrier_sync和dispatch_barrier_async區(qū)別

共同點:

  • 它們前面的任務(wù)先執(zhí)行完或辖。
  • 它們的任務(wù)執(zhí)行完,再執(zhí)行后面的任務(wù)枣接。

不同點:

  • dispatch_barrier_sync會阻止當前線程孝凌,等它的任務(wù)執(zhí)行完畢,才能往下進行月腋。
  • dispatch_barrier_async不會阻塞當前線程蟀架,允許其它非當前隊列的任務(wù)繼續(xù)執(zhí)行。

注意:
使用柵欄函數(shù)時榆骚,使用自定義隊列才有意義片拍,如果使用串行隊列/系統(tǒng)的全局并發(fā)隊列,這個柵欄函數(shù)就相當于一個同步函數(shù)

2.3妓肢、dispatch_group

問題9:使用CGD實現(xiàn)這個需求:A捌省、B、C三個任務(wù)并發(fā)碉钠,完成后執(zhí)行任務(wù)D纲缓。

 // 創(chuàng)建一個group
 dispatch_group_t group = dispatch_group_create();
 // 異步組分派到并發(fā)隊列當中
  dispatch_group_async(group, concurrent_queue, ^{
  });
  //監(jiān)聽  
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 當添加到組中的所有任務(wù)執(zhí)行完成之后會調(diào)用該Block
 });

三、NSOperation

3.1喊废、NSOperation優(yōu)點

  • 添加任務(wù)依賴
  • 任務(wù)執(zhí)行狀態(tài)控制
  • 最大并發(fā)量(maxConcurrentOperationCount)

問題10:我們可以控制任務(wù)的哪些狀態(tài)祝高?

  • isReady
    當前任務(wù)是否處于就緒狀態(tài)
  • isExecuting
    當前任務(wù)是否正在執(zhí)行
  • isFinished
    當前任務(wù)是否執(zhí)行完成
  • isCancelled
    當前任務(wù)是否被標記為取消(不是判斷是否被取消,是標記)
    是通過KVO進行控制的污筷。

3.2工闺、狀態(tài)控制

問題11:我們怎么控制NSOperation的狀態(tài)

  • 如果只重寫了main方法,底層控制任務(wù)執(zhí)行狀態(tài)以及任務(wù)退出。
  • 如果重寫了start方法陆蟆,自行控制任務(wù)狀態(tài)雷厂。

問題12:系統(tǒng)是怎樣移除一個isFinished=YES的NSOperation的?

  • 通過KVO

小結(jié):
NSOperation: 主隊列默認在主線程執(zhí)行叠殷,自定義隊列默認在后臺執(zhí)行(會開辟子線程)改鲫。

四、NSThread

4.1林束、啟動流程

啟動流程

1钩杰、調(diào)用start()方法、啟動線程诊县。
2讲弄、在start()內(nèi)部會創(chuàng)建一個pthread線程,指定pthread線程的啟動函數(shù)依痊。
3避除、在啟動函數(shù)中會調(diào)用NSThread定義的main()函數(shù)。
4胸嘁、在main()函數(shù)中會調(diào)用performSelector:函數(shù)瓶摆,來執(zhí)行我們創(chuàng)建的函數(shù)。
5性宏、指定函數(shù)運行完成群井,會調(diào)用exit()函數(shù),退出線程毫胜。

4.2书斜、常駐線程

參考RunLoop

五、多線程與鎖

問題13:iOS中都有哪些鎖酵使,你是怎樣使用的荐吉?

解釋:

5.1、@synchronized(互斥鎖) ??????

  • 一般在創(chuàng)建單例對象的時候使用口渔,保證在多線程環(huán)境下样屠,創(chuàng)建的單例對象是唯一的。

5.2缺脉、 atomic(自旋鎖)??????

  • 屬性關(guān)鍵字
  • 對被修飾對象進行原子操作(不負責使用)
    備注:

原子操作:不會被線程調(diào)度打斷的操作痪欲;這種操作一旦開始,就一直運行到結(jié)束攻礼,中間不會切換到另一個線程业踢。

不負責使用:屬性賦值時候,能夠保證線程安全秘蛔;對屬性進行操作陨亡,不能保證線程安全傍衡。
例如:
@property (atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//線程安全
[self.array addObject:obj];//線程不安全

5.3深员、 OSSpinLock(自旋鎖)

  • 循環(huán)等待訪問负蠕,不釋放當前資源。
  • 用于輕量級數(shù)據(jù)訪問倦畅,簡單的int值 +1/-1操作遮糖。
  • 使用場景:
    • 內(nèi)存引用計數(shù)加1或減1
    • runtime也有使用到。

5.4叠赐、 NSLock(互斥鎖)

螞蟻金服面試題:

NSLock面試題

解釋:
對同一把鎖兩次調(diào)用欲账,由于重入的原因會造成死鎖;解決辦法就是使用遞歸鎖(可以重入)芭概。

5.5赛不、 NSRecursiveLock(遞歸鎖)(互斥鎖)

NSRecursiveLock鎖

5.6、 dispatch_semaphore_t(信號量)??????

  • dispatch_semaphore_create(5)
    創(chuàng)建信號量罢洲,指定最大并發(fā)數(shù)
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    等待信號量>=1踢故。
    如果當前信號量>=1,信號量減1惹苗,程序繼續(xù)執(zhí)行殿较。
    如果信號量<=0,原地等待桩蓉,不允許程序往下執(zhí)行淋纲。
  • dispatch_semaphore_signal(semaphore)
    程序執(zhí)行完畢,發(fā)送信號院究,釋放資源洽瞬,信號量加1。
5.6.1业汰、dispatch_semaphore_create
dispatch_semaphore_create
5.6.2片任、dispatch_semaphore_wait
dispatch_semaphore_wait
5.6.3、dispatch_semaphore_signal
dispatch_semaphore_signal

小結(jié):
1蔬胯、鎖分為互斥鎖对供、自旋鎖
2、互斥鎖和自旋鎖的區(qū)別
自旋鎖: 忙等待氛濒。即在訪問被鎖資源時产场,調(diào)用者線程不會休眠,而是不停循環(huán)在那里舞竿,直到被鎖資源釋放
互斥鎖: 會休眠京景。即在訪問被鎖資源時,調(diào)用者線程會休眠骗奖,此時cpu可以調(diào)度其它線程工作确徙,直到被鎖資源釋放醒串,此時會喚醒休眠線程。

問題14:iOS系統(tǒng)為我們提供了幾種多線程技術(shù)鄙皇?各自有什么特點芜赌?

解釋:

  • GCD
    用于一些簡單的線程同步,包括子線程分派伴逸;還有就是解決類似于多讀單寫功能缠沈。

  • NSOperation及NSOperationQueue
    可以方便控制任務(wù)狀態(tài)、添加依賴/移除依賴错蝴;多用于復雜線程控制:AFNetWorking和SDWebImage洲愤。

  • NSThread
    往往用于實現(xiàn)一個常駐線程。

總結(jié):

概念理解

  • 隊列概念
    隊列是任務(wù)的容器

  • 串行隊列顷锰、并發(fā)隊列區(qū)別
    串行隊列: 一次僅僅調(diào)度一個任務(wù)柬赐,隊列中的任務(wù)一個個執(zhí)行姨蟋。(一個任務(wù)完成后龄砰,再運行下一個任務(wù))。
    遵循FIFO原則:先進先出棵逊、后進后出万矾。

    并發(fā)隊列:不需要把一個任務(wù)完成后悼吱,再運行下一個任務(wù)。
    仍然遵循FIFO原則良狈,只是不需要等待任務(wù)完成后添。

  • 并行、并發(fā)區(qū)別
    并行:同一時刻薪丁,多條指令在多個處理器同時執(zhí)行遇西。
    并發(fā):同一個時刻,只能處理一條指令严嗜,但是多個指令被快速的輪換執(zhí)行粱檀,達到了具有同時執(zhí)行的效果。

  • 異步漫玄、同步區(qū)別
    異步: 可以開啟新的線程茄蚯。
    同步: 不可以開啟新的線程,在當前線程運行睦优。

同步異步渗常、串行并行形象理解

這兩對概念單獨看起來,明白怎么回事汗盘;但是皱碘,一旦運用起來,總是不能得心應手隐孽“┐唬總的來說健蕊,就是不能將概念熟記于心,缺乏形象概念踢俄。

下面采用圖解 + 文字進行表述:

串行隊列圖解
并行隊列圖解

一個隊列(串行+并發(fā))好比一個容器缩功。
執(zhí)行代碼好比一個個任務(wù)
同步異步好比任務(wù)的標簽褪贵。
容器里面裝有好多個打有標簽任務(wù)掂之。
線程好比流水線的傳送帶抗俄,
所有的工作都是CPU在做脆丁,姑且將CPU比做操作工

代碼運行的時候动雹,大家想象工廠的流水線的工作場景:
1槽卫、從容器(隊列)中取出任務(wù)(執(zhí)行代碼),放到傳送帶上胰蝠。
如果容器是串行隊列歼培,則完成一個,取出一個茸塞。
如果容器是并發(fā)隊列躲庄,則一直不停的投放。
2钾虐、任務(wù)(執(zhí)行代碼)放到傳送帶(線程)的一剎那噪窘,CPU(操作工)看了一眼上面的標簽:如果標簽是同步,就將它放到當前傳送帶效扫;如果標簽是異步倔监,就新增加一條傳送帶,然后把任務(wù)放上去(理解操作工無所不能菌仁,可以隨意增加傳送帶)浩习。

上面只是一個形象的比喻,加深對多線程理解济丘。

小結(jié):
從上面的分析可知:
1谱秽、串行隊列任務(wù)之間相互包含,容易造成死鎖摹迷;并發(fā)隊列則不會疟赊。這種死鎖稱為隊列死鎖。
2泪掀、并發(fā)隊列+異步听绳,才會有多線程效果。
如果只有當前一個線程可以利用异赫,并發(fā)隊列中任務(wù)雖然可以快速取出分派椅挣,奈何只有一個線程(主干道)头岔,只能一個個排隊執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼠证,一起剝皮案震驚了整個濱河市峡竣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌量九,老刑警劉巖适掰,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荠列,居然都是意外死亡类浪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門肌似,熙熙樓的掌柜王于貴愁眉苦臉地迎上來费就,“玉大人,你說我怎么就攤上這事川队×ο福” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵固额,是天一觀的道長眠蚂。 經(jīng)常有香客問我,道長斗躏,這世上最難降的妖魔是什么逝慧? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮瑟捣,結(jié)果婚禮上馋艺,老公的妹妹穿的比我還像新娘。我一直安慰自己迈套,他們只是感情好捐祠,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桑李,像睡著了一般踱蛀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贵白,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天率拒,我揣著相機與錄音,去河邊找鬼禁荒。 笑死猬膨,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的呛伴。 我是一名探鬼主播勃痴,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼谒所,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沛申?” 一聲冷哼從身側(cè)響起劣领,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铁材,沒想到半個月后尖淘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡著觉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年村生,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片固惯。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡梆造,死狀恐怖缴守,靈堂內(nèi)的尸體忽然破棺而出葬毫,到底是詐尸還是另有隱情,我是刑警寧澤屡穗,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布贴捡,位于F島的核電站,受9級特大地震影響村砂,放射性物質(zhì)發(fā)生泄漏烂斋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一础废、第九天 我趴在偏房一處隱蔽的房頂上張望汛骂。 院中可真熱鬧,春花似錦评腺、人聲如沸帘瞭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝶念。三九已至,卻和暖如春芋绸,著一層夾襖步出監(jiān)牢的瞬間媒殉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工摔敛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留廷蓉,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓马昙,卻偏偏與公主長得像桃犬,于是被迫代替她去往敵國和親售貌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

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