亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何檢查標準輸出是否已關閉 - 不向其寫入數據?

如何檢查標準輸出是否已關閉 - 不向其寫入數據?

Go
LEATH 2022-07-11 16:25:15
我編寫了一個程序,它讀取數據、過濾和處理數據并將其寫入標準輸出。如果標準輸出通過管道傳輸到另一個進程,并且管道進程終止,我得到 SIGPIPEd,這很好,因為程序終止,并且管道及時結束。然而,根據過濾器參數,可能有幾十秒沒有一次寫入,并且在此期間不會有 SIGPIPE,盡管下游進程早已完成。我怎樣才能檢測到這一點,而無需實際向標準輸出寫入內容?目前,管道只是掛起,直到我的程序因自然原因終止。我試著寫一個零長度切片if _, err := os.Stdout.Write([]byte{}); err != nil但不幸的是,這不會導致錯誤。NB 理想情況下,無論平臺如何,這都應該有效,但如果它僅適用于 Linux,那已經是一種改進。
查看完整描述

2 回答

?
慕碼人2483693

TA貢獻1860條經驗 獲得超9個贊

這在 Go 中沒有回答,但你可能會找到一種使用它的方法。如果您可以將 Poll(2) 應用于管道的寫入端,您將在它變得不可寫時收到通知。如何將其集成到您的 Go 代碼中取決于您的程序;希望它可能有用:


#include <errno.h>

#include <poll.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>


void sp(int sno) {

    write(2, "sigpipe!\n", 9);

    _exit(1);

}


int waitfd(int fd) {

    int n;

    struct pollfd p;

    p.fd = fd;

    p.events = POLLOUT | POLLRDBAND;

    /* RDBAND is for what looks like a bug in illumos fifovnops.c */

    p.revents = 0;

    if ((n=poll(&p, 1, -1)) == 1) {

        if (p.revents & POLLOUT) {

            return fd;

        }

        if (p.revents & (POLLERR|POLLHUP)) {

            return -1;

        }

    }

    fprintf(stderr, "poll=%d (%d:%s), r=%#x\n",

        n, errno, strerror(errno), p.revents);

    return -1;

}


int main() {

    int count = 0;

    char c;

    signal(SIGPIPE, sp);

    while (read(0, &c, 1) > 0) {

        int w;

        while ((w=waitfd(1)) != -1 &&

            write(1, &c, 1) != 1) {

        }

        if (w == -1) {

            break;

        }

        count++;

    }

    fprintf(stderr, "wrote %d\n", count);

    return 0;

}

在 linux 中,您可以將這個程序運行為:./a.out < /dev/zero | sleep 1,它會打印出類似的內容:wrote 61441。您可以將其更改為休眠 3 秒,它會打印相同的內容。這是一個很好的證據,它已經填滿了管道,正在等待空間。睡眠永遠不會從管道中讀取,因此當它的時間到時,它會關閉讀取端,這會用 POLLERR 事件喚醒 poll(2)。


如果將輪詢事件更改為不包括 POLLOUT,您將獲得更簡單的程序:


#include <errno.h>

#include <fcntl.h>

#include <poll.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>


int waitfd(int fd) {

    int n;

    struct pollfd p;

    p.fd = fd;

    p.events = POLLRDBAND;

    p.revents = 0;

    if ((n=poll(&p, 1, -1)) == 1) {

        if (p.revents & (POLLERR|POLLHUP)) {

            return -1;

        }

    }

    fprintf(stderr, "poll=%d (%d:%s), r=%#x\n",

        n, errno, strerror(errno), p.revents);

    return -1;

}


int main() {

    if (waitfd(1) == -1) {

        fprintf(stderr, "Got an error!\n");

    }

    return 0;

}

“出錯了!” 表示管道已關閉。我不知道這是多么可移植,因為 poll(2) 文檔有點粗略。如果沒有 POLLRDBAND(所以 events 為 0),這適用于 Linux,但不適用于 UNIX(至少 Solaris 和 macos)。再一次,文檔沒用,但是內核源代碼回答了很多問題:)


