2021-02-04 多線程

為啥要并發(fā)

  • 讓多個任務(wù)同時執(zhí)行
  • 提高app運行的性能,保證App實時響應(yīng)
    1因為UI界面運行在主線程之上,它是一個串行線程。如果將所有代碼都放在主線程上運行,那么主線程將承擔(dān)網(wǎng)絡(luò)請求,數(shù)據(jù)處理,圖像渲染等操作,無論是GPU還是計算機內(nèi)存,都會性能耗盡,從而影響用戶體驗。

線程和進程的區(qū)別

同步和異步,串行和并行。多任務(wù)和阻塞

異步:異步和同步是相對的,同步就是順序執(zhí)行,執(zhí)行完一個再執(zhí)行下一個,需要等待、協(xié)調(diào)運行。異步就是彼此獨立,在等待某事件的過程中繼續(xù)做自己的事,不需要等待這一事件完成后再工作。線程就是實現(xiàn)異步的一個方式。異步是讓調(diào)用方法的主線程不需要同步等待另一線程的完成,從而可以讓主線程干其它的事情。
異步和多線程并不是一個同等關(guān)系,異步是最終目的,多線程只是我們實現(xiàn)異步的一種手段。異步是當(dāng)一個調(diào)用請求發(fā)送給被調(diào)用者,而調(diào)用者不用等待其結(jié)果的返回而可以做其它的事情。實現(xiàn)異步可以采用多線程技術(shù)或則交給另外的進程來處理。
Sync Async Serial Concurrent

  • Serial/Concurrent聲明隊列的屬性是串行的還是并行的。串行隊列(Serial Queue)指在同一時間內(nèi),隊列中只能執(zhí)行一個任務(wù),當(dāng)前任務(wù)執(zhí)行完后才能執(zhí)行下一個任務(wù)。
    在串行隊列中只有一個線程。
    并行隊列(Concurrent Queue)允許多個任務(wù)在同一個時間同時進行,在并行隊列中有多個線程。串行隊列的任務(wù)一定是按開始的順序結(jié)束。而并行隊列的任務(wù)并不一定會按照開始的順序結(jié)束。

任務(wù):就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的。執(zhí)行任務(wù)有兩種方式:『同步執(zhí)行』 和 『異步執(zhí)行』。兩者的主要區(qū)別是:是否等待隊列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。

同步執(zhí)行(sync):
同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。
只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
異步執(zhí)行(async):
異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)。
可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。
串行隊列(Serial Dispatch Queue):
每次只有一個任務(wù)被執(zhí)行。讓任務(wù)一個接著一個地執(zhí)行。(只開啟一個線程,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
并發(fā)隊列(Concurrent Dispatch Queue):
可以讓多個任務(wù)并發(fā)(同時)執(zhí)行。(可以開啟多個線程,并且同時執(zhí)行任務(wù))
注意:并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效。

  • Sync/Async聲明任務(wù)是同步還是異步執(zhí)行的。
    同步(Sync)會把當(dāng)前的任務(wù)加到隊列中,等到任務(wù)執(zhí)行完成,線程才會返回繼續(xù)運行。
    也就是說,同步會阻塞線程。
    異步(Async)也會把當(dāng)前的任務(wù)添加到隊列中,但它會立刻返回,無須等任務(wù)執(zhí)行完成,也就是說異步不會阻塞線程。
    無論是串行隊列還是并行隊列,都可以執(zhí)行異步或者同步操作。
    注意,在串行隊列中執(zhí)行同步操作容易造成死鎖。
    在并行隊列中則不用擔(dān)心這個問題。異步操作無論是在串行隊列中執(zhí)行還是在并行隊列中執(zhí)行,都可能出現(xiàn)競態(tài)問題;
    同時,異步操作經(jīng)常與逃逸閉包一起出現(xiàn)在API的設(shè)計中

串行隊列的代碼實戰(zhàn)

// 串行同步
serialQueue.sync {
print(1);
}
print(2);
serialQueue.sync {
print(3);
}
print(4);
1
2
3
4
串行隊列中同步操作時打印1,2,3,4

// 串行異步
serialQueue.async {
print(1);
}
print(2);
serialQueue.asyn {
print(3);
}
print(4);
serialQueue mainqueue
1 2不用等1
3等1 3等2 4等2
1 不用等我執(zhí)行完 12 21
2 主隊列,同步 1243 1234 2134 2143 2413
3 不用等我執(zhí)行完
4 主隊列,同步

同步

同步執(zhí)行:比如這里的dispatch_sync,這個函數(shù)會把一個block加入到指定的隊列中,而且會一直等到執(zhí)行完blcok,這個函數(shù)才返回。因此在block執(zhí)行完之前,調(diào)用dispatch_sync方法的線程是阻塞的。

異步

異步執(zhí)行:一般使用dispatch_async,這個函數(shù)也會把一個block加入到指定的隊列中,但是和同步執(zhí)行不同的是,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了。

// 串行異步嵌套同步 1
外邊異步塊 2
5 3阻塞線程等3完成
print(1) 4
serialQueue.async {
print(2)
serialQueue.sync {
print(3)
}
print(4)
}
print(5)
dispatch_sync() 同步執(zhí)行,完成了它預(yù)定的任務(wù)后才返回,阻塞當(dāng)前線程
dispatch_async() 異步執(zhí)行,會立即返回,預(yù)定的任務(wù)會完成但不會等它完成,不阻塞當(dāng)前線程
mainqueue serialQueue不同等
1 2 不同等
5 3 3進行的條件是整個block塊結(jié)束,3等塊結(jié)束
4 4等3結(jié)束
1,2

串行隊列+同步任務(wù):不會開啟新的線程,任務(wù)逐步完成。
不會開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)。

