自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

自帶的 Print 函數(shù)居然會(huì)報(bào)錯(cuò)?

開(kāi)發(fā) 后端
最近用 Python 寫(xiě)了幾個(gè)簡(jiǎn)單的腳本來(lái)處理一些數(shù)據(jù),因?yàn)橹皇呛?jiǎn)單功能所以我就直接使用 print 來(lái)打印日志。

[[399719]]

本文轉(zhuǎn)載自微信公眾號(hào)「crossoverJie」,作者crossoverJie。轉(zhuǎn)載本文請(qǐng)聯(lián)系crossoverJie公眾號(hào)。

前言

最近用 Python 寫(xiě)了幾個(gè)簡(jiǎn)單的腳本來(lái)處理一些數(shù)據(jù),因?yàn)橹皇呛?jiǎn)單功能所以我就直接使用 print 來(lái)打印日志。

任務(wù)運(yùn)行時(shí)偶爾會(huì)出現(xiàn)一些異常:

因?yàn)槲以诓煌胤蕉加写蛴∪罩?,?dǎo)致每次報(bào)錯(cuò)的地方都不太一樣,從而導(dǎo)致程序運(yùn)行結(jié)果非常詭異;有時(shí)候是這段代碼沒(méi)有運(yùn)行,下一次就可能是另外一段代碼沒(méi)有觸發(fā)。

雖說(shuō)當(dāng)時(shí)有注意到 Broken pipe 這個(gè)關(guān)鍵異常,但沒(méi)有特別在意,因?yàn)榇a中也有一些發(fā)送 http 請(qǐng)求的地方,一直以為是網(wǎng)絡(luò) IO 出現(xiàn)了問(wèn)題,壓根沒(méi)往 print 這個(gè)最基本的打印函數(shù)上思考??。

直到這個(gè)問(wèn)題反復(fù)出現(xiàn)我才認(rèn)真看了這個(gè)異常,定睛一看 print 不也是 IO 操作嘛,難道真的是自帶的 print 函數(shù)都出問(wèn)題了?

但在本地、測(cè)試環(huán)境我運(yùn)行無(wú)數(shù)次也沒(méi)能發(fā)現(xiàn)異常;于是我找運(yùn)維拿到了線(xiàn)上的運(yùn)行方式。

原來(lái)為了方便維護(hù)大家提交上來(lái)的腳本任務(wù),運(yùn)維自己有維護(hù)一個(gè)統(tǒng)一的腳本,在這個(gè)腳本中使用:

  1. cmd = 'python /xxx/test.py' 
  2. os.popen(cmd) 

來(lái)觸發(fā)任務(wù),這也是與我在本地、開(kāi)發(fā)環(huán)境的唯一區(qū)別。

popen 原理

為此我在開(kāi)發(fā)環(huán)境模擬出了異常:

test.py:

  1. import time 
  2. if __name__ == '__main__'
  3.     time.sleep(20) 
  4.     print '1000'*1024 

task.py:

  1. import os 
  2. import time 
  3. if __name__ == '__main__'
  4.     start = int(time.time()) 
  5.     cmd = 'python test.py' 
  6.     os.popen(cmd) 
  7.     end = int(time.time()) 
  8.     print 'end****{}s'.format(end-start) 

運(yùn)行:

  1. python task.py 

等待 20s 必然會(huì)復(fù)現(xiàn)這個(gè)異常:

  1. Traceback (most recent call last): 
  2.   File "test.py", line 4, in <module> 
  3.     print '1000'*1024 
  4. IOError: [Errno 32] Broken pipe 

為什么會(huì)出現(xiàn)這個(gè)異常呢?

首先得了解 os.popen(command[, mode[, bufsize]]) 這個(gè)函數(shù)的運(yùn)行原理。

根據(jù)官方文檔的解釋?zhuān)摵瘮?shù)會(huì)執(zhí)行 fork 一個(gè)子進(jìn)程執(zhí)行 command 這個(gè)命令,同時(shí)將子進(jìn)程的標(biāo)準(zhǔn)輸出通過(guò)管道連接到父進(jìn)程;

也就該方法返回的文件描述符。

這里畫(huà)個(gè)圖能更好地理解其中的原理:

在這里的使用場(chǎng)景中并沒(méi)有獲取 popen() 的返回值,所以 command 的執(zhí)行本質(zhì)上是異步的;

也就是說(shuō)當(dāng) task.py 執(zhí)行完畢后會(huì)自動(dòng)關(guān)閉讀取端的管道。

如圖所示,關(guān)閉之后子進(jìn)程會(huì)向 pipe 中輸出 print '1000'*1024,由于這里輸出的內(nèi)容較多會(huì)一下子填滿(mǎn)管道的緩沖區(qū);

于是寫(xiě)入端會(huì)收到 SIGPIPE 信號(hào),從而導(dǎo)致 Broken pipe 的異常。

從維基百科中我們也可以看出這個(gè)異常產(chǎn)生的一些條件:

其中也提到了 SIGPIPE 信號(hào)。

解決辦法

既然知道了問(wèn)題原因,那解決起來(lái)就比較簡(jiǎn)單了,主要有以下幾個(gè)方案:

使用 read() 函數(shù)讀取管道中的數(shù)據(jù),全部讀取之后再關(guān)閉。

如果不需要子進(jìn)程中的輸出時(shí),也可以將 command 的標(biāo)準(zhǔn)輸出重定向到 /dev/null。

