前言
理解進程和線程的概念,對于網(wǎng)絡(luò)運維或者編寫程序都是十分重要的。無論是windowx平臺或者linux平臺州丹,甚至包括手機,如果你想要運行任何一個程序蒜哀,都和進程罗心、線程脫離不了干系。
概念
我們可以把一個程序理解為一個進程含思,這里我們拿谷歌瀏覽器舉例程序舉例崎弃,當(dāng)你運行谷哥瀏覽器程序后,在你的任務(wù)管理器里面就會出現(xiàn)該程序的進程含潘。
從上圖可以很清楚的看到系統(tǒng)為谷歌瀏覽器(Google Chrome)創(chuàng)建了1個進程饲做,并且該進程創(chuàng)建了34個線程,這里就出現(xiàn)了一個進程與線程之間關(guān)系的概念遏弱。一個進程可以擁有多個線程盆均,就像你只有2只手,但你有10個手指頭腾窝。
進程是大爺
進程實際上是不做事的缀踪,而是吩咐別人做事,這個“別人”當(dāng)然只有線程這位苦力了虹脯,當(dāng)一個程序(進程)想要做一件事驴娃,比如刷新網(wǎng)頁,他就會開啟一個線程循集,讓這個線程去做這件事唇敞。
多線程
前面提過,一個進程可以有多個線程咒彤。有一點要注意疆柔,一個進程至少要有一個線程。這也很好理解镶柱,沒有線程就沒有人“干活”了旷档。
還是以瀏覽器舉例,假設(shè)我想一邊聽歌一邊寫博客歇拆,那么我必須打開2個網(wǎng)頁鞋屈,一個用來聽歌,一個用來寫作故觅。這時候瀏覽器有2種方案處理厂庇,一種是只開一個線程,先執(zhí)行聽歌的任務(wù)输吏,等歌播放任務(wù)結(jié)束后权旷,再讓線程執(zhí)行寫作任務(wù)。這就是單線程串行執(zhí)行贯溅。
不過如果這種方式執(zhí)行拄氯,我估計寫博客的想砸電腦躲查。另一種就是多線程方案。瀏覽器開啟2個線程坤邪,1個線程負責(zé)播放音樂熙含,另1個線程負責(zé)打開博客供用戶寫作。相當(dāng)于并行處理兩個任務(wù)艇纺,但這里的并行有時候并不是真正的并行怎静,而是將CPU的的時間進行分片,以1秒時間為例黔衡,CPU將這1秒時間進行分片蚓聘,每一片的單位是0.001秒,兩個線程輪番占用CPU的時間盟劫,最終在2個線程優(yōu)先級相同的情況下夜牡,會各占用CPU 0.5秒的時間。但因為這個切換太快了侣签,用戶是根本感覺不到的塘装。但這種機制并不是真正的并行。
線程優(yōu)先級
線程是有優(yōu)先級的影所,優(yōu)先級越高蹦肴,它能獲取的CPU處理時間越多,反之亦然猴娩。我們用程序在測試線程的優(yōu)先級.
#以O(shè)bjective C為程序示例
#創(chuàng)建了2個線程阴幌,一個要執(zhí)行doMusic任務(wù),一個執(zhí)行doBlog任務(wù)
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doMusic) object:nil];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(doBlog) object:nil];
#設(shè)備線程的名稱卷中,方便區(qū)別
thread1.name = @"播放音樂";
thread2.name = @"博客寫作";
#重點矛双,設(shè)置線程的優(yōu)先級,1為最高
thread1.threadPriority = 1;
thread2.threadPriority = 0.5;
#讓線程開始執(zhí)行
[thread1 start];
[thread2 start];
#播放音樂執(zhí)行的任務(wù)
- (void) doMusic {
for (int i=0; i < 10; i++) {
#輸入當(dāng)前線程的名稱和循環(huán)數(shù)i的值
NSLog(@"%@---%zd",[NSThread currentThread].name,i);
}
}
#博客寫作執(zhí)行的任務(wù)
- (void) doBlog {
for (int i=0; i < 10; i++) {
NSLog(@"%@---%zd",[NSThread currentThread].name,i);
}
}
以下是程序運行結(jié)果
我們可以觀察到2件事蟆豫,第一议忽,兩條線程的確是交叉執(zhí)行的,和我們前面說的時間分片一致十减;第二徙瓶,優(yōu)先級較高的“播放音樂”線程很快就執(zhí)行完了10次循環(huán),也就是說在同一個時間段內(nèi)嫉称,“播放音樂”線程得到了更多的CPU時間資源,這就是優(yōu)先級的作用灵疮。
線程的生命周期
此節(jié)织阅,我們來觀察一個線程從生到死的過程,是不是覺得有點殘忍震捣?
新建狀態(tài)
每個線程被創(chuàng)建時都會經(jīng)歷的狀態(tài)荔棉,這個時候的線程是不會被CPU調(diào)度的闹炉;
可運行狀態(tài)
當(dāng)線程被允許執(zhí)行(由程序操控)時,就會進入可運行狀態(tài)(runnable)润樱,此時該線程會被放入CPU資源調(diào)度池(Pool)中渣触,由CPU根據(jù)線程的優(yōu)行級決定何時執(zhí)行。
運行狀態(tài)
線程被CPU調(diào)度時壹若,就處于運行狀態(tài)嗅钻,此時線程開始占用CPU處理時間。
阻塞狀態(tài)
線程因CPU調(diào)度或者程序內(nèi)部控制等原因(比如等待用戶輸入)將運行狀態(tài)改變?yōu)樽枞麪顟B(tài)(block),此狀態(tài)下店展,線程不再占用CPU時間养篓,但線程并沒有被釋放,而是等待某種條件達成后可再次轉(zhuǎn)換為可運行狀態(tài)赂蕴,注意陰塞狀態(tài)是不能直接轉(zhuǎn)換為運行狀態(tài)的柳弄。
釋放狀態(tài)
就是線程的所有工作都執(zhí)行完畢,就像上面舉例中的10次循環(huán)結(jié)束概说,此時線程迎來了它的終點碧注,這也是每一個線程的終點,在他們干完自己的工作后糖赔,就會被釋放掉(dead)萍丐,這是為了回收該線程所占用的系統(tǒng)資源。
多線程安全問題
如果兩個線程訪問同一個資源的時候挂捻,可能會出現(xiàn)“安全”問題碉纺。
我們還是看圖說話,圖有有2個線程刻撒,A線程和B線程骨田,他們是并發(fā)執(zhí)行的關(guān)系,有一個整型變量声怔,它的初始值是17态贤。首先A線程進行了讀取變量的操作,讀取的值是17,B線程在之后也讀取了該變量醋火,讀取值為17.A線程將該值+1,然后寫回給變量悠汽,此時變量的值變?yōu)?8.那么問題來了,此時B線程在之前也是讀取的17,B線程也給變量加+1,然后再寫回芥驳,這樣本來變量被加了2次柿冲,正確值應(yīng)該是19,但最后結(jié)果是18.
你可能覺得這并不嚴重?
把17想像成你的銀行存款兆旬,假設(shè)你有17萬元存款假抄,你的家人A往你帳戶存了1萬,同一時間你的家人B在另一個地方往你帳戶也了一萬,如果他們是按這種先后時間進行的宿饱,恭喜你熏瞄,你的銀行存款就只有18萬,而不是19萬谬以,知道問題的嚴重性了吧强饮?
解決方法
解決的方法就是“加鎖”,當(dāng)A線程準(zhǔn)備讀取數(shù)據(jù)時为黎,就對要讀取的變量加鎖邮丰,讓其它線程暫無法訪問。待A進程操作完畢寫入數(shù)據(jù)后再對該變量進行解鎖操作碍舍,接著B線程再對數(shù)據(jù)進行讀寫操作柠座。這樣數(shù)據(jù)我們就認為“安全”了。但代價是B線程有2段時間處于“閑置”狀態(tài)片橡。
最后
理解了進程和線程的概念妈经,我們才能夠在日常的運維工作中深入的追究問題;也只有掌握了他們的概念捧书,我們在編寫代碼時吹泡,寫出效率與性能兼顧的好程序。你已經(jīng)掌握他們的概念了嗎经瓷?