聊聊 Linux 的匿名管道

3年前 (2021-09-02)閱讀375回復0
單訪(fǎng)旋
單訪(fǎng)旋
  • 管理員
  • 發(fā)消息
  • 注冊排名3654
  • 經(jīng)驗值55
  • 級別管理員
  • 主題11
  • 回復0
樓主
印刷廠(chǎng)直印加工●彩頁(yè)1000張只需要69元●名片5元每盒-更多產(chǎn)品印刷報價(jià)?聯(lián)系電話(huà):138-1621-1622(微信同號)

  相信很多在linux平臺工作的童鞋, 都很熟悉管道符 '|', 通過(guò)它, 我們能夠很靈活的將幾種不同的命令協(xié)同起來(lái)完成一件任務(wù).就好像下面的命令:

  echo 123 | awk '{print $0+123}' # 輸出246

  不過(guò)這次咱們不來(lái)說(shuō)這些用法, 而是來(lái)探討一些更加有意思的, 那就是 管道兩邊的數據流"實(shí)時(shí)性" 和 管道使用的小提示.

  其實(shí)我們在利用管道的時(shí)候, 可能會(huì )不經(jīng)意的去想, 我前一個(gè)命令的輸出, 是全部處理完再通過(guò)管道傳給第二個(gè)命令, 還是一邊處理一邊輸出呢? 可能在大家是試驗中或者工作經(jīng)驗中, 應該是左邊的命令全部處理完再一次性交給右邊的命令進(jìn)行處理, 不光是大家, 我在最初接觸管道時(shí), 也曾有這么一個(gè)誤會(huì ), 因為我們通過(guò)現象看到的就是這樣.

  但其實(shí)只要有簡(jiǎn)單了解過(guò)管道這工具, 應該都不難得出解釋:

  管道是兩邊是同時(shí)進(jìn)行, 也就是說(shuō), 左邊的命令輸出到管道, 管道的右邊將馬上進(jìn)行處理.

  管道的定義

  管道是由內核管理的一個(gè)緩沖區,相當于我們放入內存中的一個(gè)紙條夏普2608U提示U2-05的錯誤代碼?。管道的一端連接一個(gè)進(jìn)程的輸出。這個(gè)進(jìn)程會(huì )向管道中放入信息。管道的另一端連接一個(gè)進(jìn)程的輸入,這個(gè)進(jìn)程取出被放入管道的信息。一個(gè)緩沖區不需要很大,它被設計成為環(huán)形的數據結構,以便管道可以被循環(huán)利用。當管道中沒(méi)有信息的話(huà),從管道中讀取的進(jìn)程會(huì )等待,直到另一端的進(jìn)程放入信息。當管道被放滿(mǎn)信息的時(shí)候,嘗試放入信息的進(jìn)程會(huì )堵塞,直到另一端的進(jìn)程取出信息。當兩個(gè)進(jìn)程都終結的時(shí)候,管道也自動(dòng)消失。

  管道工作流程圖

  通過(guò)上面的解釋可以看到, 假設 COMMAND1 | COMMAND2, 那么COMMAND1的標準輸出, 將會(huì )被綁定到管道的寫(xiě)端, 而COMMAND2的標準輸入將會(huì )綁定到管道的讀端, 所以當COMMAND1一有輸出, 將會(huì )馬上通過(guò)管道傳給COMMAND2, 我們先來(lái)做個(gè)實(shí)驗驗證下:

  # 1.py

  import time

  import sys

  while 1:

  print '1111'

  time.sleep(3)

  print '2222'

  time.sleep(3)

  [root@iZ23pynfq19Z ~]# python 1 | cat

  在上面的命令, 我們可以猜測下輸出結果: 究竟是 睡眠6秒之后, 輸出"1111222", 還是輸出 "1111" 睡眠3秒, 再輸出 "2222", 然后再睡眠3秒, 再輸出"1111" 呢? 答案就是: 都不是! what! 這不可能, 大家可以嘗試下, 我們會(huì )看到終端沒(méi)反應了, 為什么呢? 這就要涉及到文件IO的緩沖方式了,關(guān)于文件IO, 可以參考我的另一篇文章: 淺談文件描述符1和2, 在最下面的地方提到文件IO的三種緩沖方式:

  全緩沖: 直到緩沖區被填滿(mǎn)夏普2608U提示U2-05的錯誤代碼?,才調用系統I/O函數, (一般是針對文件)

  行緩沖: 遇到換行符就輸出(標準輸出)

  無(wú)緩沖: 沒(méi)有緩沖區夏普2608U提示U2-05的錯誤代碼?,數據會(huì )立即讀入或者輸出到外存文件和設備上(標準錯誤

  因為python是默認采用帶緩沖的fputs(參考py27源碼: fileobject.c: PyFile_WriteString函數), 又因為標準輸出被改寫(xiě)到管道, 所以將會(huì )采取全緩沖的方式(shell 命令具體要看實(shí)現, 因為有些是用不帶緩沖write實(shí)現,如果不帶緩沖區,會(huì )直接寫(xiě)入管道), 所以將會(huì )采取全緩沖的方式, 也就是說(shuō), 直到緩沖區被填滿(mǎn), 或者手動(dòng)顯示調用flush刷入,才能看到輸出.那我們可以將代碼改寫(xiě)成下面兩種方式吧

  # 方式1: 填滿(mǎn)緩沖區, 我這邊大小是4096字節, 你們也可以試下這個(gè)值, 估計都一樣

  import time

  import sys

  while 1:

  print '1111' * 4096

  time.sleep(3)

  print '2222' * 4096

  time.sleep(3)

  # 方式2: 手動(dòng)刷入寫(xiě)隊列

  import time

  import sys

  while 1:

  print '1111'

  sys.stdout.flush() // 因為是標準輸出, 所以直接通過(guò)sys的接口去flush

  time.sleep(3)

  print '2222'

  sys.stdout.flush()

  time.sleep(3)

  輸出結果:

  # 第一種方式:

  [root@iZ23pynfq19Z ~]# python 1 | cat

  1111.....(超多1, 刷屏了..)

  睡眠3秒..

  2222.....(超多2, 刷屏了..)

  # 第二種方式:

  [root@iZ23pynfq19Z ~]# python 1 | cat

  1111

  睡眠3秒..

  2222

  睡眠3秒..

  1111

  在這里我們已經(jīng)能夠得出結果, 如果像我們以前所想的那樣, 要等到COMMAND1全部執行完才一次性輸出給COMMAND2, 那么結果應該是無(wú)限堵塞..因為我的程序一直沒(méi)有執行完..這樣應該是不符合老前輩們設計初衷的, 因為這樣可能會(huì )導致管道越來(lái)越大..然而管道也是有大小的~ 具體可以去看posix標準, 所以我們得出結論是: 只要COMMAND1的輸出寫(xiě)入管道的寫(xiě)端(不管是緩沖區滿(mǎn)還是手動(dòng)flush), COMMAND2都將立刻得到數據并且馬上處理.

  那么 管道兩邊的數據流"實(shí)時(shí)性" 討論到就先暫告一段落, 接下來(lái)將在這個(gè)基礎上繼續討論: 管道使用的小提示.

  在開(kāi)始討論前, 我想先引入一個(gè)專(zhuān)業(yè)術(shù)語(yǔ), 也是我們偶爾會(huì )遇到的, 那就是: SIGPIPE 或者是一個(gè)更加具體的描述: broken pipe (管道破裂)

  上面的專(zhuān)業(yè)術(shù)語(yǔ)都是跟管道讀寫(xiě)規則息息相關(guān)的, 那咱們來(lái)看下 管道的讀寫(xiě)規則吧:

  當沒(méi)有數據可讀時(shí)

  O_NONBLOCK (未設置):read調用阻塞,即進(jìn)程暫停執行,一直等到有數據來(lái)到為止夏普2608U提示U2-05的錯誤代碼?。

  O_NONBLOCK ( 設置 ) :read調用返回-1,errno值為EAGAIN夏普2608U提示U2-05的錯誤代碼?。

  當管道滿(mǎn)的時(shí)候

  O_NONBLOCK (未設置):write調用阻塞夏普2608U提示U2-05的錯誤代碼?,直到有進(jìn)程讀走數據

  O_NONBLOCK ( 設置 ):調用返回-1夏普2608U提示U2-05的錯誤代碼?,errno值為EAGAIN

  如果所有管道寫(xiě)端對應的文件描述符被關(guān)閉夏普2608U提示U2-05的錯誤代碼?,則read返回0

  如果所有管道讀端對應的文件描述符被關(guān)閉夏普2608U提示U2-05的錯誤代碼?,則write操作會(huì )產(chǎn)生信號SIGPIPE

  當要寫(xiě)入的數據量不大于PIPE_BUF時(shí),linux將保證寫(xiě)入的原子性夏普2608U提示U2-05的錯誤代碼?。

  當要寫(xiě)入的數據量大于PIPE_BUF時(shí),linux將不再保證寫(xiě)入的原子性夏普2608U提示U2-05的錯誤代碼?。

  在上面我們可以看到, 如果我們收到SIGPIPE信號, 那么一般情況就是讀端被關(guān)閉, 但是寫(xiě)端卻依舊嘗試寫(xiě)入

  咱們來(lái)重現下 SIGPIPE

  #!/usr/bin/python

  import time

  import sys

  while 1:

  time.sleep(10) # 手速不夠快的童鞋可以將睡眠時(shí)間設置長(cháng)點(diǎn)

  print '1111'

  sys.stdout.flush()

  這次執行命令需要考驗手速了, 因為我們要趕在py醒過(guò)來(lái)之前, 將讀端進(jìn)程殺掉

  python 1 | cat

  # 另一個(gè)終端

  [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'cat|python'

  root 10775 4074 0 00:05 pts/2 00:00:00 python 1

  root 10776 4074 0 00:05 pts/2 00:00:00 cat # 讀端進(jìn)程

  root 10833 32581 0 00:06 pts/0 00:00:00 grep -P cat|python

  [root@iZ23pynfq19Z ~]# kill 10776

  輸出結果

  [root@iZ23pynfq19Z ~]# python 1 | cat

  Traceback (most recent call last):

  File "1", line 6, in module

  sys.stdout.flush()

  IOError: [Errno 32] Broken pipe

  Terminated

  從上圖我們可以驗證兩個(gè)點(diǎn):

  當我們殺掉讀端時(shí), 寫(xiě)端會(huì )收到SIGPIPE而默認退出, 管道結束

  當我們殺掉讀端時(shí), 寫(xiě)端的程序并不會(huì )馬上收到SIGPIPE, 相反的, 只有真正寫(xiě)入管道寫(xiě)端時(shí)才會(huì )觸發(fā)這個(gè)錯誤

  如果寫(xiě)入一個(gè) 讀端已經(jīng)關(guān)閉的管道, 將會(huì )收到一個(gè) SIGPIPE, 那讀一個(gè)寫(xiě)端已經(jīng)關(guān)閉的管道又會(huì )這樣呢?

  import time

  import sys

  # 這次我們不需要死循環(huán), 因為我們想要寫(xiě)端快點(diǎn)關(guān)閉退出

  time.sleep(5)

  print '1111'

  sys.stdout.flush()

  # 因為我們想要 讀端 等到足夠長(cháng)的時(shí)間, 讓寫(xiě)端關(guān)閉, 所以我們需要利用awk先睡眠10秒

  [root@iZ23pynfq19Z ~]# python 1.py | awk '{system("sleep 10");print 123}'

  [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'awk|python'

  root 11717 4074 0 00:20 pts/2 00:00:00 python 1.py

  root 11718 4074 0 00:20 pts/2 00:00:00 awk {system("sleep 10");print 123}

  root 11721 32581 0 00:20 pts/0 00:00:00 grep -P awk|python

  # 5秒過(guò)后

  [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'awk|python'

  root 11685 4074 0 00:20 pts/2 00:00:00 awk {system("sleep 10");print 123}

  root 11698 32581 0 00:20 pts/0 00:00:00 grep -P awk|python

  # 10秒過(guò)后

  [root@iZ23pynfq19Z ~]# python 1 | awk '{system("sleep 10");print 123}'

  123

  在上面也已經(jīng)證明了上文提到的讀寫(xiě)規則: 如果所有管道寫(xiě)端對應的文件描述符被關(guān)閉,將產(chǎn)生EOF結束標志,read返回0, 程序退出夏普2608U提示U2-05的錯誤代碼?。

  總結

  通過(guò)上面的理論和實(shí)驗, 我們知道在使用管道時(shí), 兩邊命令的數據傳輸過(guò)程, 以及對管道讀寫(xiě)規則有了初步的認識, 希望我們以后在工作時(shí), 再接觸管道時(shí), 能夠更加有把握的去利用這一強大的工具夏普2608U提示U2-05的錯誤代碼?。

0
0
收藏0
回帖

聊聊 Linux 的匿名管道 期待您的回復!

取消
載入表情清單……
載入顏色清單……
插入網(wǎng)絡(luò )圖片

取消確定

圖片上傳中
編輯器信息
提示信息