線程、隊列显蝌、任務(wù)(同、異步)等概念詳解

在那一刻订咸,我意識到曼尊,我必須選擇,要么對一切屈服脏嚷,得過且過地生活骆撇,要么就得努力,爭取過上夢想的生活父叙。

進程神郊、線程和以及程序


現(xiàn)代操作系統(tǒng)比如Mac OS X,UNIX趾唱,Linux涌乳,Windows等,都是支持“多任務(wù)”的操作系統(tǒng)甜癞。有了系統(tǒng)級別的支持夕晓,我們應(yīng)該在開發(fā)過程中應(yīng)該盡可能減少用戶等待時間,讓程序盡可能快的完成運算悠咱≌袅荆可是無論是哪種語言開發(fā)的程序最終往往轉(zhuǎn)換成匯編語言進而解釋成機器碼來執(zhí)行。但是機器碼是按順序執(zhí)行的析既,一個復(fù)雜的多步操作只能一步步按順序逐個執(zhí)行躬贡。改變這種狀況可以從下面講到的兩個角度(單核和多核)出發(fā)。
什么叫“多任務(wù)”呢眼坏?簡單地說拂玻,就是操作系統(tǒng)可以同時運行多個任務(wù)。打個比方,你一邊在用瀏覽器上網(wǎng)纺讲,一邊在聽MP3擂仍,一邊在用Word趕作業(yè),這就是多任務(wù)熬甚,至少同時有3個任務(wù)正在運行逢渔。還有很多任務(wù)悄悄地在后臺同時運行著,只是桌面上沒有顯示而已乡括。
現(xiàn)在肃廓,多核CPU已經(jīng)非常普及了,但是诲泌,即使過去的單核CPU盲赊,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼都是順序執(zhí)行的敷扫,那么哀蘑,單核CPU是怎么執(zhí)行多任務(wù)的呢?
答案就是操作系統(tǒng)輪流讓各個任務(wù)交替執(zhí)行葵第,任務(wù)1執(zhí)行0.01秒绘迁,切換到任務(wù)2,任務(wù)2執(zhí)行0.01秒卒密,再切換到任務(wù)3缀台,執(zhí)行0.01秒……這樣反復(fù)執(zhí)行下去。表面上看哮奇,每個任務(wù)都是交替執(zhí)行的膛腐,但是,由于CPU的執(zhí)行速度實在是太快了鼎俘,我們感覺就像所有任務(wù)都在同時執(zhí)行一樣哲身。即對于單核處理器,可以將多個步驟放到不同的線程而芥,這樣一來用戶完成UI操作后其他后續(xù)任務(wù)在其他線程中律罢,當(dāng)CPU空閑時會繼續(xù)執(zhí)行,而此時對于用戶而言可以繼續(xù)進行其他操作棍丐;
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實現(xiàn)误辑,但是,由于任務(wù)數(shù)量遠遠多于CPU的核心數(shù)量歌逢,所以巾钉,即使是多核CPU操作系統(tǒng),操作系統(tǒng)也會自動把很多任務(wù)輪流調(diào)度到每個核心上執(zhí)行秘案。
對于操作系統(tǒng)來說砰苍,一個任務(wù)就是一個進程(Process)潦匈,比如打開一個瀏覽器就是啟動一個瀏覽器進程,打開一個記事本就啟動了一個記事本進程赚导,打開兩個記事本就啟動了兩個記事本進程茬缩,打開一個Word就啟動了一個Word進程。有些進程還不止同時干一件事吼旧,比如Word凰锡,它可以同時進行打字、拼寫檢查圈暗、打印等事情掂为。在一個進程內(nèi)部,要同時干多件事员串,就需要同時運行多個“子任務(wù)”勇哗,我們把進程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
由于每個進程至少要干一件事寸齐,所以欲诺,一個進程至少有一個線程。當(dāng)然渺鹦,像Word這種復(fù)雜的進程可以有多個線程瞧栗,多個線程可以同時執(zhí)行,多線程的執(zhí)行方式和多進程是一樣的海铆,也是由操作系統(tǒng)在多個線程之間快速切換,讓每個線程都短暫地交替運行挣惰,看起來就像同時執(zhí)行一樣卧斟。當(dāng)然,真正地同時執(zhí)行多線程需要多核CPU才可能實現(xiàn)憎茂。

進程