串行隊列+異步任務(wù):開啟新的線程,任務(wù)逐步完成。
并發(fā)隊列+同步任務(wù):不會開啟新的線程,任務(wù)逐步完成。

  • 任務(wù)按順序執(zhí)行的。按順序執(zhí)行的原因:雖然 并發(fā)隊列 可以開啟多個線程,并且同時執(zhí)行多個任務(wù)。但是因為本身不能創(chuàng)建新線程,只有當(dāng)前線程這一個線程(同步任務(wù) 不具備開啟新線程的能力),所以也就不存在并發(fā)。而且當(dāng)前線程只有等待當(dāng)前隊列中正在執(zhí)行的任務(wù)執(zhí)行完畢之后,才能繼續(xù)接著執(zhí)行下面的操作(同步任務(wù) 需要等待隊列的任務(wù)執(zhí)行結(jié)束)。所以任務(wù)只能一個接一個按順序執(zhí)行,不能同時被執(zhí)行。

并發(fā)隊列+異步任務(wù):可以開啟多個線程,任務(wù)交替(同時)執(zhí)行。
|
串 主
1 2
3 4

1一定在3之前被打印出來,因為1在3之前派發(fā),串行隊列一次只能執(zhí)行一個任務(wù)。所以一旦派發(fā)完就執(zhí)行任務(wù)。
2一定在4之前被打印出來
2一定在3之前被打印出來

多線程的實現(xiàn)方式?區(qū)別?每種方式的適用場景

1Pthread

pthread 是一套通用的多線程的 API,可以在Unix / Linux / Windows 等系統(tǒng)跨平臺使用,使用 C 語言編寫,需要程序員自己管理線程的生命周期,使用難度較大,我們在 iOS 開發(fā)中幾乎不使用 pthread,但是還是來可以了解一下的。
場景 獲取堆棧信息
https://blog.csdn.net/wxs0124/article/details/104961809
/_np 是指 not POSIX ,這里的 POSIX 是指操作系統(tǒng)的一個標(biāo)準,特別是與 Unix 兼容的操作系統(tǒng)。np 表示與標(biāo)準不兼容
pthread_t pt = pthread_from_mach_thread_np(list[i]);
獲取當(dāng)前調(diào)用棧的信息

