Slepp(0)

微軟的msdn: A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If no other threads of equal priority are ready to run, the function returns immediately, and the thread continues execution.  

當調用Sleep(0)時,如果此時系統中存在和調用Sleep(0)的線程相同優先級的線程時,調用Sleep(0)的線程會放棄它剩餘的時間片,然後和它相同優先級的線程獲得時間片開始執行。如果此時系統中不存在和調用Sleep(0)的線程相同優先級的線程。當前線程繼續佔有時間片,Sleep(0)沒有什麼作用


假設現在是 2012-12-16 3:37:40,如果我調用一下 Thread.Sleep(1000) ,在 2012-12-16 3:37:41 的時候,這個線程會 不會被喚醒?
某人的代碼中用了一句看似莫明其妙的話:Sleep(0) 。既然是 Sleep 0 毫秒,那麼他跟去掉這句代碼相比,有啥區別麼?
我們先回顧一下操作系統原理。


操作系統中,CPU競爭有很多種策略。Unix系統使用的是時間片算法,而Windows則屬於搶佔式的。


在時間片算法中,所有的進程排成一個隊列。操作系統按照他們的順序,給每個進程分配一段時間,即該進程允許運行的時間。如果在 時間片結束時進程還在運行,則CPU將被剝奪並分配給另一個進程。如果進程在時間片結束前阻塞或結束,則CPU當即進行切換。調度程 序所要做的就是維護一張就緒進程列表,,當進程用完它的時間片後,它被移到隊列的末尾。


所謂搶佔式操作系統,就是說如果一個進程得到了 CPU 時間,除非它自己放棄使用 CPU ,否則將完全霸佔 CPU 。因此可以看出,在搶 佔式操作系統中,操作系統假設所有的進程都是“人品很好”的,會主動退出 CPU 。


在搶佔式操作系統中,假設有若干進程,操作系統會根據他們的優先級、飢餓時間(已經多長時間沒有使用過 CPU 了),給他們算出一 個總的優先級來。操作系統就會把 CPU 交給總優先級最高的這個進程。當進程執行完畢或者自己主動掛起後,操作系統就會重新計算一 次所有進程的總優先級,然後再挑一個優先級最高的把 CPU 控制權交給他。


我們用分蛋糕的場景來描述這兩種算法。假設有源源不斷的蛋糕(源源不斷的時間),一副刀叉(一個CPU),10個等待吃蛋糕的人(10 個進程)。


如果是 Unix操作系統來負責分蛋糕,那麼他會這樣定規矩:每個人上來吃 1 分鐘,時間到了換下一個。最後一個人吃完了就再從頭開始。於是,不管這10個人是不是優先級不同、飢餓程度不同、飯量不同,每個人上來的時候都可以吃 1 分鐘。當然,如果有人本來不太餓,或者飯量小,吃了30秒鐘之後就吃飽了,那麼他可以跟操作系統說:我已經吃飽了(掛起)。於是操作系統就會讓下一個人接着來。


如果是 Windows 操作系統來負責分蛋糕的,那麼場面就很有意思了。他會這樣定規矩:我會根據你們的優先級、飢餓程度去給你們每個人計算一個優先級。優先級最高的那個人,可以上來吃蛋糕——吃到你不想吃爲止。等這個人吃完了,我再重新根據優先級、飢餓程度來計算每個人的優先級,然後再分給優先級最高的那個人。


這樣看來,這個場面就有意思了——可能有些人是PPMM,因此具有高優先級,於是她就可以經常來吃蛋糕。可能另外一個人是個醜男,而去很ws,所以優先級特別低,於是好半天了才輪到他一次(因爲隨着時間的推移,他會越來越飢餓,因此算出來的總優先級就會越來越高,因此總有一天會輪到他的)。而且,如果一不小心讓一個大胖子得到了刀叉,因爲他飯量大,可能他會霸佔着蛋糕連續吃很久很久,導致旁邊的人在那裏咽口水。。。
而且,還可能會有這種情況出現:操作系統現在計算出來的結果,5號PPMM總優先級最高,而且高出別人一大截。因此就叫5號來吃蛋糕。5號吃了一小會兒,覺得沒那麼餓了,於是說“我不吃了”(掛起)。因此操作系統就會重新計算所有人的優先級。因爲5號剛剛吃過,因此她的飢餓程度變小了,於是總優先級變小了;而其他人因爲多等了一會兒,飢餓程度都變大了,所以總優先級也變大了。不過這時候仍然有可能5號的優先級比別的都高,只不過現在只比其他的高一點點——但她仍然是總優先級最高的啊。因此操作系統就會說:5號mm上來吃蛋糕……(5號mm心裏鬱悶,這不剛吃過嘛……人家要減肥……誰叫你長那麼漂亮,獲得了那麼高的優先級)。