狹義定義:進程就是一段程序的執(zhí)行過程珍语。即在系統(tǒng)中正在運行的一個應(yīng)用程序。比如同時打開微信和QQ竖幔,系統(tǒng)會分別啟動2個進程板乙;每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)拳氢。

廣義定義:進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動募逞。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中馋评,進程既是基本的分配單元放接,也是基本的執(zhí)行單元。

簡單的來講進程的概念主要有兩點:第一留特,進程是一個實體纠脾。每一個進程都有它自己的地址空間玛瘸,一般情況下,包括文本區(qū)域(text region)苟蹈、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)糊渊。文本區(qū)域存儲處理器執(zhí)行的代碼;數(shù)據(jù)區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內(nèi)存慧脱;堆棧區(qū)域存儲著活動過程調(diào)用的指令和本地變量渺绒。第二,進程是一個“執(zhí)行中的程序”磷瘤。程序是一個沒有生命的實體芒篷,只有處理器賦予程序生命時,它才能成為一個活動的實體采缚,我們稱其為進程针炉。

進程模擬.png
線程和主線程

一個進程要想執(zhí)行任務(wù),必須得有線程(每一個進程至少要有一條線程扳抽,即主線程)篡帕。
在引入線程的操作系統(tǒng)中,線程是進程中執(zhí)行運算的最小單位贸呢,是進程中的一個實體镰烧,通常都是把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調(diào)度的基本單位楞陷,由于線程比進程更小怔鳖,基本上不擁有系統(tǒng)資源,故對它的調(diào)度所付出的開銷就會小得多固蛾,能更高效的提高系統(tǒng)多個程序間并發(fā)執(zhí)行的程度结执;一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。
一個程序有且只有一個主線程(UI線程)艾凯,程序啟動時創(chuàng)建(調(diào)用main來啟動)献幔,主線程的生命周期是和應(yīng)用程序綁定,程序退出時趾诗,主線程也停止蜡感;由主線程的唯一性和功能特性个扰,我們不要將耗時的操作放到主線程中燃逻,耗時操作應(yīng)放在子線程(后臺線程,非主線程); 凡是和UI相關(guān)的操作應(yīng)放在主線程中操作律胀。
同一時間內(nèi)贝乎,一個線程只能執(zhí)行一個任務(wù),若要在1個進程中執(zhí)行多個任務(wù)杈笔,那么只能一個個的按順序執(zhí)行這些任務(wù)(線程的串行)。
線程自己不擁有系統(tǒng)資源糕非,只擁有在運行中必不可少的資源蒙具,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源球榆。

線程模擬.png
多線程

概念:一個進程中可以開啟多條線程,各線程可以并行(同時)執(zhí)行不同的任務(wù)禁筏;
原理:同一時間持钉,一個CPU只能處理一條線程,只有一條線程在工作篱昔,多線程并發(fā)(同時)執(zhí)行每强,其實是CPU快速的在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快州刽,就造成了多線程并發(fā)執(zhí)行的假象空执;
優(yōu)點:提高程序資源使用效率來提高系統(tǒng)的效率(CPU、內(nèi)存利用率)穗椅;
缺點:創(chuàng)建線程是有開銷的辨绊,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、椘ケ恚空間(子線程512KB门坷,主線程1MB)、創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間,如果開啟大量的線程袍镀,會降低程序的性能(一般最多3到5個)默蚌;線程越多,CPU在調(diào)度線程上的開銷就越大苇羡;程序設(shè)計更加復(fù)雜(比如線程之間的通信绸吸、多線程的數(shù)據(jù)共享);
iOS中多線程的實現(xiàn)方案:

  • pthread
  • NSThread
  • GCD
  • NSOperation
線程的幾種狀態(tài)
  • 創(chuàng)建:新創(chuàng)建一個線程對象设江;
  • 開啟(就緒狀態(tài)):線程對象創(chuàng)建之后惯裕,其他線程調(diào)用了該對象的start方法,該狀態(tài)的線程位于可運行線程池中绣硝,變得可運行,等待獲取CPU的使用權(quán)撑刺;
  • 運行:開啟(就緒)狀態(tài)的線程獲取了CPU資源鹉胖,執(zhí)行程序代碼;
  • 阻塞:因某種原因放棄CPU使用權(quán)够傍,暫停運行甫菠,直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)冕屯;線程處理阻塞狀態(tài)時在內(nèi)存中的表現(xiàn)情況是線程被移出可調(diào)度線程池寂诱,此時不可調(diào)度;
  • 死亡:線程執(zhí)行完了安聘、因異常退出了run方法或者是強制退出痰洒,這三種情況會導(dǎo)致線程的死亡瓢棒,線程生命周期結(jié)束;


    線程狀態(tài)
