玩转python(2)多线程的历史2
線程這個(gè)概念早在多核CPU出現(xiàn)之前就提出來了,單核時(shí)代的多線程主要是為了讓CPU盡量不處于空閑狀態(tài),使其計(jì)算能力始終能得到利用。但本質(zhì)上講,在任意時(shí)刻只有一個(gè)線程在執(zhí)行。
盡管任意時(shí)刻只有一個(gè)線程在執(zhí)行,但是依然有些問題需要解決,其中最重要的就是線程安全。這個(gè)問題的來源很簡單,我之前說過,CPU對指令是一條一條執(zhí)行的,不過要注意的是,高級語言中的一行代碼在匯編語言層面上,可能由多條匯編指令組成的。舉一個(gè)簡單的例子,在c語言中對某個(gè)變量做自增操作:
int a = 0; int add(){a += 1;return a; }不過變成匯編指令就不這么簡單了,其中實(shí)現(xiàn)自增的主要是這兩條指令(大部分指令省略):
movl a(%rip), %eaxaddl $1, %eax這里之所以在例子中使用全局變量,是因?yàn)榫€程安全和數(shù)據(jù)同步主要是針對全局變量這類可以共享的資源。上面的a(%rip)表示全局變量a的值,%eax是一個(gè)寄存器。第一條指令表示把a(bǔ)的值保存在eax寄存器中,第二條指令對eax中的值加上1。
我們來考慮一個(gè)情況:現(xiàn)在有兩個(gè)線程A,B,都要調(diào)用add()函數(shù),那么a的預(yù)期值應(yīng)該是2。如果線程A執(zhí)行完第一條指令后,發(fā)生了上下文切換,此時(shí)eax寄存器的值是a的初始值0,CPU去執(zhí)行線程B,線程B執(zhí)行完畢后,把1返回給a(%rip),再恢復(fù)執(zhí)行線程A,由于線程A不會(huì)再去執(zhí)行第一條指令,因此eax寄存器的值不會(huì)被更新,依舊是0,線程A執(zhí)行完畢后,把1返回給a(%rip)。最終,a(%rip)的值是1而不是2。
上面這種情況就是我們常說的數(shù)據(jù)不同步,或者線程不安全。對此,人們提出了很多方法,比如原子操作,互斥鎖等等,出發(fā)點(diǎn)主要是以下兩種:
現(xiàn)在回到python這門語言上。荷蘭人Guido van Rossum為了打發(fā)時(shí)間,于1989年發(fā)明了腳本語言python。和java類似,python源碼首先會(huì)被編譯成字節(jié)碼(python項(xiàng)目中的pyc文件),然后由解釋器進(jìn)行解釋。類似于在高級語言和匯編語言之間還多了一層中間語言。
上圖中,一個(gè)python表達(dá)式可以由多個(gè)解釋器指令構(gòu)成,一個(gè)解釋器指令又可以被分成多個(gè)匯編指令,這意味著一個(gè)解釋器指令可能在執(zhí)行過程中被打斷,事實(shí)也確實(shí)是這樣,python的解釋器CPython并不是線程安全的。所以,為了保證線程安全,首先要做到讓一個(gè)解釋器指令能不受線程調(diào)度影響被執(zhí)行完畢,對此python解釋器的開發(fā)者們搗鼓出了python全局解釋器鎖,簡稱GIL。GIL在任一時(shí)刻只允許運(yùn)行一個(gè)線程,當(dāng)一個(gè)線程執(zhí)行時(shí)間達(dá)到閾值時(shí),釋放GIL,這樣連線程調(diào)度也變得簡單了許多。
那么GIL是不是解決了線程安全的問題了呢?沒有。這是python中的一個(gè)深坑。下一篇博客我會(huì)寫一些自己在學(xué)習(xí)GIL時(shí)的心得。
轉(zhuǎn)載于:https://www.cnblogs.com/bugsheep/p/9008396.html
總結(jié)
以上是生活随笔為你收集整理的玩转python(2)多线程的历史2的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于jQuery/zepto的单页应用(
- 下一篇: Python学习笔记——GIF倒放处理