Python3中的線程,GIL,線程安全(上)
假期看了一篇關于 Python3 線程的文章,感覺非常棒,特意分享給大家。說它好在于完完全全解答了我的很多疑問,以一種更高階的方式解讀 Python3 中的線程,尤其現在大家都在說異步編程,很容易在線程和異步之間做比較,而這篇文章無疑解釋的很好。
另外讀完這篇文章也在想,為什么大家有這么多錯誤的理解?難道深入一門語言一定要研究語言的源碼嗎?我們如何才能更加高效的掌握一個技能?
這篇文章從三部分解釋了線程,分別是原理、實踐、解疑,為了把事情說清楚,我分兩篇文章說明,關于代碼實踐部分在下一篇介紹。
什么是線程?
在過去,線程認為是一種輕量級的任務,比如主程序在后臺啟動五個線程,在需要的時候可以將五個線程運行的結果匯總在一起。
當啟動python程序的時候,會產生一個包含主線程的新進程,如果你需要,可以并行運行多個任務(即線程)。
一個常見的例子就是主線程等待網絡連接,然后將接收到的連接交給其他線程去處理。
線程由操作系統管理,在大多數情況下,程序調用 pthreads C 庫讓操作系統來創建新進程,CPython 也是如此工作的。
可怕的線程
線程非常強大但也很難使用:因為它們共享相同的內存空間。
正因為共享,所以線程非常的輕量,產生一個線程僅僅額外需要一點點內存,內核有足夠的內存處理線程棧。
這也意味著同個進程的每個線程能夠互相訪問其他線程。比如一個線程處理一個網絡套接字,并不能保證僅僅由它來處理,在同一時刻,其他線程也能處理該套接字,比如修改、關閉、銷毀。
很多和線程打交道的程序員都有一個共識:使用線程編寫代碼很難。
線程安全
線程編碼很困難,但已經存在很長時間了,很多程序員傾向使用它,所以必須盡可能找到一種方式和它共存。
一些優秀的開發者發明了很多可以和線程良好共存的可重用的工具和庫,這些 API 可以避免線程陷阱,這些 API 是線程安全的。
在 Python 中,線程安全通常通過避免共享可變狀態來實現,重點就是避免修改線程共享的數據。
當然完全避免共享可變狀態是不可能的,線程在協作的時候必須:
所有的線程可以讀,完全沒有線程在寫入數據。
一個單獨的線程在寫,沒有其他線程在讀。
如果要理解線程,這兩點必須要記住。
Rust在編譯代碼的時候能夠保證代碼是線程安全的,如果你經常編寫與線程有關的代碼,可以采用它。
使用 Python 編寫線程代碼的難點在于你無法證明代碼是線程安全的,就算有單元測試、靜態分析器也沒有用,唯一要做的就是小心編寫代碼。
CPython,GIL,原子操作
網絡上有很多對 Python 線程的誤解,最糟糕也是最流行的錯誤觀點是“Python不能使用線程”,如果你不使用線程,那么你就不會犯錯,這是你不使用 Python 線程的原因嗎?
另外一種沒有危險的說法:由于 GIL 的存在,能夠保證 Python 線程編碼是線程安全的。
GIL 是由 CPython 實現的,GIL 能夠避免 Python 內部出現和線程安全有關的一些錯誤(并不是為了線程安全而產生的)。
1:Python 絕對能使用線程,不要有絲毫疑問
CPython 使用操作系統原生的線程,GIL 能夠確保同一時刻僅有一個線程執行 Python 字節碼,潛臺詞是 CPython 無法有效使用多核。
這個特性讓 Python 很適合有大量 IO 操作的任務(這些任務不依賴于多核 CPU)。
如果是 CPU 密集操作,即使你有多核,使用 CPython 多線程編碼是很糟糕的,如果要發揮多核的能力,為規避 CPython GIL 帶來的問題,有以下兩種解決方案:
用原生 C 代碼編寫,某些庫(比如 Numpy)不會有 GIL 帶來的問題。
用多進程代替多線程。
2:GIL 能讓 Python 代碼線程安全嗎?
不會,GIL 不會讓編寫的 Python 代碼線程安全,但會保證某些 Python 基本操作原子性。其他一些 Python 實現,比如 Pypy 不會有相同的保證。
重要的是,GIL 并不完全保證 Python 的基本操作是原子性的,帶來的影響就是很難校驗代碼是線程安全的。
一些常見的問題
1:線程安全的代價
如果你編寫多線程代碼,額外的付出就是內存的消耗,具體消耗依賴很多因素,每個線程至少消耗 32KB。
當操作系統決定切換線程的時候,上下文切換也是要付出 CPU 時間的,和其他 Python 操作相比,這些消耗可以忽略不計。一個相對較新的操作系統很容易同時處理1萬個線程。
線程需要有很多的開發成本,多線程程序開發比單線程程序開發有更多的復雜性。
2:多線程還是異步IO
關于這個問題很容易迷失,有很多異步IO庫,但 Python 生態系統仍然在尋找社區都滿意的異步 IO 庫,這些第三方庫多多少少存在一些問題。
換句話說,第三方庫視同步的線程代碼為一等公民,如果你忘記異步 IO,會從這種開發模型中獲益。
對于開發人員來說也是如此,寫同步代碼是相對容易的,每個開發者都知道這一點,而想找到在有經驗的異步編碼的人則相對較難。
3:多線程有沒有落伍?
現在如果你關心一些流行的開發語言,很容易會這么想(比如我),比如 Nodejs 僅使用單線程運行時間循環;Go 語言對開發者隱藏了多線程,提供一種更簡單的接口進行并發編程。
但多線程仍然是很多任務首選的解決方案,比如文件系統的操作;Linux 并沒有提供異步 API;甚至很多重度依賴異步 IO 的軟件也會使用多線程,比如 Nginx 也會使用線程池避免異步操作變慢。
值得一提的是 CPU 的頻率增速已經逐步停止了,現在是多核的時代,要么使用多線程,要么使用多進程。
本文轉載自異步社區
Python 任務調度 網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。