程序

說起進程丘喻,就不得不說下程序脯宿。先看定義:程序是指令和數(shù)據(jù)的有序集合,其本身沒有任何運行的含義泉粉,是一個靜態(tài)的概念连霉。而進程則是在處理機上的一次執(zhí)行過程,它是一個動態(tài)的概念嗡靡。這個不難理解跺撼,其實進程是包含程序的,進程的執(zhí)行離不開程序讨彼,進程中的文本區(qū)域就是代碼區(qū)歉井,也就是程序。

進程和線程比較
  • 進程是CPU分配資源和調(diào)度的單位点骑;
  • 線程是CPU調(diào)度(執(zhí)行任務(wù))的最小單位酣难,是程序執(zhí)行的最小單元;
  • 同一個進程內(nèi)的線程共享進程的所有資源黑滴;

任務(wù)和隊列


概念:

  • 任務(wù)(Task):要執(zhí)行的操作憨募;
  • 隊列(Queue):隊列是用來管理線程的,隊列里面放著很多的任務(wù)袁辈,來管理這些任務(wù)什么時候在那條線程里去執(zhí)行菜谣;廣義定義為隊列是一種特殊的線性表,特殊之處在于它只允許在表的前端(front)進行刪除操作晚缩,而在表的后端(rear)進行插入操作尾膊,即FIFO(front input front output),和棧一樣荞彼,隊列是一種操作受限制的線性表冈敛。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭鸣皂。

隊列類型:

  • 串行隊列(serialQueue):隊列中的線程按順序執(zhí)行(不會同時執(zhí)行)抓谴,即一次只能執(zhí)行一個task;


  • 并發(fā)隊列(concurrentQueue):隊列中的線程會并發(fā)執(zhí)行(同時執(zhí)行)寞缝,即一次可以執(zhí)行多個task癌压;


  • 主隊列:主線程隊列只有一個線程(主線程),主隊列是串行隊列荆陆;
隊列

任務(wù)(同滩届、異步)


  • 同步任務(wù)(Synchronous):所謂同步,就是發(fā)出一個功能調(diào)用時被啼,在沒有得到結(jié)果之前帜消,該調(diào)用就不返回或繼續(xù)執(zhí)行后續(xù)操作棠枉。優(yōu)先級高,在線程中有執(zhí)行順序券犁,不會開啟新的線程术健;
  • 異步任務(wù)(Asynchronous):異步與同步相對,當(dāng)一個異步過程調(diào)用發(fā)出后粘衬,調(diào)用者在沒有得到結(jié)果之前荞估,就可以繼續(xù)執(zhí)行后續(xù)操作。當(dāng)這個調(diào)用完成后稚新,一般通過狀態(tài)勘伺、通知和回調(diào)來通知調(diào)用者。對于異步調(diào)用褂删,調(diào)用的返回并不受調(diào)用者控制飞醉。優(yōu)先級低,在線程中執(zhí)行沒有順序屯阀,看cpu閑不閑缅帘。在主隊列中不會開啟新的線程,其他隊列會開啟新的線程难衰;

隊列和任務(wù)的區(qū)分


  1. 同步和異步主要影響:能不能開啟新的線程钦无。
    同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力盖袭。
    異步:可以在新的線程中執(zhí)行任務(wù)失暂,具備開啟新線程的能力。
  2. 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
    并發(fā):允許多個任務(wù)并發(fā)(同時)執(zhí)行鳄虱。
    串行:一個任務(wù)執(zhí)行完畢后弟塞,再執(zhí)行下一個任務(wù)。

以上概念之間關(guān)系抽象理解和舉例實現(xiàn)


  • 隊列(queue)當(dāng)做是管道容器拙已。
  • 容器(queue)里面可以裝有貨物(task)决记。
  • 線程(thread)相當(dāng)于生產(chǎn)線,多線程相當(dāng)于有多條生產(chǎn)線倍踪。
  • 同步異步相當(dāng)于貨物(task) 在生產(chǎn)線上是否生成完成系宫。
  • 串行隊列和并行隊列是對貨物的處理方式,串行隊列相當(dāng)于只允許一條生產(chǎn)線(thread)處理隊列的任務(wù)(task)惭适,并行隊列相當(dāng)于允許多條生產(chǎn)線(thread)處理隊列的任務(wù)。