也可以使用 Python3 的 subprocess.Popen 模塊來(lái)運(yùn)行。

這里使用第一種方案進(jìn)行演示:

 

  1. import os 
  2. import time 
  3. if __name__ == '__main__'
  4.     start = int(time.time()) 
  5.     cmd = 'python test.py' 
  6.     with os.popen(cmd) as p: 
  7.         print p.read() 
  8.     end = int(time.time()) 
  9.     print 'end****{}s'.format(end-start) 

運(yùn)行 task.py 之后不會(huì)再拋異常,同時(shí)也將 command 的輸出打印出來(lái)。

線(xiàn)上修復(fù)時(shí)我沒(méi)有采用這個(gè)方案,為了方便查看日志,還是使用標(biāo)準(zhǔn)的日志框架將日志輸出到了 es 中,方便統(tǒng)一在 kibana 中進(jìn)行查看。

由于日志框架并沒(méi)有使用到管道,所以自然也不會(huì)有這個(gè)問(wèn)題。

更多內(nèi)容

問(wèn)題雖然是解決了,其中還是涉及到了一些咱們平時(shí)不太注意的知識(shí)點(diǎn),這次我們就來(lái)一起回顧一下。

首先是父子進(jìn)程的內(nèi)容,這個(gè)在 c/c++/python 中比較常見(jiàn),在 Java/golang 中直接使用多線(xiàn)程、協(xié)程會(huì)更多一些。

比如這次提到的 Python 中的 os.popen() 就是創(chuàng)建了一個(gè)子進(jìn)程,既然是子進(jìn)程那肯定是需要和父進(jìn)程進(jìn)行通信才能達(dá)到協(xié)同工作的目的。

很容易想到,父子進(jìn)程之間可以通過(guò)上文提到的管道(匿名管道)來(lái)進(jìn)行通信。

還是以剛才的 Python 程序?yàn)槔?,?dāng)運(yùn)行 task.py 后會(huì)生成兩個(gè)進(jìn)程:

分別進(jìn)入這兩個(gè)程序的/proc/pid/fd 目錄可以看到這兩個(gè)進(jìn)程所打開(kāi)的文件描述符。

父進(jìn)程:

子進(jìn)程:

可以看到子進(jìn)程的標(biāo)準(zhǔn)輸出與父進(jìn)程關(guān)聯(lián),也就是 popen() 所返回的那個(gè)文件描述符。

這里的 0 1 2 分別對(duì)應(yīng)一個(gè)進(jìn)程的stdin(標(biāo)準(zhǔn)輸入)/stdout(標(biāo)準(zhǔn)輸出)/stderr(標(biāo)準(zhǔn)錯(cuò)誤)。

還有一點(diǎn)需要注意的是,當(dāng)我們?cè)诟高M(jìn)程中打開(kāi)的文件描述符,子進(jìn)程也會(huì)繼承過(guò)去;

比如在 task.py 中新增一段代碼:

  1. x = open("1.txt""w"

之后查看文件描述符時(shí)會(huì)發(fā)現(xiàn)父子進(jìn)程都會(huì)有這個(gè)文件:

但相反的,子進(jìn)程中打開(kāi)的文件父進(jìn)程是不會(huì)有的,這個(gè)應(yīng)該很容易理解。

總結(jié)

一些基礎(chǔ)知識(shí)在排查一些詭異問(wèn)題時(shí)顯得尤為重要,比如本次涉及到的父子進(jìn)程的管道通信,最后來(lái)總結(jié)一下:

os.popen() 函數(shù)是異步執(zhí)行的,如果需要拿到子進(jìn)程的輸出,需要自行調(diào)用 read() 函數(shù)。

父子進(jìn)程是通過(guò)匿名管道進(jìn)行通信的,當(dāng)讀取端關(guān)閉時(shí),寫(xiě)入端輸出到達(dá)管道最大緩存時(shí)會(huì)收到 SIGPIPE 信號(hào),從而拋出 Broken pipe 異常。

 

子進(jìn)程會(huì)繼承父進(jìn)程的文件描述符。

 

責(zé)任編輯:武曉燕 來(lái)源: crossoverJie
相關(guān)推薦

2023-09-07 17:06:21

@Autowired報(bào)錯(cuò)原因分析

2024-06-18 13:12:32

2021-08-03 22:26:46

Go函數(shù)分頁(yè)

2022-07-26 16:08:43

print函數(shù)

2009-10-20 09:28:18

VB.NET Prin

2021-07-19 09:58:22

Pythonprint函數(shù)

2019-07-22 09:02:49

工作公司開(kāi)發(fā)

2020-04-28 10:40:54

Python開(kāi)發(fā)工具

2017-07-10 13:31:03

異構(gòu) 存儲(chǔ)

2017-07-14 08:18:08

異構(gòu)存儲(chǔ)復(fù)制

2024-11-05 18:12:04

Python函數(shù)

2022-09-27 10:52:25

Pythonprint函數(shù)

2022-06-24 08:33:13

ECMAScriptjavaScript

2025-01-15 00:00:00

Java內(nèi)省性能

2017-05-10 12:05:17

pyspiderHTMLCSS

2022-11-11 09:41:04

連接池微服務(wù)數(shù)據(jù)庫(kù)

2019-04-24 09:25:14

Python編程語(yǔ)言代碼

2018-08-27 11:40:59

小程序

2025-01-10 08:46:09

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)