C 如何在内存中缓冲标准输出并从专用线程写入

C 如何在内存中缓冲标准输出并从专用线程写入,c,file,stdout,buffering,C,File,Stdout,Buffering,我有一个具有许多工作线程的C应用程序。重要的是,这些线程不会阻塞,因此当工作线程需要写入磁盘上的文件时,我让它们写入内存中的循环缓冲区,然后有一个专用线程将该缓冲区写入磁盘 工作线程不再阻塞。专用线程在写入磁盘时可以安全地阻塞,而不会影响工作线程(在写入磁盘时它不会持有锁)。我的内存缓冲区被调整为足够大,写入线程可以跟上 这一切都很好。我的问题是,如何为stdout实现类似的东西 我可以通过宏printf()写入内存缓冲区,但我无法控制可能写入stdout的所有代码(其中一些代码位于第三方库中)

我有一个具有许多工作线程的C应用程序。重要的是,这些线程不会阻塞,因此当工作线程需要写入磁盘上的文件时,我让它们写入内存中的循环缓冲区,然后有一个专用线程将该缓冲区写入磁盘

工作线程不再阻塞。专用线程在写入磁盘时可以安全地阻塞,而不会影响工作线程(在写入磁盘时它不会持有锁)。我的内存缓冲区被调整为足够大,写入线程可以跟上

这一切都很好。我的问题是,如何为stdout实现类似的东西

我可以通过宏printf()写入内存缓冲区,但我无法控制可能写入stdout的所有代码(其中一些代码位于第三方库中)

想法? NickB

您可以使用
freopen()
stdout
重定向到文件中

man freopen
说:

函数的作用是:打开文件 字符串指向谁的名字 通过路径并关联流 用它指着溪流。这个 原始流(如果存在)为 关闭使用mode参数 就像在fopen()函数中一样。 freopen()的主要用途 函数用于更改文件 与标准文本关联 流(stderr、stdin或stdout)


此文件很可能是管道-工作线程将写入该管道,写入线程将侦听。

您可以使用
setvbuf()
setbuf()
更改缓冲的工作方式。这里有一个描述:

[编辑]


stdout
实际上是一个
文件*
。如果现有代码与
文件*
一起工作,我看不出是什么阻止它与
标准输出一起工作

为什么不将整个应用程序包装到另一个文件中?基本上,您需要的是一个智能的
cat
,它将stdin复制到stdout,并根据需要进行缓冲。然后使用标准stdin/stdout重定向。这可以在不修改当前应用程序的情况下完成

~MSalters/# YourCurrentApp | bufcat

如果您正在使用GNULIBC,您可能会使用。

一个解决方案(对于您所做的两件事)是使用一个收集写通孔

例如,每个线程都可以将sprintf放入iovec缓冲区,然后将iovec指针传递给writer线程,让它用stdout简单地调用writev

下面是一个使用writev from的示例


在Windows下,您可以使用WSAsend实现类似的功能。

我喜欢使用
freopen
的想法。您还可以使用and将
stdout
重定向到管道,然后使用
read
从管道中获取数据

大概是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_LEN 40

int main( int argc, char *argv[] ) {
  char buffer[MAX_LEN+1] = {0};
  int out_pipe[2];
  int saved_stdout;

  saved_stdout = dup(STDOUT_FILENO);  /* save stdout for display later */

  if( pipe(out_pipe) != 0 ) {          /* make a pipe */
    exit(1);
  }

  dup2(out_pipe[1], STDOUT_FILENO);   /* redirect stdout to the pipe */
  close(out_pipe[1]);

  /* anything sent to printf should now go down the pipe */
  printf("ceci n'est pas une pipe");
  fflush(stdout);

  read(out_pipe[0], buffer, MAX_LEN); /* read from pipe into buffer */

  dup2(saved_stdout, STDOUT_FILENO);  /* reconnect stdout for testing */
  printf("read: %s\n", buffer);

  return 0;
}
#包括
#包括
#包括
#定义最大长度40
int main(int argc,char*argv[]){
字符缓冲区[MAX_LEN+1]={0};
int out_管道[2];
int保存的标准输出;
saved_stdout=dup(stdout_FILENO);/*保存stdout供以后显示*/
如果(管道)!=0{/*制作管道*/
出口(1);
}
dup2(输出管道[1],标准输出文件号);/*将标准输出重定向到管道*/
关闭(管道[1]);
/*发送到printf的任何内容现在都应该被传送到管道中*/
printf(“ceci n'est pas une管道”);
fflush(stdout);
读取(输出管道[0],缓冲区,最大长度);/*从管道读取到缓冲区*/
dup2(已保存的标准输出,标准输出文件号);/*重新连接标准输出以进行测试*/
printf(“读取:%s\n”,缓冲区);
返回0;
}

使用4096 bigbuf的方法只会起到某种作用。我尝试过这段代码,虽然它成功地将stdout捕获到缓冲区中,但在现实世界中它是不可用的。您无法知道捕获的输出有多长,因此无法知道何时终止字符串“\0”。如果您尝试使用该缓冲区,那么如果您成功捕获了96个字符的标准输出,您将得到4000个字符的垃圾


在我的应用程序中,我在C程序中使用perl解释器。我不知道在C程序中抛出的文档会有多少输出,因此上面的代码不允许我在任何地方清晰地打印输出。

这并不能解决我的问题。我正在尝试将磁盘从执行printf()调用的线程中移出。使用freopen()仍会使printf()调用写入文件,尽管与stdout的文件不同。我可以为freopen()指定一个不是磁盘文件的“文件”吗?当然可以。使用管道而不是文件。这听起来好像解决了我的问题。我试试看。非常感谢。当我尝试这种方法时,我发现如果管道中没有任何内容,则
read
会挂起。但是添加
long flags=fcntl(out_pipe[0],F_GETFL);标志|=O|U非块;fcntl(输出管道[0],F\U设置FL,标志)到初始化部分修复了这个问题。正是我要找的!很好的方法,但是如何在Windows中使用Similar?@NateKohl Hey Nate,很棒的代码,你能为两个不同的函数提供一个例子吗?一个读取另一个的输出,另一个读取另一个的输出。这里所有的解决方案都是错误的,因为写入管道会阻塞。(如果您将它们设置为非阻塞,那么
文件
将进入不可恢复的错误状态,如果它在需要刷新时阻塞。)我认为这是真正正确的答案。管道方法涉及昂贵的系统调用,肯定会阻塞。链接已过期,请改用此方法。如果先用零填充bigbuf,则可以保证字符串将以null结尾。。。除非bigbuf的最后一个字节不是零。但在这种情况下,您可以检测到溢出。