從上面看我們想執(zhí)行一個任務(wù)task 楼镐,有兩個要素癞志,queue(容器)和對task的處理方式(同步和異步)。(不需要thread框产,因為queue是自動選擇生產(chǎn)線的thread)凄杯。因此對任務(wù)的處理排列組合有四種情況

序號 任務(wù)處理排列組合
1 同步+串行隊列
2 異步+串行隊列
3 同步+并發(fā)隊列
4 異步+并發(fā)隊列
(1)任務(wù)處理 同步 + 串行隊列的線程選擇
void syncSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 創(chuàng)建串行隊列 */
    dispatch_queue_t serialQueue1 = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);

    /* 2. 將任務(wù)放到隊列中 */
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task1 exe thread--------%@",[NSThread currentThread]);
    });

    dispatch_sync(serialQueue2, ^{
        NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue2, ^{
        NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task3 exe thread--------%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果

2019-08-28 22:14:00.994993+0800 任務(wù)[797:36742] current thread <NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995158+0800 任務(wù)[797:36742] serialQueue1 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995276+0800 任務(wù)[797:36742] serialQueue2 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995374+0800 任務(wù)[797:36742] serialQueue1 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995473+0800 任務(wù)[797:36742] serialQueue2 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995563+0800 任務(wù)[797:36742] serialQueue1 task3 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}

同步 + 串行隊列

同步執(zhí)行 + 串行隊列 可以看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的错洁,并沒有開啟新的線程(同步執(zhí)行 不具備開啟新線程的能力)。
  • 所有任務(wù)都是依次打印的 (同步任務(wù) 需要等待隊列的任務(wù)執(zhí)行結(jié)束)戒突。
  • 在當(dāng)前線程中將隊列的任務(wù)按照加入的順序依次執(zhí)行完畢屯碴,即任務(wù)是按順序執(zhí)行的(串行隊列 每次只有一個任務(wù)被執(zhí)行,任務(wù)一個接一個按順序執(zhí)行)膊存。

串行隊列中的特殊隊列导而,主隊列MainQueue隔崎,那么同步 + 主隊列對任務(wù)的處理情況會是什么樣的呢今艺?

void syncMainQueue() {
    NSLog(@"------start-------");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    NSLog(@"-------end---------");
}

執(zhí)行結(jié)果

同步 + 主隊列

直接崩潰了, 以前這種情況是會發(fā)生死鎖的,現(xiàn)在直接報錯爵卒。 那么, 為什么會發(fā)生這種情況呢虚缎?這里需要探討下,什么時候線程和queue綁定在一起了钓株。

void syncMoreSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    //1.創(chuàng)建串行隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    //2.將任務(wù)放到隊列里
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
        dispatch_sync(serialQueue, ^{
            NSLog(@"serialQueue task2 exe thread-%@",[NSThread currentThread]);
        });
    });
}

執(zhí)行結(jié)果:
2019-09-02 11:06:34.575166+0800 任務(wù)[4420:131455] current thread <NSThread: 0x6000038348c0>{number = 1, name = main}
2019-09-02 11:06:34.575421+0800 任務(wù)[4420:131455] serialQueue task1 exe thread-<NSThread: 0x6000038348c0>{number = 1, name = main}

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

同樣發(fā)生了崩潰实牡。
我們發(fā)現(xiàn)在主線程中調(diào)用同步sync調(diào)用串行隊列serialQueue ,沒有發(fā)生崩潰轴合。而在串行隊列的task中再次調(diào)用 同步sync調(diào)用串行隊列serialQueue執(zhí)行任務(wù)發(fā)生崩潰了创坞,但是串行隊列的task執(zhí)行是在主線程中的。為什么第一次調(diào)用主隊列調(diào)用task沒問題值桩,而第二次執(zhí)行執(zhí)行task發(fā)生崩潰了呢摆霉?

syncMoreSerial