#pragma mark - Interface
+ (NSString *)callStackWithType:(SMCallStackType)type {
// 所有線程
if (type == SMCallStackTypeAll) {
thread_act_array_t threads; //int 組成的數(shù)組比如 thread[1] = 5635

2NSThread

最大限度的掌控每個線程的生命周期。但是,也需要開發(fā)者手動管理所有的進程活動,
比如創(chuàng)建、同步、暫停、取消,其中手動加鎖操作的挑戰(zhàn)性很大。
NSThread總體使用場景很小,基本是在開發(fā)底層的開源軟件或是測試時使用。
還有就是獲取一些線程信息,如當(dāng)前線程,主線程,線程名稱。

NSThread *nsthread = [NSThread currentThread]; //當(dāng)前執(zhí)行的指令

3GCD

4Operation

// A、B、C、D、E、F六個任務(wù)
// A、B、C 并發(fā)執(zhí)行
// D--->A,B
// E--->B,C
// F--->D、E
// 1A、B、C沒有依賴關(guān)系

NSOperation實現(xiàn)

 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 6;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"A任務(wù)");
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"B任務(wù)");
        }
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"C任務(wù)");
        }
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"D任務(wù)");
        }
    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"E任務(wù)");
        }
    }];
    NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"F任務(wù)");
        }
    }];
    [op4 addDependency:op1];
    [op4 addDependency:op2];
    [op5 addDependency:op2];
    [op5 addDependency:op3];
    [op6 addDependency:op4];
    [op6 addDependency:op5];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
    [queue addOperation:op6];

dispatch實現(xiàn)

/ D--->A,B
    dispatch_group_t g1 = dispatch_group_create();
    // E--->B,C
    dispatch_group_t g2 = dispatch_group_create();
    // F--->D、E
    dispatch_group_t g3 = dispatch_group_create();
    dispatch_group_enter(g1);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"A任務(wù)");
        }
        dispatch_group_leave(g1);
    });
    dispatch_group_enter(g1);
    dispatch_group_enter(g2);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"B任務(wù)");
        }
        dispatch_group_leave(g1);
        dispatch_group_leave(g2);
    });
    dispatch_group_enter(g2);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"C任務(wù)");
        }
        dispatch_group_leave(g2);
    });
    dispatch_group_enter(g3);
    dispatch_group_notify(g1, dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"D任務(wù)");
        }
        dispatch_group_leave(g3);
    });
    dispatch_group_enter(g3);
    dispatch_group_notify(g2, dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"E任務(wù)");
        }
        dispatch_group_leave(g3);
    });
    dispatch_group_notify(g3, dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"F任務(wù)");
        }
    });

dispatch_group_t底層
底層是一個結(jié)構(gòu)體
類似一個鏈表的形式存儲當(dāng)前的任務(wù)
struct dispatch_group_s {
DISPATCH_SEMAPHORE_HEADER(group, dg)
int_volatile dg_waiters;
struct dispatch_continuation_s * volatitile dg_notify_head;
struct dispatch_continuation_s * volatitile dg_notify_tail;
}
enter函數(shù)原子自增,dg_value做加 兩次enter之后就是等于2
leve函數(shù)原子自減,dg_value做減,每次減1
當(dāng)dg_value為0的時候,就是執(zhí)行_dispatch_group_wake(dg, false)
(dispatch源碼蘋果官網(wǎng)可下載)
enter,leave類似于信號量的標(biāo)記
標(biāo)記當(dāng)前有多少個任務(wù)要執(zhí)行
notify就是等group的任務(wù)為0,就是dg_value的任務(wù)為0的時候

BlockOperation是基于狀態(tài)機制實現(xiàn)的
隊列的任務(wù)是isReady狀態(tài)時,才會被隊列調(diào)度
operationqueue是一個隊列
adddependency其實就是在底層建立一個依賴關(guān)系
比如D任務(wù)依賴于A,B其實實現(xiàn)就是,等A任務(wù)B任務(wù)的狀態(tài)都編程isFinish后,D任務(wù)的狀態(tài)變成isReady,然后isReady的任務(wù)就可以被調(diào)度了
所以D需要監(jiān)聽A,B的狀態(tài)值變化,就是一個KVO
A依賴于B任務(wù),A會存在一個dpendencies數(shù)組,里面存放著B
B有downdenpendices數(shù)組,存放著B
isReady的判斷是根據(jù)當(dāng)前計數(shù)器判斷
然后最底層還是根據(jù)GCD來調(diào)度
operation源碼在gihub上

