2009-06-05 11 views
16

Ich habe eine C-Anwendung mit vielen Worker-Threads. Es ist wichtig, dass diese nicht blockiert werden, so dass die Worker-Threads in eine Datei auf der Festplatte schreiben müssen. Ich schreibe sie in einen Ringpuffer im Speicher und dann einen dedizierten Thread, um diesen Puffer auf die Platte zu schreiben.Wie stdout im Speicher puffern und es von einem dedizierten Thread schreiben

Die Worker-Threads blockieren nicht mehr. Der dedizierte Thread kann beim Schreiben auf die Festplatte sicher blockieren, ohne die Worker-Threads zu beeinflussen (er hält beim Schreiben auf die Festplatte keine Sperre). Mein Speicherpuffer ist so groß eingestellt, dass der Writer-Thread mithalten kann.

Das alles funktioniert super. Meine Frage ist, wie implementiere ich etwas ähnliches für stdout?

Ich könnte Makro printf() in einen Speicherpuffer schreiben, aber ich habe keine Kontrolle über den gesamten Code, der auf stdout schreiben könnte (einige davon ist in Bibliotheken von Drittanbietern).

Gedanken? NickB

+0

Alle Lösungen hier sind falsch, weil das Schreiben in Rohrleitungen blockieren kann. (Und wenn Sie sie nicht blockieren, wird die 'FILE' nur in einen nicht behebbaren Fehlerzustand geraten, wenn sie blockieren würde, wenn sie gespült werden muss.) –

Antwort

27

Ich mag die Idee der Verwendung freopen. Sie können möglicherweise auch stdout zu einer Pipe umleiten, indem Sie dup und dup2 verwenden und dann read verwenden, um Daten aus der Pipe abzurufen.

So etwas so:

#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

Das klingt wie es mein Problem löst. Ich werde es ausprobieren. Vielen Dank! – NickB

+4

Als ich einen Ansatz wie diesen versuchte, fand ich "gelesen", wenn nichts in der Pipe ist. Aber das Hinzufügen von 'long flags = fcntl (out_pipe [0], F_GETFL); Flags = O_NONBLOCK; fcntl (out_pipe [0], F_SETFL, flags); 'zum init-Abschnitt hat das Problem behoben. – aschepler

+0

Genau das, was ich gesucht habe! – Marcin

4

Sie können stdout in Datei umleiten mit freopen().

man freopen sagt:

Die freopen() Funktion öffnet die Datei , dessen Name der String durch Pfad zeigte und ordnet den Strom zu mit ihm durch den Strom gerichtet. Der Original-Stream (falls vorhanden) ist geschlossen. Das mode-Argument wird wie in der Funktion fopen() verwendet. Die primäre Verwendung der Funktion freopen() ist, die Datei zu ändern, die einem Standardtext Stream (stderr, stdin oder stdout) zugeordnet ist.

Diese Datei kann eine Pipe sein - Worker-Threads schreiben auf diese Pipe, und der Writer-Thread hört zu.

+0

Das löst mein Problem nicht. Ich versuche, die Festplatte von dem Thread zu verschieben, der den printf() - Aufruf ausführt. Die Verwendung von freopen() würde immer noch meine printf() -Aufrufe in eine Datei schreiben, wenn auch eine andere Datei als stdout. Ist es mir möglich, eine "Datei" für freopen() anzugeben, die keine Datei ist? – NickB

+0

Sicher. Verwenden Sie eine Pipe anstelle einer Datei. – qrdl

2

Warum verpacken Sie nicht Ihre gesamte Anwendung in einem anderen? Im Grunde, was Sie wollen, ist eine intelligente cat, die stdin nach stdout, Pufferung wie nötig kopiert. Verwenden Sie dann die Standard-Umleitung stdin/stdout. Dies kann durchgeführt werden, ohne Ihre aktuelle Anwendung zu ändern.

~MSalters/# YourCurrentApp | bufcat 
8

Wenn Sie mit dem GNU libc arbeiten, könnten Sie memory streams verwenden.

+1

Das ist meiner Meinung nach die richtige Antwort. Der Pipe-Ansatz beinhaltet teure Systemaufrufe und wird sicherlich blockieren. –

0

Eine Lösung (für beide Dinge, die Sie tun) wäre eine Sammel schreiben über writev.

Jeder Thread könnte zum Beispiel sprintf in einen IOVEC-Puffer und dann die IOVEC-Zeiger an den Writer-Thread übergeben und einfach Writev mit STDOUT aufrufen.Hier

ist ein Beispiel für die Verwendung von writev Advanced Unix Programming

Unter Windows Sie WSASend für ähnliche Funktionalität verwenden würde.

0

Die Methode, um die 4096 bigbuf Verwendung wird nur Art von Arbeit. Ich habe diesen Code ausprobiert, und während es stdout in den Puffer erfolgreich feststellt, ist es in einem realen Fall unbrauchbar. Sie haben keine Möglichkeit zu wissen, wie lange die erfasste Ausgabe ist, also können Sie nicht wissen, wann Sie die Zeichenfolge '\ 0' beenden müssen. Wenn Sie versuchen, den Puffer zu verwenden, werden 4000 Zeichen Müll ausgegeben, wenn Sie erfolgreich 96 Zeichen der Stdout-Ausgabe erfasst haben.

In meiner Anwendung verwende ich einen Perl-Interpreter im C-Programm. Ich habe keine Ahnung, wie viel Ausgabe aus dem ausgegeben wird, was auch immer im C-Programm ausgegeben wird, und daher würde der obige Code es mir nie erlauben, diese Ausgabe irgendwo sauber auszugeben.

+0

Wenn Sie zuerst den bigbuf mit Nullen füllen, dann ist garantiert, dass die Zeichenkette null terminiert ist ... es sei denn, das letzte Byte von bigbuf ist nicht Null. In diesem Fall können Sie jedoch einen Überlauf feststellen. –