前言
這篇文章是關(guān)于協(xié)程隧膏、線程與線程池的總結(jié)。相信有不少人曾經(jīng)和我一樣辙芍,不理解他們之間的差異和使用場(chǎng)景等等,那么在這篇文章羹与,我嘗試使用圖解的方式對(duì)他們進(jìn)行一個(gè)總結(jié)和對(duì)比故硅,來(lái)了解他們的細(xì)節(jié)。
線程
首先是線程注簿,考慮有一個(gè)單核的CPU契吉、一個(gè)有很多任務(wù)(Task)的隊(duì)列和一個(gè)線程(Thread),線程從隊(duì)列拿到任務(wù)Task1并準(zhǔn)備執(zhí)行诡渴,當(dāng)CPU調(diào)度執(zhí)行該線程的時(shí)候捐晶,那么這個(gè)時(shí)候的執(zhí)行模型是這個(gè)樣子的。
正常而言妄辩,如果任務(wù)都是計(jì)算型的任務(wù)惑灵,線程Thread1會(huì)一直執(zhí)行下去,直到遇到操作系統(tǒng)調(diào)度眼耀,向線程Thread1發(fā)出中斷英支,讓線程把CPU讓出來(lái)以執(zhí)行別的線程。
但是哮伟,當(dāng)任務(wù)有可能造成線程阻塞時(shí)干花,比如磁盤(pán)IO、網(wǎng)絡(luò)IO楞黄、線程Sleep等等池凄,CPU可能就沒(méi)有耐心等待了,CPU會(huì)讓Thread1收拾好自己的包袱(State)(需要一定的時(shí)間)鬼廓,讓出CPU肿仑;然后讓別的線程進(jìn)來(lái)CPU運(yùn)行。
線程池
線程池是為了減少創(chuàng)建線程和銷(xiāo)毀線程時(shí)候的開(kāi)銷(xiāo)而發(fā)明的碎税,如果總是在需要使用線程的時(shí)候才創(chuàng)建線程尤慰,又總是在使用完線程之后銷(xiāo)毀線程,并且這樣的操作很多的話雷蹂,那么在線程新建和銷(xiāo)毀上所耗費(fèi)的時(shí)間將會(huì)不少伟端;因而提出將多個(gè)線程存放到一個(gè)容器里面,每次需要使用線程的時(shí)候萎河,將線程拿出來(lái)荔泳,當(dāng)線程使用完畢以后蕉饼,再講線程放回去,以此來(lái)節(jié)省創(chuàng)建和銷(xiāo)毀線程帶來(lái)的開(kāi)銷(xiāo)玛歌。
還是以上的條件昧港,使用線程池或單線程處理同樣的Task時(shí),所面臨的CPU的對(duì)待是一樣的支子,使用線程池與單線程的區(qū)別就在于创肥,處理隊(duì)列任務(wù)的線程是隨機(jī)的,而不是同一個(gè)線程值朋。
通常情況下叹侄,線程池都有多個(gè)線程,如果線程池只有一個(gè)線程昨登,那么情景就會(huì)退化到第一種情況趾代。
多線程
多線程是為了利用CPU的多核而存在的,在單核CPU使用多線程沒(méi)有意義丰辣。
所以假設(shè)條件做一些變更撒强,考慮有一個(gè)雙核的CPU、一個(gè)有很多任務(wù)(Task)的隊(duì)列和兩個(gè)線程(Thread)笙什,因?yàn)镃PU是雙核CPU飘哨,所以CPU能夠同時(shí)執(zhí)行兩個(gè)線程。當(dāng)兩個(gè)線程同時(shí)從任務(wù)隊(duì)列取任務(wù)執(zhí)行時(shí)琐凭,執(zhí)行模型如下芽隆。
由于CPU能夠同時(shí)執(zhí)行兩個(gè)線程,所以相比單線程時(shí)候的情況统屈,處理的任務(wù)的速度會(huì)快一些胚吁,但單個(gè)CPU核的處理速度是沒(méi)有變化的。
當(dāng)兩個(gè)線程其中一個(gè)線程遇到阻塞任務(wù)時(shí)愁憔,CPU會(huì)將阻塞任務(wù)線程調(diào)出CPU核囤采,讓其他線程使用。
異步(不考慮協(xié)程)
異步的意思就是惩淳,當(dāng)調(diào)用者發(fā)起一個(gè)異步調(diào)用時(shí),調(diào)用者不能立刻得到運(yùn)行的結(jié)果乓搬,而是在這個(gè)異步調(diào)用完成后思犁,發(fā)送消息通知調(diào)用者,或者由調(diào)用者主動(dòng)查詢異步調(diào)用的狀態(tài)以得到運(yùn)行的結(jié)果进肯。
其實(shí)以上使用隊(duì)列的方式就已經(jīng)是異步調(diào)用了激蹲,只不過(guò)在前面沒(méi)有強(qiáng)調(diào)異步的概念。調(diào)用者將回調(diào)函數(shù)保存在任務(wù)里面江掩,等線程把任務(wù)執(zhí)行完学辱,通過(guò)回調(diào)函數(shù)通知調(diào)用者乘瓤;或者調(diào)用者保存任務(wù)對(duì)象的指針或引用,在恰當(dāng)?shù)臅r(shí)機(jī)通過(guò)指針或引用策泣,訪問(wèn)這個(gè)任務(wù)的執(zhí)行狀態(tài)衙傀。
異步執(zhí)行任務(wù)的本質(zhì)還是線程,只要存在阻塞任務(wù)萨咕,線程依然會(huì)被阻塞也依然會(huì)被請(qǐng)出CPU统抬。
那為什么在高并發(fā)的情況下還是推薦異步編程呢?那是因?yàn)?strong>異步不會(huì)阻塞調(diào)用者的線程危队,調(diào)用者在發(fā)起一個(gè)任務(wù)的時(shí)候聪建,不需要同步的等待結(jié)果,而是可以同時(shí)去執(zhí)行別的邏輯茫陆,對(duì)于執(zhí)行異步任務(wù)的線程而言金麸,阻塞時(shí)依然存在的。
協(xié)程
協(xié)程簿盅,英文coroutines挥下,是一種比線程更加輕量級(jí)的存在。正如一個(gè)進(jìn)程可以擁有多個(gè)線程一樣挪鹏,一個(gè)線程也可以擁有多個(gè)協(xié)程见秽。協(xié)程是承載在線程之上的,所以協(xié)程和線程的關(guān)系看起來(lái)像這個(gè)樣子讨盒。
正常情況下解取,執(zhí)行的模型是這個(gè)樣子的。
當(dāng)線程運(yùn)行了阻塞任務(wù)的時(shí)候返顺,任務(wù)會(huì)主動(dòng)讓出線程禀苦,并且將下一個(gè)任務(wù)調(diào)度線程運(yùn)行,執(zhí)行的模型是這樣子的遂鹊。需要注意的是振乏,在這個(gè)時(shí)候,CPU里面正在運(yùn)行的線程沒(méi)有變秉扑,只是在線程里面執(zhí)行的任務(wù)切換了而已慧邮。當(dāng)Task1阻塞,Task1能夠主動(dòng)讓出線程舟陆,Task2能夠進(jìn)入線程執(zhí)行误澳,等Task1處理完阻塞了并且能夠繼續(xù)往下運(yùn)行,Task1能夠重新回到線程執(zhí)行秦躯;在調(diào)用者看來(lái)就好像Task1和Task2在同時(shí)運(yùn)行一樣(每個(gè)任務(wù)就好像在更小的一個(gè)執(zhí)行單元執(zhí)行——協(xié)程)忆谓,而且沒(méi)有CPU線程的切換,這種情況就是協(xié)程踱承。
相比CPU切換線程倡缠,在線程切換協(xié)程有幾個(gè)好處:
- 切換協(xié)程在用戶態(tài)進(jìn)行 哨免,無(wú)需系統(tǒng)調(diào)用,更快
- 調(diào)用者能夠自主控制協(xié)程切換昙沦,更自由
- 沒(méi)有鎖的概念琢唾,協(xié)程安全,不擔(dān)心死鎖等狀況桅滋,省去鎖的開(kāi)銷(xiāo)