多線程和runloop的關(guān)系

  1. 一一對應(yīng)的關(guān)系
    -通過k-value的形式存儲在一個全局的字典中,來對應(yīng)當(dāng)前的線程和
    runloop
    -runloop源碼
    TSL是線程局部存儲空間
    當(dāng)前線程訪問autoreleasepool
    return os_mac
    pthread_getspecific
    return os_linux
    pthread_getspecific
    return os_win32
    pthread_getspecific
    私有函數(shù),當(dāng)前線程的局部存儲空間只能被當(dāng)前線程所訪問到,其他線程訪問不到。
    runloop沒有就會創(chuàng)建,放在全局字典里的同時也會在TSL里做一個緩存。
    當(dāng)前線程的?;?br> 子線程執(zhí)行完任務(wù)后就會銷毀掉,
    線程常駐,就是
    _port = [NSMacPort port];
    _thread = [[LXJThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil]];
    [_thread start];
    @autoreleasepool {
    // port mach_msg 消息隊列的收發(fā),決定當(dāng)前runloop是休眠還是執(zhí)行任務(wù)
    // port是和mac_os內(nèi)核有關(guān)的東西
    // 端口收發(fā)消息隊列
    // 先獲取當(dāng)前子線程的runloop
    NSRunloop *runloop = [NSRunloop currentRunloop];
    // 向當(dāng)前的子線程runloop add port
    // port用來收發(fā)消息
    [self registerobserver]
    [[runloop addPort:port forMode:nsrunloopdefaultnmode]];
    [runloop run];
    線程常駐后如何銷毀子線程
    子線程能存活就是依賴與port事件源
    是否可以移除事件源頭,然后解除
    // runloop不會退出 [[runloop remove:port forMode:nsrunloopdefaultnmode]];
    如果控制器銷毀,runloop會不會退出
    // runloop不會退出,還會有野指針問題
    // 因為移除當(dāng)前線程的事件源時,并不能保證系統(tǒng)不往線程里添加一些額外的事件源。所以沒有辦法通過移除自己事件源來移除常駐線程。
    // 當(dāng)前直接去退出當(dāng)前線程
    // [NSThread exit];線程無法響應(yīng),當(dāng)前線程銷毀,野指針。程序崩潰,線程銷毀了,runloop不銷毀還會造成內(nèi)存泄漏。
    // 看源碼CFRunloop怎么stop的
    // 判斷CFRunloop finished是通過
    // CFRunloop isFinished
    // 如果當(dāng)前runloop的mode是空的或者runloopmode是空,isfinish就是true。沒有runloop mode 就finish了但是對現(xiàn)在沒有用,因為當(dāng)前runloop run 肯定有mode
    // 如果mode name == null或者是commonmode(commomode只是一個標(biāo)記)
    // runloop run起來本質(zhì)就是一個while循環(huán)
    // 循環(huán)結(jié)束依賴于。。。
    // _cfrunllop stop就相當(dāng)于有了退出標(biāo)記
    // CFRunloopStop([CFRunloop current])當(dāng)前的runloop還是退出不了
    }
    // 仿照源碼
    runloop runmode:nsdefaultmode beforedate: [snadate distantfuture]
  1. 有序列表第二項

# iOS 并發(fā)編程中的三大問題
* 競態(tài)條件
  兩個或兩個以上線程對共享的數(shù)據(jù)進行讀寫操作時,最終的數(shù)據(jù)結(jié)果不確定的情況。
* 優(yōu)先倒置
  低優(yōu)先級的任務(wù)會因為各種原因先于高優(yōu)先級的任務(wù)執(zhí)行。
* 死鎖問題
  兩個或者兩個以上的線程,它們之間互相等待彼此停止執(zhí)行,以獲得某種資源,但是沒有一方會提前退出的情況。在iOS開發(fā)中,**有一個經(jīng)典的例子就是兩個Operation互相依賴。
在對同一個串行隊列中進行異步、同步嵌套時:
#如何Debug并發(fā)編程問題
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容