這篇文章是上一篇博客的補充,旨在把沒有講清楚的「用戶級線程和內核級線程」補充完整。希望讀者能對線程有更進一步的了解。
小白最近在學習多線程編程。
網上關於多線程的資料很多,小白很快就把線程的基本概念弄懂了,但關於「用戶級線程和內核級線程」的概念,她卻怎么也搞不清楚,只好向操作系統基礎扎實的小明請教。
對於小白的問題,小明總會耐心解答:“線程里面這兩個概念確實比較難理解,我先給你講用戶級線程吧。”
用戶級線程
“既然你說你已經看過線程的基本概念,那我就直接跳過這一部分了。
很久很久之前,線程的概念是出現了,但操作系統廠商可不能直接就去修改操作系統的內核,因為對他們來說,穩定性是最重要的。貿然把未經驗證的東西加入內核,出問題了怎么辦?所以想要驗證線程的可用性,得另想辦法。”
“我知道我知道,那些研究人員就編寫了一個關於線程的函數庫,用函數庫來實現線程!”小白得意的說:“這個我剛剛在網上看到了。”
“是的,他們把創建線程、終止線程等功能放在了這個線程庫內,用戶就可以通過調用這些函數來實現所需要的功能。”小明找了張紙,寫上了幾個函數:pthread_creat
,pthread_exit
,pthread_join
,pthread_yield
,接着說:“這是幾個重要的函數,你應該能大概猜出這些函數的功能吧?”
“emmmm,讓我想想,pthread_creat
是創建一個新線程,pthread_exit
是結束線程,pthread_join
嘛,我猜是准備運行某一個線程,然后把它加進就緒隊列。最后一個函數我就不知道了。”
“不知道也沒關系,一會你就清楚了。”小明接着講:“要知道,剛剛我們說的線程庫,是位於用戶空間的,操作系統內核對這個庫一無所知,所以從內核的角度看,它還是按正常的方式管理。”
小白問道:“也就是說操作系統眼里還是只有進程嘍?那我用線程庫寫的一個多線程進程,只能一次在一個 CPU 核心上運行?”
小明點點頭,說:“你說的沒錯,這其實是用戶級線程的一個缺點,這些線程只能占用一個核,所以做不到並行加速,而且由於用戶線程的透明性,操作系統是不能主動切換線程的,換句話講,如果 A,B 是同一個進程的兩個線程的話, A 正在運行的時候,線程 B 想要運行的話,只能等待 A 主動放棄 CPU,也就是主動調用 pthread_yield
函數。”
tobe 注:對操作系統來說,用戶級線程具有不可見性,也稱透明性。
“停一下,讓我想一想,”小白飛速思考着小明的話,“是不是說,即使有線程庫,用戶級線程也做不到像進程那樣的輪轉調度?”
“非常正確!看來你對進程的概念很清楚嘛。不過呢,雖然不能做到輪轉調度,但用戶級線程也有他自己的好處——你可以為你的應用程序定制調度算法,畢竟什么時候退出線程你自己說了算。剛剛說了,因為操作系統只能看到進程的存在,那如果某一個線程阻塞了,你覺得會發生什么?”
“在操作系統眼里,是進程阻塞了,那么整個進程就會進入阻塞態,在阻塞操作結束前,這個進程都無法得到 CPU 資源。那就相當於,所有的線程都被阻塞了。”小白得意的回答。
“沒錯,所以如果任由線程進行阻塞操作,進程的效率將受到很大的影響,所以在這個過程中,出現了一個替代方案——jacket。所謂 jacket,就是把一個產生阻塞的系統調用轉化成一個非阻塞的系統調用。”
小白驚訝地問:“這怎么做得到?該阻塞的調用,還能變得不阻塞?”
小明答道:“我來舉個例子吧,不是直接調用一個系統 I/O 例程,而是調用一個應用級別的 I/O jacket 例程,這個 jacket 例程中的代碼會檢查並且確定 I/O 設備是不是正忙,如果忙的話,就在用戶態下將該線程阻塞,然后把控制權交給另一個線程。隔一段時間后再次檢查 I/O 設備。就像你說的,最后還是會執行阻塞調用,但使用 jacket 可以縮短被阻塞的時間。不過有些情況下是可以不被阻塞的,取決於具體的實現。”
小明停頓了一會,說:“用戶級線程的概念大概就這么多,我們接下來講內核級線程吧。”
內核級線程
“有了用戶級線程的鋪墊,內核級線程就好講多了。現在我們知道,許多操作系統都已經支持內核級線程了。為了實現線程,內核里就需要有用來記錄系統里所有線程的線程表。當需要創建一個新線程的時候,就需要進行一個系統調用,然后由操作系統進行線程表的更新。當然了,傳統的進程表也還是有的。你想想看,如果操作系統「看得見」線程,有什么好處?“
小白自信的回答:“操作系統內核如果知道線程的存在,就可以像調度多個進程一樣,把這些線程放在好幾個 CPU 核心上,就能做到實際上的並行了。”
“還有一點你沒有說到,如果線程可見,那么假如線程 A 阻塞了,與他同屬一個進程的線程也不會被阻塞。這是內核級線程的絕對優勢。”
“那內核級線程就沒有什么缺點嗎?”
“缺點當然是有的,你想想看,讓操作系統進行線程調度,那意味着每次切換線程,就需要「陷入」內核態,而操作系統從用戶態到內核態的轉變是有開銷的,所以說內核級線程切換的代價要比用戶級線程大。還有很重要的一點——線程表是存放在操作系統固定的表格空間或者堆棧空間里,所以內核級線程的數量是有限的,擴展性比不上用戶級線程。”
"內核級線程就這么點東西,我最后給你留一張圖,你要是能看得懂,就說明你理解今天的概念了。"
小白得意地說:“我當然看得懂了,謝謝小明!”
希望你在看完我的文章之后有所收獲。
感謝你的閱讀,我們后會有期!
聲明:原創文章,未經授權,禁止轉載