2 回答

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

TA貢獻1829條經驗 獲得超7個贊
這不是問題的真正解決方案 - 即,檢測管道中的進程是否在沒有寫入的情況下終止 - 但這是一個解決方法,在 Daniel Farrell 的評論中建議:(定義和)使用將被忽略的心跳信號下游。
由于此解決方法不透明,因此如果您不控制所涉及的所有進程,則可能無法實現。
這是一個使用 NUL 字節作為基于文本數據的心跳信號的示例:
my-cmd | head -1 | tr -d '\000' > file
my-cmd 將在不活動時發送 NUL 字節以獲得及時的 EPIPE / SIGPIPE。
請注意,tr
一旦達到其目的,使用 就再次剝離心跳 - 否則它們最終會出現在file
.
- 2 回答
- 0 關注
- 133 瀏覽
添加回答
舉報