实时写入的多FILE指针(stdin、popen)fgets阻塞问题解决方法探讨
问题解决思路
惯用解决方法
核心方案是使用I/O多路复用机制,Unix/Linux下常用select()、poll()或更高效的epoll(),Windows下则可用WaitForMultipleObjects()。这类机制能同时监听多个文件描述符,直到至少一个有数据可读,再针对性地执行读取操作,避免单线程下交替调用fgets()导致的阻塞问题。
具体实现步骤:
- 从FILE指针提取对应文件描述符:用
fileno()函数,例如int fd_stdin = fileno(stdin);、int fd_popen = fileno(popen_fp);(popen_fp为popen返回的FILE指针)。 - 初始化文件描述符集合:通过
fd_set结构将需要监听的文件描述符加入集合。 - 调用多路复用函数:以
select()为例,传入最大文件描述符+1、读集合、NULL、NULL、超时参数(若无需超时则传NULL),函数会阻塞直到至少一个描述符可读。 - 检查就绪描述符:根据返回结果判断哪个FILE指针有数据,再调用
fgets()读取(此时fgets()不会阻塞,因为对应描述符已就绪)。
是否必须使用多线程?
不是必须的。I/O多路复用是单线程处理多源I/O的经典方案,代码更简洁,还能避开多线程带来的同步问题(如共享数据锁、线程安全校验等)。当然,如果场景中除了I/O还有大量计算任务,多线程/多进程可作为备选,但单纯处理多源I/O监听时,多路复用的效率更高、维护成本更低。
如何阻塞至至少一个FILE存在可用输入?
直接借助上述I/O多路复用函数即可实现,以下是Unix/Linux环境的简化示例代码:
#include <stdio.h> #include <sys/select.h> int main() { FILE *popen_fp = popen("some_command", "r"); int fd_stdin = fileno(stdin); int fd_popen = fileno(popen_fp); int max_fd = (fd_stdin > fd_popen) ? fd_stdin : fd_popen; fd_set read_fds; char buf[256]; while (1) { FD_ZERO(&read_fds); FD_SET(fd_stdin, &read_fds); FD_SET(fd_popen, &read_fds); // 阻塞直到至少一个文件描述符可读 int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ret == -1) { perror("select error"); break; } if (FD_ISSET(fd_stdin, &read_fds)) { if (fgets(buf, sizeof(buf), stdin) != NULL) { printf("From stdin: %s", buf); } } if (FD_ISSET(fd_popen, &read_fds)) { if (fgets(buf, sizeof(buf), popen_fp) != NULL) { printf("From popen: %s", buf); } else { // popen关联的命令执行完毕,关闭指针 pclose(popen_fp); break; } } } return 0; }
注意事项:
select()的fd_set有默认最大限制(通常为FD_SETSIZE,默认值1024),若需监听更多描述符,建议改用epoll()(Linux)或kqueue()(BSD/macOS)。- 混用stdio函数(如
fgets)和底层系统调用(如read)时,需注意stdio缓存可能导致的数据不同步问题,可通过fflush()或禁用缓存(setvbuf(fp, NULL, _IONBF, 0))规避。
内容的提问来源于stack exchange,提问作者Tom Huntington
相关产品推荐
相关产品推荐

