Python程序異常退出問題的解決與思考
- 分類:行業洞察
- 作者:
- 來源:
- 發布時間:2019-12-31 13:45
- 訪問量:
【概要描述】Python代碼出現類似于C程序中的內存問題,Why?歡迎點擊尋找答案哦!
Python程序異常退出問題的解決與思考
【概要描述】Python代碼出現類似于C程序中的內存問題,Why?歡迎點擊尋找答案哦!
- 分類:行業洞察
- 作者:
- 來源:
- 發布時間:2019-12-31 13:45
- 訪問量:
Python編程語言是一種解釋性語言,給開發人員的映像就是簡單易學、面向對象。并且由于Python有垃圾回收機制,不需要理會內存的申請和釋放操作,這給開發人員帶來了極大的方便。不像C語言編程,時刻要注意內存的申請和釋放,避免出現內存泄漏以及內存重復釋放的問題。
某天在某臺設備上,測試人員發現該設備上的Python進程發生異常退出的情況(CPython環境)。問題出現后,首先想到的就是查看日志信息。查看錯誤日志,出現“double free or corruption”錯誤信息。而且調試信息里面并沒有出現exception信息打印。也就是說該錯誤無法被try/exception機制捕捉。進一步查閱了“double free or corruption”錯誤,該問題類似于C程序中的內存重復釋放。
等等,Python代碼中不是沒有內存操作相關的代碼嗎?為什么會出現類似于C程序中的內存問題?原來, Python的底層是用 C 語言寫的,很多標準庫和第三方庫也都是用 C 語言寫的。在底層的調用上出現問題,是可能出現內存相關的問題的。
如果要定位問題,必須先得復現問題。根據問題出現的現場環境和復現操作,初步定位到一個具體的模塊。通過改寫并簡化相關模塊的代碼(注釋掉某些線程代碼),按照測試人員所做的操作進行復現。該問題需要在長時間重復做某種操作才會出現。在觸發問題的時間上有一定的偶然性,每次復現時長都不一定(7-15分鐘之間),但是出問題是必然的。通過不斷的修改代碼并復現,問題集中到了該模塊的某個流程相關的代碼。該流程包括同一個進程中的兩個線程。既然“double free or corruption”錯誤是內存重復釋放的問題,那就重點關注資源的申請和釋放操作相關的代碼。
此時,一個全局LIST類型的變量C出現在視野中,該變量會在線程A中進行初始化(清空),在線程B中會進行遍歷以及append操作。這與資源的申請和釋放操作有很大的相關性。
LIST類型變量不是線程安全的。提到線程安全,大家會想到要加鎖之類的操作。同時Python開發人員對GIL鎖并不陌生。GIL的作用是,對于一個解釋器,只能有一個線程在執行字節碼。所以任何時刻只有一條字節碼在被一個線程執行。GIL在字節碼層面上保證了線程安全,但是Python多線程包里依然提供了加鎖機制,這是為何?
假設有個操作,比如x+= 1,這個操作需要多個字節碼操作,在執行這個操作的多條字節碼期間,可能會發生線程切換,這樣就出現了線程競爭的情況。如下圖,“INPLACE_ADD”就是一條字節碼。
也可以理解成GIL鎖保證的是字節碼操作的原子性,而不是Python程序中某條語句執行的原子性。對于LIST變量的初始化以及append操作,肯定也可以拆分成多個字節碼操作。那么在線程A中LIST變量C的初始化操作的正在執行過程中,是很有可能被線程B打斷來執行LIST變量C的append操作的;或者線程B執行LIST變量C的append操作的過程中,被線程A打斷來執行LIST變量C的初始化操作??傊?,GIL鎖無法保證LIST變量C某個操作的原子性,必須依靠Python的線程同步機制,如對關鍵全局變量加互斥鎖。
在原有代碼中,由于編程人員的疏忽,沒有對LIST變量C加互斥鎖操作。通過對LIST變量C加互斥鎖操作,執行之前的問題復現操作,進程運行正常,沒有出現異常退出的情況,問題完美解決。
通過上述問題的原因分析和問題解決,總結如下:
1.在Python程序中必須要注意全局變量在多線程中的使用。如果是非線程安全變量,需要利用Python的同步機制,如互斥鎖。否則會出現意想不到的問題,隨機性很大,并且出現的問題無法被try/exception機制捕捉到,問題復現和問題的定位都非常困難。
2.GIL鎖是無法保證非線程安全變量在多線程中使用的安全性,必須依靠Python的線程同步機制,如對非線程安全變量加互斥鎖。
3.常見的非線程安全變量包括LIST(列表)、DICT(字典)等;常見的線程安全變量包括Queue(隊列)。

公司總部:北京市海淀區中關村軟件園8號華夏科技大廈三層
服務熱線:400-810-8981 / 010-82896289
版權所有:北京天地和興科技有限公司 京ICP備17065546號-1

掃一掃關注
天地和興微信公眾號