Как буферизовать стандартный вывод в памяти и записать его из выделенного потока - PullRequest
20 голосов
/ 05 июня 2009

У меня есть приложение C со многими рабочими потоками. Важно, чтобы они не блокировались, поэтому, когда рабочим потокам нужно записать файл на диск, я записал их в кольцевой буфер в памяти, а затем выделил поток для записи этого буфера на диск.

Рабочие потоки больше не блокируются. Выделенный поток может безопасно блокировать во время записи на диск, не затрагивая рабочие потоки (он не удерживает блокировку во время записи на диск). Мой буфер памяти настроен так, чтобы он был достаточно большим, чтобы поток писателя мог его поддерживать.

Все это прекрасно работает. У меня вопрос, как мне реализовать нечто подобное для stdout?

Я мог бы использовать макрос printf () для записи в буфер памяти, но у меня нет контроля над всем кодом, который может записываться в стандартный вывод (часть из них находится в сторонних библиотеках).

Мысли? NickB

Ответы [ 7 ]

33 голосов
/ 05 июня 2009

Мне нравится идея использования freopen. Вы также можете перенаправить stdout в канал, используя dup и dup2 , а затем использовать 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;
}
8 голосов
/ 05 июня 2009

Если вы работаете с GNU libc, вы можете использовать потоки памяти .

4 голосов
/ 05 июня 2009

Вы можете "перенаправить" stdout в файл, используя freopen().

man freopen говорит:

Функция freopen () открывает файл чье имя является строкой, на которую указывает по пути и связывает поток указал потоком с ним. оригинальный поток (если он существует) закрыто. Аргумент mode используется как в функции fopen (). Основное использование freopen () функция состоит в том, чтобы изменить файл связан со стандартным текстом поток (stderr, stdin или stdout).

Этот файл может быть каналом - рабочие потоки будут писать в этот канал, а поток записи будет слушать.

2 голосов
/ 05 июня 2009

Почему бы вам не обернуть все ваше приложение в другое? По сути, вам нужен умный cat, который копирует стандартный ввод в стандартный вывод, буферизуя при необходимости Затем используйте стандартное перенаправление stdin / stdout. Это можно сделать без изменения вашего текущего приложения.

~MSalters/# YourCurrentApp | bufcat
0 голосов
/ 26 апреля 2010

Метод, использующий 4096 bigbuf, будет только работать. Я пробовал этот код, и хотя он успешно захватывает стандартный вывод в буфер, он не может использоваться в реальных условиях. У вас нет способа узнать, как долго захватывается вывод, поэтому нет способа узнать, когда завершать строку '\ 0'. Если вы попытаетесь использовать буфер, вы получите 4000 символов мусора, если вы успешно перехватили 96 символов вывода stdout.

В моем приложении я использую интерпретатор perl в программе на Си. Я понятия не имею, сколько выходных данных будет выплеснуто из того, что когда-либо будет брошено в программу на C, и, следовательно, приведенный выше код никогда не позволит мне печатать этот вывод в любом месте.

0 голосов
/ 05 июня 2009

Одним из решений (для обеих ваших задач) было бы использование записи сбора через writev .

Каждый поток может, например, sprintf в буфер iovec, а затем передавать указатели iovec потоку писателя и просто вызывать writev с помощью stdout.

Вот пример использования writev из Расширенное программирование Unix

В Windows вы бы использовали WSAsend для аналогичной функциональности.

0 голосов
/ 05 июня 2009

Вы можете изменить работу буферизации с setvbuf() или setbuf(). Здесь есть описание: http://publications.gbdirect.co.uk/c_book/chapter9/input_and_output.html.

[Изменить]

stdout на самом деле FILE*. Если существующий код работает с FILE* s, я не вижу, что мешает ему работать с stdout.

...