我們發(fā)現(xiàn)在執(zhí)行task1 的時候,在serierQueue中添加了task2奔坟,同時要求執(zhí)行task2携栋,這個時候,task1還沒有完成咳秉,這違背了串行隊列的含義了婉支,F(xiàn)IFO,崩潰了。

主線程也是同樣的道理澜建,可以這樣理解, 上圖中, 執(zhí)行syncMainQueue這個方法是在主線程中執(zhí)行的, 你可以把它看做一個任務(wù)A, 這個任務(wù)A也是在主隊列中的,那么代碼執(zhí)行到第一個dispatch_sync的時候, 啟動了任務(wù)B, 把任務(wù)B放進了主隊列中, 由于是同步執(zhí)行, 所以, 必須等待任務(wù)B執(zhí)行完了之后才能繼續(xù)向下執(zhí)行, 但是主線程有任務(wù)A, 所以任務(wù)B無法放到主線程中去執(zhí)行,任務(wù)B等待任務(wù)A執(zhí)行, 任務(wù)A等待任務(wù)B執(zhí)行, 這樣就造成了死鎖向挖。

同步 + 主隊列 = 死鎖

我們再來看個例子:

void syncMoreSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    //1.創(chuàng)建串行隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue1", DISPATCH_QUEUE_SERIAL);
    //2.將任務(wù)放到隊列里
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
        dispatch_sync(serialQueue1, ^{
            NSLog(@"serialQueue1 task1 exe thread-%@",[NSThread currentThread]);
        });
    });
}

執(zhí)行結(jié)果:
2019-09-02 11:13:56.558896+0800 任務(wù)[4486:142175] current thread <NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559102+0800 任務(wù)[4486:142175] serialQueue task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559211+0800 任務(wù)[4486:142175] serialQueue1 task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}

上面這個實例和前面的syncMoreSerial實例唯一的區(qū)別就是serialQueue task1和serialQueue task2分別在不同隊列中,
這樣互不影響炕舵,就不會出現(xiàn)崩潰死鎖現(xiàn)象何之,這讓我們更好理解同步+串行隊列的內(nèi)容。

(2)任務(wù)處理 異步+串行隊列的線程選擇
void asyncSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 創(chuàng)建串行隊列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
    
    /* 2. 將任務(wù)放到隊列中 */
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    
    dispatch_async(serialQueue2, ^{
        NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue2, ^{
        NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:
2019-09-02 12:56:21.371933+0800 任務(wù)[5177:205067] current thread <NSThread: 0x600002c35380>{number = 1, name = main}
2019-09-02 12:56:21.372169+0800 任務(wù)[5177:205143] serialQueue task1 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372197+0800 任務(wù)[5177:205145] serialQueue2 task1 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372291+0800 任務(wù)[5177:205143] serialQueue task2 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372326+0800 任務(wù)[5177:205145] serialQueue2 task2 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372513+0800 任務(wù)[5177:205143] serialQueue task3 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}

由上可以看出異步執(zhí)行 + 串行隊列中串行隊列的thread不是主線程了咽筋,而是一條新開辟的線程溶推,但是不管任務(wù)有多少個任務(wù),異步執(zhí)行 + 串行隊列(同一條串行隊列只開啟一條新的線程),任務(wù)的執(zhí)行順序也是按照隊列中的順序執(zhí)行的,因為同一條線程中蒜危,必須等到前一個任務(wù)執(zhí)行完畢后虱痕,才能執(zhí)行下一個任務(wù)。但是串行隊列對應(yīng)的任務(wù)是可能在多條線程中執(zhí)行辐赞,多個串行隊列的開辟的線程之間是可以共享使用的部翘。

異步執(zhí)行 + 串行隊列 可以看到:

  • 開啟了一條新線程(異步執(zhí)行 具備開啟新線程的能力,串行隊列 只開啟一個線程)响委。
  • 所有任務(wù)是在打印的 syncConcurrent---beginsyncConcurrent---end 之后才開始執(zhí)行的(異步執(zhí)行 不會做任何等待新思,可以繼續(xù)執(zhí)行任務(wù))。
  • 任務(wù)是按順序執(zhí)行的(串行隊列 每次只有一個任務(wù)被執(zhí)行晃酒,任務(wù)一個接一個按順序執(zhí)行)表牢。

串行隊列中的特殊隊列,主隊列MainQueue贝次,那么異步 + 主隊列對任務(wù)的處理情況會是什么樣的呢崔兴?即主隊列在主線程中執(zhí)行異步什么情況呢?

void asyncMainQueue() {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download2------%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download3------%@",[NSThread currentThread]);
    });
}

