谈谈 Python 程序的运行原理
談?wù)?Python 程序的運(yùn)行原理
這篇文章準(zhǔn)確說(shuō)是『Python 源碼剖析』的讀書(shū)筆記,整理完之后才發(fā)現(xiàn)很長(zhǎng),那就將就看吧。
1. 簡(jiǎn)單的例子
先從一個(gè)簡(jiǎn)單的例子說(shuō)起,包含了兩個(gè)文件 foo.py 和 demo.py
[foo.py] def add(a, b):return a + b [demo.py] import fooa = [1, 'python'] a = 'a string'def func():a = 1b = 257print(a + b)print(a)if __name__ == '__main__':func()foo.add(1, 2)執(zhí)行這個(gè)程序
python demo.py輸出結(jié)果
a string 258同時(shí),該文件目錄多出一個(gè) foo.pyc 文件
2. 背后的魔法
看完程序的執(zhí)行結(jié)果,接下來(lái)開(kāi)始一行行解釋代碼。
2.1 模塊
Python 將 .py 文件視為一個(gè) module,這些 module 中,有一個(gè)主 module,也就是程序運(yùn)行的入口。在這個(gè)例子中,主 module 是 demo.py。
2.2 編譯
執(zhí)行?python demo.py?后,將會(huì)啟動(dòng) Python 的解釋器,然后將 demo.py 編譯成一個(gè)字節(jié)碼對(duì)象 PyCodeObject。
有的人可能會(huì)很好奇,編譯的結(jié)果不應(yīng)是 pyc 文件嗎,就像 Java 的 class 文件,那為什么是一個(gè)對(duì)象呢,這里稍微解釋一下。
在 Python 的世界中,一切都是對(duì)象,函數(shù)也是對(duì)象,類型也是對(duì)象,類也是對(duì)象(類屬于自定義的類型,在 Python 2.2 之前,int, dict 這些內(nèi)置類型與類是存在不同的,在之后才統(tǒng)一起來(lái),全部繼承自 object),甚至連編譯出來(lái)的字節(jié)碼也是對(duì)象,.pyc 文件是字節(jié)碼對(duì)象(PyCodeObject)在硬盤上的表現(xiàn)形式。
在運(yùn)行期間,編譯結(jié)果也就是 PyCodeObject 對(duì)象,只會(huì)存在于內(nèi)存中,而當(dāng)這個(gè)模塊的 Python 代碼執(zhí)行完后,就會(huì)將編譯結(jié)果保存到了 pyc 文件中,這樣下次就不用編譯,直接加載到內(nèi)存中。pyc 文件只是 PyCodeObject 對(duì)象在硬盤上的表現(xiàn)形式。
這個(gè) PyCodeObject 對(duì)象包含了 Python 源代碼中的字符串,常量值,以及通過(guò)語(yǔ)法解析后編譯生成的字節(jié)碼指令。PyCodeObject 對(duì)象還會(huì)存儲(chǔ)這些字節(jié)碼指令與原始代碼行號(hào)的對(duì)應(yīng)關(guān)系,這樣當(dāng)出現(xiàn)異常時(shí),就能指明位于哪一行的代碼。
2.3 pyc 文件
一個(gè) pyc 文件包含了三部分信息:Python 的 magic number、pyc 文件創(chuàng)建的時(shí)間信息,以及 PyCodeObject 對(duì)象。
magic number 是 Python 定義的一個(gè)整數(shù)值。一般來(lái)說(shuō),不同版本的 Python 實(shí)現(xiàn)都會(huì)定義不同的 magic number,這個(gè)值是用來(lái)保證 Python 兼容性的。比如要限制由低版本編譯的 pyc 文件不能讓高版本的 Python 程序來(lái)執(zhí)行,只需要檢查 magic number 不同就可以了。由于不同版本的 Python 定義的字節(jié)碼指令可能會(huì)不同,如果不做檢查,執(zhí)行的時(shí)候就可能出錯(cuò)。
下面所示的代碼可以來(lái)創(chuàng)建 pyc 文件,使用方法
python generate_pyc.py module_name例如
python generate_pyc.py demo [generate_pyc.pyc] import imp import sysdef generate_pyc(name):fp, pathname, description = imp.find_module(name)try:imp.load_module(name, fp, pathname, description) finally:if fp:fp.close()if __name__ == '__main__':generate_pyc(sys.argv[1])2.4 字節(jié)碼指令
為什么 pyc 文件也稱作字節(jié)碼文件?因?yàn)檫@些文件存儲(chǔ)的都是一些二進(jìn)制的字節(jié)數(shù)據(jù),而不是能讓人直觀查看的文本數(shù)據(jù)。
Python 標(biāo)準(zhǔn)庫(kù)提供了用來(lái)生成代碼對(duì)應(yīng)字節(jié)碼的工具?dis。dis 提供一個(gè)名為 dis 的方法,這個(gè)方法接收一個(gè) code 對(duì)象,然后會(huì)輸出 code 對(duì)象里的字節(jié)碼指令信息。
s = open('demo.py').read() co = compile(s, 'demo.py', 'exec') import dis dis.dis(co)執(zhí)行上面這段代碼可以輸出 demo.py 編譯后的字節(jié)碼指令
1 0 LOAD_CONST 0 (-1)3 LOAD_CONST 1 (None)6 IMPORT_NAME 0 (foo)9 STORE_NAME 0 (foo)3 12 LOAD_CONST 2 (1)15 LOAD_CONST 3 (u'python')18 BUILD_LIST 221 STORE_NAME 1 (a)4 24 LOAD_CONST 4 (u'a string')27 STORE_NAME 1 (a)6 30 LOAD_CONST 5 (<code object func at 00D97650, file "demo.py", line 6>)33 MAKE_FUNCTION 036 STORE_NAME 2 (func)11 39 LOAD_NAME 1 (a)42 PRINT_ITEM 43 PRINT_NEWLINE 13 44 LOAD_NAME 3 (__name__)47 LOAD_CONST 6 (u'__main__')50 COMPARE_OP 2 (==)53 POP_JUMP_IF_FALSE 8214 56 LOAD_NAME 2 (func)59 CALL_FUNCTION 062 POP_TOP 15 63 LOAD_NAME 0 (foo)66 LOAD_ATTR 4 (add)69 LOAD_CONST 2 (1)72 LOAD_CONST 7 (2)75 CALL_FUNCTION 278 POP_TOP 79 JUMP_FORWARD 0 (to 82)>> 82 LOAD_CONST 1 (None)85 RETURN_VALUE2.5 Python 虛擬機(jī)
demo.py 被編譯后,接下來(lái)的工作就交由 Python 虛擬機(jī)來(lái)執(zhí)行字節(jié)碼指令了。Python 虛擬機(jī)會(huì)從編譯得到的 PyCodeObject 對(duì)象中依次讀入每一條字節(jié)碼指令,并在當(dāng)前的上下文環(huán)境中執(zhí)行這條字節(jié)碼指令。我們的程序就是通過(guò)這樣循環(huán)往復(fù)的過(guò)程才得以執(zhí)行。
2.6 import 指令
demo.py 的第一行代碼是?import foo。import 指令用來(lái)載入一個(gè)模塊,另外一個(gè)載入模塊的方法是?from xx import yy。用 from 語(yǔ)句的好處是,可以只復(fù)制需要的符號(hào)變量到當(dāng)前的命名空間中(關(guān)于命名空間將在后面介紹)。
前文提到,當(dāng)已經(jīng)存在 pyc 文件時(shí),就可以直接載入而省去編譯過(guò)程。但是代碼文件的內(nèi)容會(huì)更新,如何保證更新后能重新編譯而不入舊的 pyc 文件呢。答案就在 pyc 文件中存儲(chǔ)的創(chuàng)建時(shí)間信息。當(dāng)執(zhí)行 import 指令的時(shí)候,如果已存在 pyc 文件,Python 會(huì)檢查創(chuàng)建時(shí)間是否晚于代碼文件的修改時(shí)間,這樣就能判斷是否需要重新編譯,還是直接載入了。如果不存在 pyc 文件,就會(huì)先將 py 文件編譯。
2.7 絕對(duì)引入和相對(duì)引入
前文已經(jīng)介紹了?import foo?這行代碼。這里隱含了一個(gè)問(wèn)題,就是?foo?是什么,如何找到?foo。這就屬于 Python 的模塊引入規(guī)則,這里不展開(kāi)介紹,可以參考?pep-0328。
2.8 賦值語(yǔ)句
接下來(lái),執(zhí)行到?a = [1, 'python'],這是一條賦值語(yǔ)句,定義了一個(gè)變量 a,它對(duì)應(yīng)的值是 [1, 'python']。這里要解釋一下,變量是什么呢?
按照[維基百科]("https://en.wikipedia.org/wiki/Variable_(computer_science") 的解釋
變量是一個(gè)存儲(chǔ)位置和一個(gè)關(guān)聯(lián)的符號(hào)名字,這個(gè)存儲(chǔ)位置包含了一些已知或未知的量或者信息。
變量實(shí)際上是一個(gè)字符串的符號(hào),用來(lái)關(guān)聯(lián)一個(gè)存儲(chǔ)在內(nèi)存中的對(duì)象。在 Python 中,會(huì)使用 dict(就是 Python 的 dict 對(duì)象)來(lái)存儲(chǔ)變量符號(hào)(字符串)與一個(gè)對(duì)象的映射。
那么賦值語(yǔ)句實(shí)際上就是用來(lái)建立這種關(guān)聯(lián),在這個(gè)例子中是將符號(hào)?a?與一個(gè)列表對(duì)象?[1, 'python']?建立映射。
緊接著的代碼執(zhí)行了?a = 'a string',這條指令則將符號(hào)?a?與另外一個(gè)字符串對(duì)象?a string?建立了映射。今后對(duì)變量?a?的操作,將反應(yīng)到字符串對(duì)象?a string?上。
2.9 def 指令
我們的 Python 代碼繼續(xù)往下運(yùn)行,這里執(zhí)行到一條?def func(),從字節(jié)碼指令中也可以看出端倪?MAKE_FUNCTION。沒(méi)錯(cuò)這條指令是用來(lái)創(chuàng)建函數(shù)的。Python 是動(dòng)態(tài)語(yǔ)言,def 實(shí)際上是執(zhí)行一條指令,用來(lái)創(chuàng)建函數(shù)(class 則是創(chuàng)建類的指令),而不僅僅是個(gè)語(yǔ)法關(guān)鍵字。函數(shù)并不是事先創(chuàng)建好的,而是執(zhí)行到的時(shí)候才創(chuàng)建的。
def func()?將會(huì)創(chuàng)建一個(gè)名稱為?func?的函數(shù)對(duì)象。實(shí)際上是先創(chuàng)建一個(gè)函數(shù)對(duì)象,然后將 func 這個(gè)名稱符號(hào)綁定到這個(gè)函數(shù)上。
Python 中是無(wú)法實(shí)現(xiàn) C 和 Java 中的重載的,因?yàn)橹剌d要求函數(shù)名要相同,而參數(shù)的類型或數(shù)量不同,但是 Python 是通過(guò)變量符號(hào)(如這里的?func)來(lái)關(guān)聯(lián)一個(gè)函數(shù),當(dāng)我們用 def 語(yǔ)句再次創(chuàng)建一個(gè)同名的函數(shù)時(shí),這個(gè)變量名就綁定到新的函數(shù)對(duì)象上了。
2.10 動(dòng)態(tài)類型
繼續(xù)看函數(shù)?func?里面的代碼,這時(shí)又有一條賦值語(yǔ)句?a = 1。變量?a?現(xiàn)在已經(jīng)變成了第三種類型,它現(xiàn)在是一個(gè)整數(shù)了。那么 Python 是怎么實(shí)現(xiàn)動(dòng)態(tài)類型的呢?答案就藏在具體存儲(chǔ)的對(duì)象上。變量?a?僅僅只是一個(gè)符號(hào)(實(shí)際上是一個(gè)字符串對(duì)象),類型信息是存儲(chǔ)在對(duì)象上的。在 Python 中,對(duì)象機(jī)制的核心是類型信息和引用計(jì)數(shù)(引用計(jì)數(shù)屬于垃圾回收的部分)。
用 type(a),可以輸出 a 的類型,這里是 int
b = 257?跳過(guò),我們直接來(lái)看看?print(a + b),print 是輸出函數(shù),這里略過(guò)。這里想要探究的是?a + b。
因?yàn)?a?和?b?并不存儲(chǔ)類型信息,因此當(dāng)執(zhí)行?a + b?的時(shí)候就必須先檢查類型,比如 1 + 2 和 "1" + "2" 的結(jié)果是不一樣的。
看到這里,我們就可以想象一下執(zhí)行一句簡(jiǎn)單的?a + b,Python 虛擬機(jī)需要做多少繁瑣的事情了。首先需要分別檢查?a?和?b?所對(duì)應(yīng)對(duì)象的類型,還要匹配類型是否一致(1 + "2" 將會(huì)出現(xiàn)異常),然后根據(jù)對(duì)象的類型調(diào)用正確的?+?函數(shù)(例如數(shù)值的 + 或字符串的 +),而 CPU 對(duì)于上面這條語(yǔ)句只需要執(zhí)行 ADD 指令(還需要先將變量 MOV 到寄存器)。
2.11 命名空間 (namespace)
在介紹上面的這些代碼時(shí),還漏掉了一個(gè)關(guān)鍵的信息就是命名空間。在 Python 中,類、函數(shù)、module 都對(duì)應(yīng)著一個(gè)獨(dú)立的命名空間。而一個(gè)獨(dú)立的命名空間會(huì)對(duì)應(yīng)一個(gè) PyCodeObject 對(duì)象,所以上面的 demo.py 文件編譯后會(huì)生成兩個(gè) PyCodeObject,只是在 demo.py 這個(gè) module 層的 PyCodeObject 中通過(guò)一個(gè)變量符號(hào)?func?嵌套了一個(gè)函數(shù)的 PyCodeObject。
命名空間的意義,就是用來(lái)確定一個(gè)變量符號(hào)到底對(duì)應(yīng)什么對(duì)象。命名空間可以一個(gè)套一個(gè)地形成一條命名空間鏈,Python 虛擬機(jī)在執(zhí)行的過(guò)程中,會(huì)有很大一部分時(shí)間消耗在從這條命名空間鏈中確定一個(gè)符號(hào)所對(duì)應(yīng)的對(duì)象是什么。
在 Python中,命名空間是由一個(gè) dict 對(duì)象實(shí)現(xiàn)的,它維護(hù)了(name,obj)這樣的關(guān)聯(lián)關(guān)系。
說(shuō)到這里,再補(bǔ)充一下?import foo?這行代碼會(huì)在 demo.py 這個(gè)模塊的命名空間中,創(chuàng)建一個(gè)新的變量名?foo,foo?將綁定到一個(gè) PyCodeObject 對(duì)象,也就是 foo.py 的編譯結(jié)果。
2.11.1 dir 函數(shù)
Python 的內(nèi)置函數(shù) dir?可以用來(lái)查看一個(gè)命名空間下的所有名字符號(hào)。一個(gè)用處是查看一個(gè)命名空間的所有屬性和方法(這里的命名空間就是指類、函數(shù)、module)。
比如,查看當(dāng)前的命名空間,可以使用 dir(),查看 sys 模塊,可以使用 dir(sys)。
2.11.2 LEGB 規(guī)則
Python 使用 LEGB 的順序來(lái)查找一個(gè)符號(hào)對(duì)應(yīng)的對(duì)象
locals -> enclosing function -> globals -> builtins
locals,當(dāng)前所在命名空間(如函數(shù)、模塊),函數(shù)的參數(shù)也屬于命名空間內(nèi)的變量
enclosing,外部嵌套函數(shù)的命名空間(閉包中常見(jiàn))
def fun1(a):def fun2():# a 位于外部嵌套函數(shù)的命名空間print(a)globals,全局變量,函數(shù)定義所在模塊的命名空間
a = 1 def fun():# 需要通過(guò) global 指令來(lái)聲明全局變量global a# 修改全局變量,而不是創(chuàng)建一個(gè)新的 local 變量a = 2builtins,內(nèi)置模塊的命名空間。Python 在啟動(dòng)的時(shí)候會(huì)自動(dòng)為我們載入很多內(nèi)置的函數(shù)、類,比如 dict,list,type,print,這些都位于?__builtins__?模塊中,可以使用?dir(__builtins__)?來(lái)查看。這也是為什么我們?cè)跊](méi)有 import 任何模塊的情況下,就能使用這么多豐富的函數(shù)和功能了。
介紹完命名空間,就能理解?print(a)?這行代碼輸出的結(jié)果為什么是?a string?了。
2.12 內(nèi)置屬性?__name__
現(xiàn)在到了解釋?if __name__ == '__main__'?這行代碼的時(shí)候了。當(dāng) Python 程序啟動(dòng)后,Python 會(huì)自動(dòng)為每個(gè)模塊設(shè)置一個(gè)屬性?__name__?通常使用的是模塊的名字,也就是文件名,但唯一的例外是主模塊,主模塊將會(huì)被設(shè)置為?__main__。利用這一特性,就可以做一些特別的事。比如當(dāng)該模塊以主模塊來(lái)運(yùn)行的時(shí)候,可以運(yùn)行測(cè)試用例。而當(dāng)被其他模塊 import 時(shí),則只是乖乖的,提供函數(shù)和功能就好。
2.13 函數(shù)調(diào)用
最后兩行是函數(shù)調(diào)用,這里略去不講。
3. 回顧
講到最后,還有些內(nèi)容需要再回顧和補(bǔ)充一下。
3.1 pyc 文件
Python 只會(huì)對(duì)那些以后可能繼續(xù)被使用和載入的模塊才會(huì)生成 pyc 文件,Python 認(rèn)為使用了 import 指令的模塊,屬于這種類型,因此會(huì)生成 pyc 文件。而對(duì)于只是臨時(shí)用一次的模塊,并不會(huì)生成 pyc 文件,Python 將主模塊當(dāng)成了這種類型的文件。這就解釋了為什么?python demo.py?執(zhí)行完后,只會(huì)生成一個(gè)?foo.pyc?文件。
如果要問(wèn) pyc 文件什么時(shí)候生成,答案就是在執(zhí)行了 import 指令之后,from xx import yy 同樣屬于 import 指令。
3.2 小整數(shù)對(duì)象池
在 demo.py 這里例子中,所用的整數(shù)特意用了一個(gè) 257,這是為了介紹小整數(shù)對(duì)象池的。整數(shù)在程序中的使用非常廣泛,Python 為了優(yōu)化速度,使用了小整數(shù)對(duì)象池,避免為整數(shù)頻繁申請(qǐng)和銷毀內(nèi)存空間。
Python 對(duì)小整數(shù)的定義是 [-5, 257),這些整數(shù)對(duì)象是提前建立好的,不會(huì)被垃圾回收。在一個(gè) Python 的程序中,所有位于這個(gè)范圍內(nèi)的整數(shù)使用的都是同一個(gè)對(duì)象,從下面這個(gè)例子就可以看出。
> a = 1 > id(a) 40059744 > b = 1 > id(b) 40059744 > c = 257 > id(c) 41069072 > d = 257 > id(257) 41069096id 函數(shù)可以用來(lái)查看一個(gè)對(duì)象的唯一標(biāo)志,可以認(rèn)為是內(nèi)存地址
對(duì)于大整數(shù),Python 使用的是一個(gè)大整數(shù)對(duì)象池。這句話的意思是:
每當(dāng)創(chuàng)建一個(gè)大整數(shù)的時(shí)候,都會(huì)新建一個(gè)對(duì)象,但是這個(gè)對(duì)象不再使用的時(shí)候,并不會(huì)銷毀,后面再建立的對(duì)象會(huì)復(fù)用之前已經(jīng)不再使用的對(duì)象的內(nèi)存空間。(這里的不再使用指的是引用計(jì)數(shù)為0,可以被銷毀)
3.3 字符串對(duì)象緩沖池
如果仔細(xì)思考一下,一定會(huì)猜到字符串也采用了這種類似的技術(shù),我們來(lái)看一下
a = 'a' b = 'a' id(a) 14660456 id(b) 14660456沒(méi)錯(cuò),Python 的設(shè)計(jì)者為一個(gè)字節(jié)的字符對(duì)應(yīng)的字符串對(duì)象 (PyStringObject) 也設(shè)計(jì)了這樣一個(gè)對(duì)象池。同時(shí)還有一個(gè)?intern?機(jī)制,可以將內(nèi)容相同的字符串變量轉(zhuǎn)換成指向同一個(gè)字符串對(duì)象。
intern 機(jī)制的關(guān)鍵,就是在系統(tǒng)中有一個(gè)(key,value)映射關(guān)系的集合,集合的名稱叫做 interned。在這個(gè)集合中,記錄著被 intern 機(jī)制處理過(guò)的 PyStringObject 對(duì)象。不過(guò) Python 始終會(huì)為字符串創(chuàng)建 PyStringObject 對(duì)象,即便在interned 中已經(jīng)有一個(gè)與之對(duì)應(yīng)的 PyStringObject 對(duì)象了,而 intern 機(jī)制是在字符串被創(chuàng)建后才起作用。
a = 'a string' b = 'a string' a is b False a = intern('a string') # 手動(dòng)調(diào)用 intern 方法 b = intern('a string') a is b True關(guān)于 intern 函數(shù) 可以參考官方文檔,更多擴(kuò)展閱讀:
http://stackoverflow.com/questions/15541404/python-string-interning
值得說(shuō)明的是,數(shù)值類型和字符串類型在 Python 中都是不可變的,這意味著你無(wú)法修改這個(gè)對(duì)象的值,每次對(duì)變量的修改,實(shí)際上是創(chuàng)建一個(gè)新的對(duì)象。得益于這樣的設(shè)計(jì),才能使用對(duì)象緩沖池這種優(yōu)化。
Python 的實(shí)現(xiàn)上大量采用了這種內(nèi)存對(duì)象池的技術(shù),不僅僅對(duì)于這些特定的對(duì)象,還有專門的內(nèi)存池用于小對(duì)象,使用這種技術(shù)可以避免頻繁地申請(qǐng)和釋放內(nèi)存空間,目的就是讓 Python 能稍微更快一點(diǎn)。更多內(nèi)容可以參考這里。
如果想了解更快的 Python,可以看看?PyPy
3.4 import 指令
前文提到 import 指令是用來(lái)載入 module 的,如果需要,也會(huì)順道做編譯的事。但 import 指令,還會(huì)做一件重要的事情就是把 import 的那個(gè) module 的代碼執(zhí)行一遍,這件事情很重要。Python 是解釋執(zhí)行的,連函數(shù)都是執(zhí)行的時(shí)候才創(chuàng)建的。如果不把那個(gè) module 的代碼執(zhí)行一遍,那么 module 里面的函數(shù)都沒(méi)法創(chuàng)建,更別提去調(diào)用這些函數(shù)了。
執(zhí)行代碼的另外一個(gè)重要作用,就是在這個(gè) module 的命名空間中,創(chuàng)建模塊內(nèi)定義的函數(shù)和各種對(duì)象的符號(hào)名稱(也就是變量名),并將其綁定到對(duì)象上,這樣其他 module 才能通過(guò)變量名來(lái)引用這些對(duì)象。
Python 虛擬機(jī)還會(huì)將已經(jīng) import 過(guò)的 module 緩存起來(lái),放到一個(gè)全局 module 集合 sys.modules 中。這樣做有一個(gè)好處,即如果程序的在另一個(gè)地方再次 import 這個(gè)模塊,Python 虛擬機(jī)只需要將全局 module 集合中緩存的那個(gè) module 對(duì)象返回即可。
你現(xiàn)在一定想到了 sys.modules 是一個(gè) dict 對(duì)象,可以通過(guò) type(sys.modules) 來(lái)驗(yàn)證
3.5 多線程
demo.py 這個(gè)例子并沒(méi)有用到多線程,但還是有必要提一下。
在提到多線程的時(shí)候,往往要關(guān)注線程如何同步,如何訪問(wèn)共享資源。Python 是通過(guò)一個(gè)全局解釋器鎖 GIL(Global Interpreter Lock)來(lái)實(shí)現(xiàn)線程同步的。當(dāng) Python 程序只有單線程時(shí),并不會(huì)啟用 GIL,而當(dāng)用戶創(chuàng)建了一個(gè) thread 時(shí),表示要使用多線程,Python 解釋器就會(huì)自動(dòng)激活 GIL,并創(chuàng)建所需要的上下文環(huán)境和數(shù)據(jù)結(jié)構(gòu)。
Python 字節(jié)碼解釋器的工作原理是按照指令的順序一條一條地順序執(zhí)行,Python 內(nèi)部維護(hù)著一個(gè)數(shù)值,這個(gè)數(shù)值就是 Python 內(nèi)部的時(shí)鐘,如果這個(gè)數(shù)值為 N,則意味著 Python 在執(zhí)行了 N 條指令以后應(yīng)該立即啟動(dòng)線程調(diào)度機(jī)制,可以通過(guò)下面的代碼獲取這個(gè)數(shù)值。
import sys sys.getcheckinterval() # 100線程調(diào)度機(jī)制將會(huì)為線程分配 GIL,獲取到 GIL 的線程就能開(kāi)始執(zhí)行,而其他線程則必須等待。由于 GIL 的存在,Python 的多線程性能十分低下,無(wú)法發(fā)揮多核 CPU 的優(yōu)勢(shì),性能甚至不如單線程。因此如果你想用到多核 CPU,一個(gè)建議是使用多進(jìn)程。
3.6 垃圾回收
在講到垃圾回收的時(shí)候,通常會(huì)使用引用計(jì)數(shù)的模型,這是一種最直觀,最簡(jiǎn)單的垃圾收集技術(shù)。Python 同樣也使用了引用計(jì)數(shù),但是引用計(jì)數(shù)存在這些缺點(diǎn):
- 頻繁更新引用計(jì)數(shù)會(huì)降低運(yùn)行效率
- 引用計(jì)數(shù)無(wú)法解決循環(huán)引用問(wèn)題
Python 在引用計(jì)數(shù)機(jī)制的基礎(chǔ)上,使用了主流垃圾收集技術(shù)中的標(biāo)記——清除和分代收集兩種技術(shù)。
關(guān)于垃圾回收,可以參考
http://hbprotoss.github.io/posts/pythonla-ji-hui-shou-ji-zhi.html
4. 參考文獻(xiàn)
- Python 源碼剖析
- Python 官方文檔
總結(jié)
以上是生活随笔為你收集整理的谈谈 Python 程序的运行原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js处理json和字符串示例
- 下一篇: 实例教程:1小时学会Python