1. 程式人生 > >我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

進群:548377875  加入萬人交流基地!

我的第一個評論是:

以我之愚見,這是PyEval_InitThreads()中的一個Bug。

修復PyGILState_Ensure()

2年的時間裡,我完全不記得這個bug了。 2016年3月,我修改了Steve的測試程式,使其與Linux相容(該測試是為Windows編寫的)。 我成功地重現了我電腦上的錯誤,並且為PyGILState_Ensure()寫了一個修復程式。

一年後,2017年11月,卡辛斯基問道:

此修復釋出了嗎? 我在更新日誌中找不到...

哎呀,我又完全忘記了這個問題! 這一次,我不僅安裝了我的PyGILState_Ensure()修復,還編寫了單元測試test_embed.test_bpo20891():

好的,這個bug現在已經在Python 2.7, 3.6 和master(將來的3.7)中得到解決。 在3.6和master版本中,此修復帶有單元測試。

我的主分支的修復,提交b4d1e1f7:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

於是我關閉了問題bpo-20891 ...

macOS上測試發生隨機崩潰

一切都很好......但一週後,我注意到我新增加的單元測試在macOS buildbots上發生了隨機崩潰。 我成功地手動重現了這個bug,第三次執行時崩潰的例子:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

macOS上的test_embed.test_bpo20891()在PyGILState_Ensure() 中顯示有競態條件(race condition):GIL鎖本身的建立...沒有被加鎖保護! 新增一個新的鎖來檢查Python是否有GIL鎖,好像沒有意義...

我提出了PyThread_start_new_thread()的一個不完整的修復:

我發現有一個修復是管用的:在PyThread_start_new_thread()中呼叫PyEval_InitThreads()。 那麼,一旦生成第二個執行緒就會建立GIL鎖。 當兩個執行緒正在執行時,GIL不能再建立。 至少,用python程式碼不可以建。 如果一個執行緒不是由Python產生的話,此修復不能解決這個問題,但是這個執行緒呼叫了PyGILState_Ensure()。

為什麼不始終建立GIL?

Antoine Pitrou問了一個簡單的問題:

為什麼不在直譯器初始化時總是呼叫PyEval_InitThreads()? 有什麼缺點嗎?

當然在學習Python的道路上肯定會困難,沒有好的學習資料,怎麼去學習呢?

所以小編準備了一份零基礎入門Python的學習資料。關注,轉發,私信“007”即可領取!

感謝git blame和git log,我發現了“按需”建立GIL的程式碼,來自於26年前做出的改變!

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

我的猜測是,動態建立GIL的目的是為了減少GIL的“開銷”。這些GIL用於那些只使用單個Python執行緒的應用程式(永遠不會產生新的Python執行緒)。

幸運的是,Guido van Rossum在我附近,能夠對基本原理加以闡述:

是的,最初的理由是執行緒是深奧的,不為大多數程式碼所使用,並且當時我們一定覺得:總是使用GIL會導致(微小的)速度放緩,並增加由於GIL程式碼中的錯誤而導致崩潰的風險。 我很高興得知我們不再需要擔心這一點,並且可以始終對其進行初始化。

提出Py_Initialize()的第二個修復

我提出了Py_Initialize()的第二個修復,以便在Python啟動時始終建立GIL,並且不再“按需”,以防止出現競態條件的風險:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

Nick Coghlan問我是否可以通過效能基準測試我的補丁。 我在我的PR 4700上執行pyperformance。差異至少5%:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

哦,5個基準比較慢。 Python中效能退步是不受歡迎的:我們正在努力讓Python變得更快!

在聖誕節前忽略錯誤測試

我沒有想到5個基準測試會變慢。 我需要進一步的調查,但時間不夠。也許是我太害羞,或者羞於承擔導致效能退步的責任。

在聖誕節假期之前,我沒有做任何決定,而test_embed.test_bpo20891()在macOS buildbots上仍然是隨機失敗。 在離開兩個星期之前,我對於觸及Python的關鍵部分,即GIL,並沒有太多把握。 所以我決定,等到我回來之前,先跳過test_bpo20891()。

沒有聖誕禮物給你了:Python 3.7。

執行新的基準測試,和應用於master的第二個修復

在2018年1月底,我再次運行了那5個由於我的PR(Pull request)而變慢的基準測試。 我使用了CPU隔離,在我的膝上型電腦上手動執行這些基準測試:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

好吧,它證實了,依照Python效能基準套件,我的第二個修復對效能沒有顯著的影響。

我決定將我的修復程式推送到master分支,提交2914bb32:

 

我花了整整四年才解決這個bug!程式設計師果真不是一般人能做的!

 

 

然後我在master分支上重新啟用了test_embed.test_bpo20891()。

沒有適用於Python 2.7和3.6的第二個修復,抱歉!

Antoine Pitrou認為,不應該合併Python 3.6的backport (注:backport是將一個軟體的補丁應用到比此補丁所對應的版本更老的版本的行為):

我不這麼認為。 人們可能已經呼叫PyEval_InitThreads()。

Guido van Rossum也不想把這一修改做backport。 所以我只從3.6的分支中刪除了test_embed.test_bpo20891()。

由於相同的原因,我沒有將我的第二個修復應用於Python 2.7。 而且,Python 2.7沒有單元測試,因為它很難backport。

至少,Python 2.7和3.6獲得了我的第一個PyGILState_Ensure()修復。

結論

在少數案例中,Python仍然存在一些競態條件。 當一個C執行緒開始使用Python API時,在建立GIL時就可以發現這樣的Bug。 我推出了第一個修復程式,但在macOS上發現了一個新的不同的競態條件。

我不得不深入研究Python GIL的歷史(1992年)。 幸運的是,Guido van Rossum也能夠闡述其基本原理。

在基準測試出現故障後,我們同意修改Python 3.7,以便始終建立GIL,而不是按需建立GIL。 該變化對效能沒有顯著的影響。

我們還決定讓Python 2.7和3.6保持不變,以防止任何回退風險:可以繼續按需建立GIL。

我花了4年的時間修復了Python GIL中的一個令人討厭的bug。 在接觸Python中如此關鍵的部分時,我從未自信滿滿。 現在,我很高興這個bug被我們甩在了身後:現在,它已經在未來的Python 3.7中完全修復了!

完整的故事見bpo-20891。 感謝幫助我解決這個Bug的所有開發人員!