運行結(jié)果:
2019-09-02 13:21:20.124919+0800 任務(wù)[5355:235069] download1------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125129+0800 任務(wù)[5355:235069] download2------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125332+0800 任務(wù)[5355:235069] download3------<NSThread: 0x6000030ff780>{number = 1, name = main}

異步執(zhí)行雖然有開啟新線程的能力, 但是異步執(zhí)行 + 主隊列并不會開啟新的線程, 任務(wù)都是在主線程中執(zhí)行的蛔翅。

(3)任務(wù)處理 同步+并發(fā)隊列的線程選擇
void syncConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 創(chuàng)建一條并發(fā)隊列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
    /* 2. 把任務(wù)放到隊列中 */
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:
2019-09-02 16:10:13.360714+0800 任務(wù)[11194:422301] current thread <NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.360871+0800 任務(wù)[11194:422301] concurrentQueue task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361007+0800 任務(wù)[11194:422301] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361111+0800 任務(wù)[11194:422301] concurrentQueue task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361213+0800 任務(wù)[11194:422301] concurrentQueue2 task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361308+0800 任務(wù)[11194:422301] concurrentQueue task3 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}

同步執(zhí)行 + 并發(fā)隊列中可看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中按照順序依次執(zhí)行的敲茄,沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)。
  • 所有任務(wù)都是依次打印的(同步任務(wù) 需要等待隊列的任務(wù)執(zhí)行結(jié)束)山析。
  • 任務(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í)行洁灵。
