2009-06-05 10 views
16

Ho un'applicazione C con molti thread di lavoro. È essenziale che questi non blocchino in modo tale che i thread di lavoro debbano scrivere su un file su disco, li faccio scrivere su un buffer circolare in memoria e quindi avere un thread dedicato per scrivere quel buffer su disco.Come bufferizzare lo stdout in memoria e scriverlo da un thread dedicato

I thread di lavoro non bloccano più. Il thread dedicato può bloccare in modo sicuro durante la scrittura su disco senza influire sui thread di lavoro (non tiene un blocco durante la scrittura su disco). Il mio buffer di memoria è regolato per essere sufficientemente grande che il thread dello scrittore può tenere il passo.

Questo funziona perfettamente. La mia domanda è, come faccio a implementare qualcosa di simile per stdout?

È possibile che macro printf() scriva in un buffer di memoria, ma non ho il controllo su tutto il codice che potrebbe scrivere su stdout (parte delle librerie di terze parti).

Pensieri? NickB

+0

Tutte le soluzioni qui sono sbagliate, perché la scrittura su pipe può bloccare. (E se li si imposta non bloccante, il 'FILE' entrerà in uno stato di errore irreversibile se si bloccherebbe quando deve essere scaricato.) –

risposta

27

Mi piace l'idea di utilizzare freopen. Potresti anche essere in grado di reindirizzare stdout a una pipe utilizzando dup e dup2 e quindi utilizzare read per prelevare i dati dalla pipe.

Qualcosa in questo modo:

#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; 
} 
+0

Sembra che questo risolva il mio problema. Ci proverò. Grazie! – NickB

+4

Quando ho provato un approccio come questo, ho trovato 'leggi' si blocca se non c'è nulla nella pipe. Ma aggiungendo 'long flags = fcntl (out_pipe [0], F_GETFL); flags | = O_NONBLOCK; fcntl (out_pipe [0], F_SETFL, flags); 'alla sezione init ha risolto il problema. – aschepler

+0

Esattamente quello che stavo cercando! – Marcin

4

È possibile "reindirizzare" stdout in file utilizzando freopen().

man freopen dice:

La funzione freopen() apre il file il cui nome è la stringa puntata dal percorso e associa il flusso puntato da flusso con esso. Lo stream originale (se esistente) è chiuso. L'argomento della modalità viene utilizzato proprio come nella funzione fopen(). L'uso principale della funzione freopen() consiste nel modificare il file associato a un flusso di testo standard (stderr, stdin o stdout).

Questo file potrebbe essere un pipe - i thread di lavoro scriveranno su quel pipe e il thread del writer ascolterà.

+0

Questo non risolve il mio problema. Sto cercando di spostare la scrittura del disco fuori dal thread effettuando la chiamata a printf(). Usando freopen() avrei ancora le mie chiamate printf() scrivendo su un file, anche se un file diverso da stdout. È possibile per me specificare un "file" su freopen() che non è un file su disco? – NickB

+0

Sicuro. Usa pipe invece di file. – qrdl

2

Perché non avvolgere l'intera applicazione in un altro? Fondamentalmente, quello che vuoi è uno smart cat che copia stdin su stdout, buffering come necessario. Quindi utilizzare il reindirizzamento standard stdin/stdout. Questo può essere fatto senza modificare la tua attuale applicazione.

~MSalters/# YourCurrentApp | bufcat 
8

Se si sta lavorando con la GNU libc, si potrebbe utilizzare memory streams.

+1

Questa penso sia la vera risposta giusta. L'approccio pipe comporta costose chiamate di sistema e sicuramente bloccherà. –

0

Una soluzione (per entrambe le cose il vostro fare) sarebbe quella di utilizzare una scrittura di raccolta tramite writev.

Ogni thread può ad esempio sprintf in un buffer iovec e quindi passare i puntatori iovec al thread del writer e farlo chiamare semplicemente writev con stdout.

Ecco un esempio di utilizzo di writev da Advanced Unix Programming

In Windows si usa WSASend per una funzionalità simile.

0

Il metodo che utilizza il 4096 bigbuf sarà solo tipo di lavoro. Ho provato questo codice, e mentre cattura con successo lo stdout nel buffer, è inutilizzabile in un caso reale. Non hai modo di sapere per quanto tempo l'output catturato è, quindi non c'è modo di sapere quando terminare la stringa '\ 0'. Se si tenta di utilizzare il buffer si ottengono 4000 caratteri di garbage spit se si sono acquisiti con successo 96 caratteri di output stdout.

Nella mia applicazione, sto usando un interprete perl nel programma C. Non ho idea di quanta uscita verrà sputata da qualsiasi documento venga lanciato nel programma C, e quindi il codice sopra non mi permetterebbe mai di stampare in modo pulito quell'output ovunque.

+0

Se si riempie prima il bigbuf con zeri, allora si garantisce che la stringa sarà terminata con null ... a meno che l'ultimo byte di bigbuf non sia uno zero. Ma in tal caso, è possibile rilevare un overflow. –