視頻教程:多線程場(chǎng)景下诊县,用C++調(diào)用Python腳本的方法
Git: https://github.com/JasonLiThirty/C-andPython
接口函數(shù)
Python3.6提供給C/C++接口函數(shù)缎浇,基本都是定義pylifecycle.h骡楼,pythonrun.h熔号,ceval.h中。
Py_Initialize() 和 Py_Finalize()
- C++應(yīng)用程序調(diào)用Python腳本之前鸟整,必須先調(diào)用Py_Initialize()進(jìn)行初始化引镊,這個(gè)API用來(lái)分配Python解釋器使用的全局資源,應(yīng)用程序結(jié)束時(shí)需要調(diào)用Py_Finalize()來(lái)關(guān)閉Python的使用環(huán)境篮条。
Py_IsInitialized()
- 用來(lái)判斷Python解釋器是否初始化成功弟头,true為成功,false為失敗涉茧。
PyErr_Print() & PyErr_Clear()
- 執(zhí)行Python出錯(cuò)時(shí)赴恨,PyErr_Print()可將錯(cuò)誤信息顯示出來(lái),PyErr_Clear()將錯(cuò)誤信息在Python解釋器的緩存清除。
PyRun_SimpleString()
- 這個(gè)函數(shù)能夠用來(lái)執(zhí)行簡(jiǎn)單的Python語(yǔ)句。
PyEval_InitThreads()
- 如果使用多線程調(diào)用Python腳本失受,就需要在初始化Python解釋器時(shí)調(diào)用PyEval_InitThreads()來(lái)啟用線程支持(導(dǎo)致Python內(nèi)部啟用線程鎖)勺阐,最好在主線程啟動(dòng)時(shí)就調(diào)用。該API同時(shí)也鎖定全局解釋鎖匹舞,所以,還需要在初始化完成后需要自行釋放鎖。
- 如果不需要使用多線程汛聚,不建議啟用該選項(xiàng),互斥鎖也會(huì)不可避免的增加系統(tǒng)開銷短荐。
PyEval_AcquireLock() & PyEval_ReleaseLock()
- 因?yàn)镻ython編輯器不是線程安全的倚舀,為了支持多線程叹哭, Python 使用了互斥使訪問內(nèi)部數(shù)據(jù)結(jié)構(gòu)串行化。這種互斥即 “全局解釋器鎖 – global interpreter lock”痕貌,當(dāng)某個(gè)線程想使用 Python 的C API的時(shí)候风罩,它必須獲得全局解釋器鎖。所以多線程調(diào)用Python腳本前必須先持有這個(gè)全局解釋器鎖舵稠,然后才能進(jìn)行對(duì)Python的一系列操作超升。
- 調(diào)用了 PyEval_AcquireLock之后,可以安全地假定你的線程已經(jīng)持有了鎖哺徊,其他相關(guān)線程不是被阻塞就是在執(zhí)行與 Python 解析器無(wú)關(guān)的代碼室琢。之后就可以開始執(zhí)行與Python相關(guān)的代碼了。
- 調(diào)用了 PyEval_AcquireLock取得了鎖之后落追,必須確保運(yùn)行完需要的代碼后調(diào)用 PyEval_ReleaseLock 來(lái)釋放它盈滴,否則就會(huì)導(dǎo)致線程死鎖并凍結(jié)其他 Python 線程。
PyThreadState對(duì)象及其相關(guān)函數(shù)
每個(gè)Python 線程都需要維護(hù)自己的狀態(tài)信息轿钠,在被Python解釋器執(zhí)行時(shí)巢钓,需要先切換線程的狀態(tài)信息,才可以開始執(zhí)行相應(yīng)的代碼疗垛,這個(gè)線程狀態(tài)信息就是用PyThreadState這個(gè)對(duì)象來(lái)描述的症汹。
- PyThreadState:每個(gè)Python 線程維護(hù)自己的狀態(tài)信息,存儲(chǔ)在 PyThreadState對(duì)象中贷腕。在多線程應(yīng)用中用 C 語(yǔ)言調(diào)用 Python API 函數(shù)時(shí)背镇,你必須維護(hù)自己的 PyThreadState 對(duì)象以便能安全地執(zhí)行并發(fā)的 Python 代碼。
- PyInterpreterState:描述了幾個(gè)協(xié)作線程共享的狀態(tài)花履,屬于同一解釋器的線程共享它們的模塊維護(hù)和幾個(gè)其他的內(nèi)部子項(xiàng)芽世。
- PyThreadState_Get():得到當(dāng)前PyThreadState的指針。一般用在初始化時(shí)用來(lái)得到主線程的PyThreadState對(duì)象诡壁。
- PyThreadState_New():為新的C++線程創(chuàng)建一個(gè) PyThreadState 對(duì)象济瓢,需要用到既有的 PyInterpreterState 對(duì)象。該對(duì)象為所有參與的線程所共享的信息妹卿,初始化 Python 時(shí)旺矾,會(huì)自動(dòng)創(chuàng)建一個(gè) PyInterpreterState 對(duì)象,附加在主線程的 PyThreadState 對(duì)象上夺克, 所以可以利用PyInterpreterState 對(duì)象創(chuàng)建新的 PyThreadState箕宙。
- PyThreadState_Swap():將要執(zhí)行Python代碼的線程特定的PyThreadState對(duì)象加載到解釋器
- PyThreadState_Clear():重置線程狀態(tài)對(duì)象的所有相關(guān)信息。
- PyThreadState_Delete():銷毀一個(gè)線程狀態(tài)對(duì)象铺纽,該對(duì)象的線程狀態(tài)必須提前調(diào)用 PyThreadState_Clear() 進(jìn)行清除柬帕。
以下是幾個(gè)進(jìn)階的接口函數(shù),這些函數(shù)可以將全局解釋器的加鎖和釋放鎖的操作,以及線程切換的操作封裝起來(lái)陷寝,比較方便锅很。
PyEval_AcquireThread(PyThreadState *tstate)
- 獲得全局解釋器鎖并將當(dāng)前線程狀態(tài)設(shè)定為 tstate ,它不能為NULL凤跑。鎖必須提前創(chuàng)建爆安。如果線程已經(jīng)擁有鎖,會(huì)發(fā)生死鎖仔引。
PyEval_ReleaseThread(PyThreadState *tstate)
- 重置當(dāng)前線程狀態(tài)為NULL并釋放全局解釋器鎖扔仓。鎖必須提前創(chuàng)建并且以在當(dāng)前線程中獲得。參數(shù) tstate 不能為 NULL咖耘。它只能用于校驗(yàn)它描述的當(dāng)前線程狀態(tài)——如果它不對(duì)翘簇,會(huì)報(bào)告一個(gè)致命錯(cuò)誤。這個(gè)函數(shù)在編譯時(shí)禁用線程支持的情況下不可用鲤看。
PyGILState_STATE state = PyGILState_Ensure()
- 獲得全局解釋器鎖并自動(dòng)創(chuàng)建一個(gè)線程狀態(tài)缘揪,返回值是一個(gè)不透明的線程狀態(tài)“句柄”。
PyGILState_Release(PyGILState_STATE state)
- 釋放全局解釋器鎖义桂,Python解析器線程狀態(tài)切換到調(diào)用PyGILState_Acquire之前的狀態(tài)。
PyGILState_Ensure() 和 PyGILState_Release()需要成對(duì)出現(xiàn)蹈垢,僅在CPython中有效慷吊,這樣就能實(shí)現(xiàn)Python解釋器的鎖功能。只有在關(guān)閉Python的使用環(huán)境時(shí)曹抬,可以不再需要調(diào)用PyGILState_Release()函數(shù)
示例代碼
Git: https://github.com/JasonLiThirty/C-andPython
PyInvoker
面向?qū)ο蟮姆椒▽?shí)現(xiàn)Python解釋器的管理和與Python腳本交互的實(shí)現(xiàn)溉瓶。
PyLock
運(yùn)用PyGILState_Acqsuire和PyGILState_ReleasePython的Global Interpreter Lock,來(lái)實(shí)現(xiàn)多線程之間數(shù)據(jù)完整性和狀態(tài)同步谤民,GIL機(jī)制能保證在任意時(shí)刻只有一個(gè)線程在解釋器中運(yùn)行堰酿,效率上會(huì)有一定的損失。
PythonGreet.py
def Hello(): print('Hello world!!!')
PythonCalc.py
def Add(num1, num2): return num1+num2