(4)任務(wù)處理 異步+并發(fā)隊列的線程選擇
void asyncConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 創(chuàng)建一條并發(fā)隊列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
    /* 2. 把任務(wù)放到隊列中 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:
2019-09-02 16:34:33.406510+0800 任務(wù)[11384:446153] current thread <NSThread: 0x600003991400>{number = 1, name = main}
2019-09-02 16:34:33.406761+0800 任務(wù)[11384:446280] concurrentQueue2 task1 exe thread--------<NSThread: 0x6000039cbc40>{number = 4, name = (null)}
2019-09-02 16:34:33.406763+0800 任務(wù)[11384:446276] concurrentQueue task1 exe thread--------<NSThread: 0x6000039f4740>{number = 3, name = (null)}
2019-09-02 16:34:33.406794+0800 任務(wù)[11384:446277] concurrentQueue task2 exe thread--------<NSThread: 0x6000039cbcc0>{number = 5, name = (null)}
2019-09-02 16:34:33.406891+0800 任務(wù)[11384:446279] concurrentQueue task3 exe thread--------<NSThread: 0x6000039cbd40>{number = 7, name = (null)}
2019-09-02 16:34:33.406803+0800 任務(wù)[11384:446278] concurrentQueue2 task2 exe thread--------<NSThread: 0x6000039f4780>{number = 6, name = (null)}

并發(fā)隊列concurrentQueue分別執(zhí)行在了三條線程上。并發(fā)隊列concurrentQueue2分別執(zhí)行在兩條線程上掺出。并發(fā)隊列創(chuàng)建的線程之間是可以共享使用的徽千,看如下執(zhí)行結(jié)果:

執(zhí)行結(jié)果:
2019-09-02 16:38:04.019812+0800 任務(wù)[11421:451122] current thread <NSThread: 0x600000042940>{number = 1, name = main}
2019-09-02 16:38:04.020140+0800 任務(wù)[11421:451244] concurrentQueue task2 exe thread--------<NSThread: 0x600000026680>{number = 6, name = (null)}
2019-09-02 16:38:04.020163+0800 任務(wù)[11421:451242] concurrentQueue2 task2 exe thread--------<NSThread: 0x600000026600>{number = 5, name = (null)}
2019-09-02 16:38:04.020171+0800 任務(wù)[11421:451241] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}
2019-09-02 16:38:04.020201+0800 任務(wù)[11421:451243] concurrentQueue task1 exe thread--------<NSThread: 0x6000000265c0>{number = 3, name = (null)}
2019-09-02 16:38:04.020467+0800 任務(wù)[11421:451241] concurrentQueue task3 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}

這里線程4被復(fù)用了。這是因為線程4任務(wù)已經(jīng)執(zhí)行完畢concurrentQueue2 task1的任務(wù)空閑下來了汤锨。隊列會先利用空閑下來的線程而不是去創(chuàng)建双抽。當(dāng)然系統(tǒng)創(chuàng)建線程也是有限制的,那么系統(tǒng)做多會創(chuàng)建多少條線程呢闲礼?

void asyncMoreConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 創(chuàng)建一條并發(fā)隊列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i =0; i<1000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSLog(@"%d concurrentQueue task1 exe thread--------%@",i,[NSThread currentThread]);
            sleep(100);
        });
    }
}

執(zhí)行結(jié)果:

2019-09-02 16:47:50.451507+0800 任務(wù)[11511:464499] 58 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c00>{number = 61, name = (null)}
2019-09-02 16:47:50.451939+0800 任務(wù)[11511:464501] 60 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94200>{number = 63, name = (null)}
2019-09-02 16:47:50.451583+0800 任務(wù)[11511:464500] 59 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c80>{number = 62, name = (null)}
2019-09-02 16:47:50.451940+0800 任務(wù)[11511:464502] 61 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94280>{number = 64, name = (null)}
2019-09-02 16:47:50.452695+0800 任務(wù)[11511:464503] 62 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94340>{number = 66, name = (null)}
2019-09-02 16:47:50.452649+0800 任務(wù)[11511:464504] 63 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94380>{number = 65, name = (null)}

異步執(zhí)行 + 并發(fā)隊列 中可以看出:

  • 除了當(dāng)前線程(主線程)牍汹,系統(tǒng)又開啟了 3 個線程琅翻,并且任務(wù)是交替/同時執(zhí)行的。(異步執(zhí)行 具備開啟新線程的能力柑贞。且 并發(fā)隊列 可開啟多個線程,同時執(zhí)行多個任務(wù))聂抢。
  • 所有任務(wù)是在打印的 syncConcurrent---beginsyncConcurrent---end 之后才執(zhí)行的钧嘶。說明當(dāng)前線程沒有等待,而是直接開啟了新線程琳疏,在新線程中執(zhí)行任務(wù)(異步執(zhí)行 不做等待有决,可以繼續(xù)執(zhí)行任務(wù))。

執(zhí)行方式匯總

匯總圖

最后還有一個系統(tǒng)以及為我們創(chuàng)建好的并發(fā)隊列空盼,全局并發(fā)隊列书幕,首先, 它是一個并發(fā)隊列, 他是系統(tǒng)為我們創(chuàng)建好的一個全局的并發(fā)隊列, 所以, 有時候, 我們不需要自己創(chuàng)建一個并發(fā)隊列, 直接用系統(tǒng)為我們提供的全局隊列就可以了,所以全局隊列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊列是一樣的。

本文參考于 http://www.reibang.com/p/8a6141c893d9, 如有侵權(quán)揽趾,請原著與我溝通台汇,可以立即取消發(fā)布。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篱瞎,一起剝皮案震驚了整個濱河市苟呐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俐筋,老刑警劉巖牵素,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澄者,居然都是意外死亡笆呆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門粱挡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赠幕,“玉大人,你說我怎么就攤上這事抱怔×臃唬” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵屈留,是天一觀的道長局冰。 經(jīng)常有香客問我,道長灌危,這世上最難降的妖魔是什么康二? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮勇蝙,結(jié)果婚禮上沫勿,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好产雹,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布诫惭。 她就那樣靜靜地躺著,像睡著了一般蔓挖。 火紅的嫁衣襯著肌膚如雪夕土。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天瘟判,我揣著相機與錄音怨绣,去河邊找鬼。 笑死拷获,一個胖子當(dāng)著我的面吹牛篮撑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匆瓜,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赢笨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驮吱?” 一聲冷哼從身側(cè)響起质欲,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糠馆,沒想到半個月后嘶伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡又碌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年九昧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毕匀。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡铸鹰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出皂岔,到底是詐尸還是另有隱情蹋笼,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布躁垛,位于F島的核電站剖毯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏教馆。R本人自食惡果不足惜逊谋,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望土铺。 院中可真熱鬧胶滋,春花似錦板鬓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至部宿,卻和暖如春唤蔗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窟赏。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箱季,地道東北人涯穷。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像藏雏,于是被迫代替她去往敵國和親拷况。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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