這個例子,使用線程,可以直接映射到go:


#include <pthread.h>

#include <errno.h>

#include <poll.h>

#include <signal.h>

#include <stdio.h>

#include <stdint.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>


int Events = POLLRDBAND;


void sp(int sno) {

    char buf[64];

    write(2, buf, snprintf(buf, sizeof buf, "%d: sig%s(%d)\n", getpid(), sys_siglist[sno], sno));

    _exit(1);

}


int waitfd(int fd) {

    int n;

    struct pollfd p;

    p.fd = fd;

    p.events = Events;

    /* RDBAND is for what looks like a bug in illumos fifovnops.c */

    p.revents = 0;

    if ((n=poll(&p, 1, -1)) == 1) {

        if (p.revents & (POLLERR|POLLHUP)) {

            return -1;

        }

        return fd;

    }

    return -1;

}


void *waitpipe(void *t) {

    int x = (int)(intptr_t)t; /*gcc braindead*/

    waitfd(x);

    kill(getpid(), SIGUSR1);

    return NULL;

}


int main(int ac) {

    pthread_t killer;

    int count = 0;

    char c;

    

    Events |= (ac > 1) ? POLLOUT : 0;


    signal(SIGPIPE, sp);

    signal(SIGUSR1, sp);


    pthread_create(&killer, 0, waitpipe, (int *)1);

    while (read(0, &c, 1) > 0) {

        write(1, &c, 1);

        count++;

    }

    fprintf(stderr, "wrote %d\n", count);

    return 0;

}

請注意,它會在 poll 上駐留一個線程,并生成一個 SIGUSR1。這是運行它:


mcloud:pipe $ ./spthr < /dev/zero | hexdump -n80

0000000 0000 0000 0000 0000 0000 0000 0000 0000

*

0000050

185965: sigUser defined signal 1(10)

mcloud:pipe $ ./spthr < /dev/zero | sleep 1

185969: sigUser defined signal 1(10)

mcloud:pipe $ ./spthr | sleep 1

185972: sigUser defined signal 1(10)

mcloud:pipe $ ./spthr < /dev/zero | hexdump -n800000

0000000 0000 0000 0000 0000 0000 0000 0000 0000

*

00c3500

185976: sigBroken pipe(13)

在第一個命令中,hexdump 在 80 字節后退出,輪詢基本上是在與 read+write 循環競爭,因此它可能生成了一個 sigpipe 或 sigusr1。


后兩個演示了 sleep 將導致 sigusr1 (輪詢返回異常事件),無論管道讀取器退出時管道的寫入端是否已滿。


第四,使用 hexdump 讀取大量數據,遠遠超過管道容量,這更確定性地導致了 sigpipe。


您可以生成更準確地對其建模的測試程序,但關鍵是一旦管道關閉,程序就會收到通知;不必等到下一次寫入。


查看完整回答
反對 回復 2022-07-11
?
千巷貓影

TA貢獻1829條經驗 獲得超7個贊

這不是問題的真正解決方案 - 即,檢測管道中的進程是否在沒有寫入的情況下終止 - 但這是一個解決方法,在 Daniel Farrell 的評論中建議:(定義和)使用將被忽略的心跳信號下游。

由于此解決方法不透明,因此如果您不控制所涉及的所有進程,則可能無法實現。

這是一個使用 NUL 字節作為基于文本數據的心跳信號的示例:

my-cmd | head -1 | tr -d '\000' > file

my-cmd 將在不活動時發送 NUL 字節以獲得及時的 EPIPE / SIGPIPE。

請注意,tr一旦達到其目的,使用 就再次剝離心跳 - 否則它們最終會出現在file.


查看完整回答
反對 回復 2022-07-11
  • 2 回答
  • 0 關注
  • 133 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號