那麼,Thread.Sleep 函數是幹嗎的呢?還用剛纔的分蛋糕的場景來描述。上面的場景裏面,5號MM在吃了一次蛋糕之後,覺得已經有8分飽了,她覺得在未來的半個小時之內都不想再來吃蛋糕了,那麼她就會跟操作系統說:在未來的半個小時之內不要再叫我上來吃蛋糕了。這樣,操作系統在隨後的半個小時裏面重新計算所有人總優先級的時候,就會忽略5號mm。Sleep函數就是幹這事的,他告訴操作系統“在未來的多少毫秒內我不參與CPU競爭”。


看完了 Sleep 的作用,我們再來想想文章開頭的兩個問題。


對於第一個問題,答案是:不一定。因爲你只是告訴操作系統:在未來的1000毫秒內我不想再參與到CPU競爭。那麼1000毫秒過去之後,這時候也許另外一個線程正在使用CPU,那麼這時候操作系統是不會重新分配CPU的,直到那個線程掛起或結束;況且,即使這個時候恰巧輪到操作系統進行CPU 分配,那麼當前線程也不一定就是總優先級最高的那個,CPU還是可能被其他線程搶佔去。


與此相似的,Thread有個Resume函數,是用來喚醒掛起的線程的。好像上面所說的一樣,這個函數只是“告訴操作系統我從現在起開始參與CPU競爭了”,這個函數的調用並不能馬上使得這個線程獲得CPU控制權。


對於第二個問題,答案是:有,而且區別很明顯。假設我們剛纔的分蛋糕場景裏面,有另外一個PPMM 7號,她的優先級也非常非常高(因爲非常非常漂亮),所以操作系統總是會叫道她來吃蛋糕。而且,7號也非常喜歡吃蛋糕,而且飯量也很大。不過,7號人品很好,她很善良,她沒吃幾口就會想:如果現在有別人比我更需要吃蛋糕,那麼我就讓給他。因此,她可以每吃幾口就跟操作系統說:我們來重新計算一下所有人的總優先級吧。不過,操作系統不接受這個建議——因爲操作系統不提供這個接口。於是7號mm就換了個說法:“在未來的0毫秒之內不要再叫我上來吃蛋糕了”。這個指令操作系統是接受的,於是此時操作系統就會重新計算大家的總優先級——注意這個時候是連7號一起計算的,因爲“0毫秒已經過去了”嘛。因此如果沒有比7號更需要吃蛋糕的人出現,那麼下一次7號還是會被叫上來吃蛋糕。


因此,Sleep(0)的作用,就是“觸發操作系統立刻重新進行一次CPU競爭”。競爭的結果也許是當前線程仍然獲得CPU控制權,也許會換成別的線程獲得CPU控制權。這也是我們在大循環裏面經常會寫一句Sleep(0) ,因爲這樣就給了其他線程比如Paint線程獲得CPU控制權的權力,這樣界面就不會假死在那裏。


另外,雖然上面提到說“除非它自己放棄使用 CPU ,否則將完全霸佔 CPU”,但這個行爲仍然是受到制約的——操作系統會監控你霸佔CPU的情況,如果發現某個線程長時間霸佔CPU,會強制使這個線程掛起,因此在實際上不會出現“一個線程一直霸佔着 CPU 不放”的情況。至於我們的大循環造成程序假死,並不是因爲這個線程一直在霸佔着CPU。實際上在這段時間操作系統已經進行過多次CPU競爭了,只不過其他線程在獲得CPU控制權之後很短時間內馬上就退出了,於是就又輪到了這個線程繼續執行循環,於是就又用了很久才被操作系統強制掛起。。。因此反應到界面上,看起來就好像這個線程一直在霸佔着CPU一樣。

發佈了22 篇原創文章 · 獲贊 5 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章