boost thread and thread save cout
-
Hi, ich hätt gern paar kommentar zu diesem code, der cout ersetzt, um keine wilden halbsatz-zusammenwürfelungen mit cout zu erhalten, wenn mehrere threads cout benutzen.
es sollte genau 1 (aber mind 1) echo item pro thread geben. Die ausgabe in die console erfolgt über die methode write().
Um besthenden code zu ersetzen, einfach alle couts ersetzen (durch die entsprechende echo instanz per thread)/* * echo.hpp * * Created on: 25.06.2009 * Author: Bernhard Schuster * License: GPLv2 or later */ #ifndef ECHO_HPP_ #define ECHO_HPP_ #include <boost/thread/mutex.hpp> #include <sstream> #include <iostream> using namespace std; class echo{ private: ostringstream os; static boost::mutex locker; public: template <class T> ostream& operator<<(T &x){ os << x; return os; } void write(){{ boost::mutex::scoped_lock(locker); cout.write(os.str().c_str(), os.str().length()); cout.flush(); cout.clear(); }} }; #endif /* ECHO_HPP_ */
MfG
-
Die Anforderung, dass der Anwender eine echo-Instanz pro Thread benötigt, finde ich etwas störend. Bequemer für den Anwender wäre es, wenn sich echo die richtige Instanz je nach Thread selber sucht.
Auch die Notwendigkeit das write explicit aufzurufen stört etwas. Die Anwender sähe doch etwa so aus:einEcho << "Der Wert für n ist " << n << endl; einEcho.write(); // stört hier - oder?
In Zeile 23 würde ich den Typ T noch als const deklarieren, ansonsten finde ich den Code ok. Ein Inserter für Manipulatoren fehlt noch - z.B. geht ein
einEcho << endl;
nicht.
Die Idee an sich ist gut.Gruß
Werner
-
OK, danke. An die manipulatoren (width, fill, ...) hab ich noch garnicht gedacht, werde aber mal einen blick darauf werfen.
Bezügl dem störenden write, damit kann man sicher gehen dass z.B. ganze blöcke in einem Stück ausgegeben werden, so dass keine zeilenweise *vermischung* statt findet.
Was endl, betrifft, so sollte das doch funktionieren oder etwa nicht? Zumindes läuft dieser code bei mir correct: [linking against boost_thread-mt]
#include "./echo.hpp" #include <boost/thread/thread.hpp> #include <boost/thread/barrier.hpp> #include <boost/thread/mutex.hpp> boost::barrier BBB(5); void testA() { BBB.wait(); for (int i = 0; i < 7; ++i) { echo e; e << "halligalli A" << "____A" << "?_? A" << endl; e.write(); sleep(0.1); } } void testB() { BBB.wait(); for (int i = 0; i < 2; ++i) { echo e; e << "halligalli B" << "____B" << "?_? B" << endl; e.write(); sleep(0.1); } } void testC() { BBB.wait(); for (int i = 0; i < 7; ++i) { echo e; e << "halligalli C" << "____C" << "?_? C" << endl; e.write(); sleep(0.1); } } void testD() { BBB.wait(); for (int i = 0; i < 2; ++i) { echo e; e << "halligalli D" << "____D" << "?_? D" << endl; e.write(); sleep(0.1); } } int main() { boost::thread T(boost::bind(testD)); boost::thread_group tg; tg.create_thread(boost::bind(testA)); tg.create_thread(boost::bind(testB)); tg.create_thread(boost::bind(testC)); BBB.wait(); tg.join_all(); T.join(); }
Danke für die Kritik.
-
soxs060389 schrieb:
Was endl, betrifft, so sollte das doch funktionieren oder etwa nicht?
ich meine ein endl unmittelbar hinter dem echo-Objekt. Das funktioniert:
echo e; e << "Hallo" << endl;
aber das nicht:
e << endl;
Das liegt daran, dass sowohl der inserter von echo als auch das endl beides template-Funktionen sind, d.h. der Compiler kann gar nicht wissen, wie er diesen Ausdruck instantisieren soll.
Ach so - jetzt habe ich verstanden, wie Du es verwenden willst. Dann tritt aber noch ein zusätzliches Problem auf, was ich am Anfang nicht bedacht habe - Angenommen, der Anwender schreibt:
echo e; e << "Hallo" << endl; e.write(); // 620 Zeilen sonstiger Code ... e << "2.Zeile " << endl; e.write();
dann wird die erste Zeile beim zweiten Mal wieder mit ausgegeben, da sie sich immer noch im stringstream befindet. Das könntest Du übrigens leicht lösen, wenn Du echo wie folgt änderst:
class echo{ private: std::stringstream os; // verwende stringstream statt ostringstream static boost::mutex locker; public: template <class T> std::ostream& operator<<( const T& x){ os << x; return os; } void write(){ boost::mutex::scoped_lock(locker); std::cout << os.rdbuf(); // gibt den Buffer aus, anschließend ist er "leer" std::cout.flush(); std::cout.clear(); } };
Zuletzt ist mir noch aufgefallen, dass dieser Code in einem Header-File steht und dort
using namespace std;
steht. Das sollte man NIE tun. Damit untergräbt man die ganze namespace-Abgrenzung. Bei größeren Programmen kann man damit Compiler-Fehler produzieren, nach denen man stundenlang suchen muss.
Tipp: entferne das 'using namespace std' und schreibe im Header überall std:: vor die Typen, wenn notwendig.
Noch mal so als Denkanstoß: man könnte das echo::write auch in den Destruktor von echo packen bzw. dort aufrufen. Anschließend wäre dann so etwas möglich:
echo() << "Wert = " << wert << endl;
und mit Verlassen dieses Statements wird der Destruktor und damit echo::write() aufgerufen. Dann kann write auch nie vergessen werden.
Blöd ist aber u.U. sowas:
echo e; e << "1.Zeile" << endl; // Oops - hier das write() vergessen irgendeinefunktion(); // in dieser Funktion wird auch auf echo ausgegeben } // Scope-Ende gibt die 1.Zeile aus
damit wäre die Reihenfolge der Ausgaben dann nicht mehr identisch mit der Reihenfolge der geschriebenen Ausgaben.
Gruß
Werner
-
"write()" könnte ja auch automatisch beim Einsatz von "flush" und "endl" aufgerufen werden, das fände ich glaubich auch relativ intuitiv.
-
Was bringt es für vorteile statt ostringstream stringstream zu benutzen? Alles andere erscheint mir einleuchtend, und ich werde wohl die nächsten Tage daran weiter tüfteln...
Gruß
-
Irgendwie klappt nun garnichts mehr, es werden die zeilen alle zu oft ausgegeben, unabhängig davon ob ich es über rdbuf() oder str() mache, und ich habe auch keine memeberfunktion gefunden die direkt den buffer leert.
Hab die funktionen nochmal soweit verändert:
/* * echo.hpp * * Created on: 25.06.2009 * Author: Schuster Bernhard * License: GPLv2 or later */ #ifndef ECHO_HPP_ #define ECHO_HPP_ #include <boost/thread/mutex.hpp> #include <sstream> #include <iostream> class echo{ private: std::stringstream os; static boost::mutex locker; public: ~echo(){ write(); } template <class T> std::ostream& operator<<(const T &x){ os << x; return os; } std::ostream& write(){{ boost::mutex::scoped_lock(locker); //std::cout << os.rdbuf()->str(); // gibt den Buffer aus, anschließend ist er "leer" // wäre schon, aber das tut nicht so wie es soll.... es gibt hierbei keine ausgabe //std::cout.write(os.str().c_str(), os.str().length()); std::cout << os.rdbuf()->str(); std::cout.flush(); std::cout.clear(); return os; }} }; #endif /* ECHO_HPP_ */
Testfunktion:
/* * main.cpp * * Created on: 25.06.2009 * Author: bernhard */ #include "./echo.hpp" #include <boost/thread/thread.hpp> #include <boost/thread/barrier.hpp> #include <boost/thread/mutex.hpp> #include <string> boost::barrier B(4); void testBlock(std::string str) { B.wait(); echo e; for (int i = 0; i < 3; ++i) { e << "Block:" << str << std::endl; sleep(0.1); } e.write(); } void testLine(std::string str) { //lines *may* be shuffled B.wait(); echo e; for (int i = 0; i < 2; ++i) { e << "line" << str << std::endl; e.write(); sleep(0.1); } } void doLineTest() { boost::thread T(boost::bind(testLine, "A_")); boost::thread_group tg; tg.create_thread(boost::bind(testLine, "B_B_")); tg.create_thread(boost::bind(testLine, "C_C_C_")); tg.create_thread(boost::bind(testLine, "D_D_D_D_")); tg.join_all(); T.join(); } void doBlockTest() { boost::thread T(boost::bind(testBlock, "A_")); boost::thread_group tg; tg.create_thread(boost::bind(testBlock, "B_B_")); tg.create_thread(boost::bind(testBlock, "C_C_C_")); tg.create_thread(boost::bind(testBlock, "D_D_D_D_")); tg.join_all(); T.join(); } int main() { echo e; //e << " " << std::endl << e.write(); e << "---------------------------------\n"; e.write(); e << "----------------\n"; e.write(); e << "-------\n"; e.write(); doLineTest(); e << "---------------------------------\n"; e.write(); doBlockTest(); e << "---------------------------------\n"; e.write(); }
Noch eine Frage: Wie setzt man es um methoden ohne () aufrufen zu können, sodass man echo << echo::flush << echo::endl << echo::fill << "or alike"; benutzen kann?
Vielen dank. MfG
-
ungetestet !
// in der klasse: void flush() { std::count.flush(); } // .... struct base_manip { virtual void action(echo *) = 0; }; class c_flush : public base_manip { public: void action(echo *e) { e->flush(); } } flush; // hier wird ein objekt angelegt echo &operator << (base_manip *b) { b->action(this); } // echo e; e << echo::flush;
mfg vermutung
-
Mein Code sieht im Moment so aus:
Er compaliert, hat aber noch mehere Probleme:• Zeilen werden immer noch zu oft ausgegeben, liegt an ostream, wie flushed man das "richtig" so dass es 100%ig leer ist?
• Mann muss für jeden base_minpulator (endl, flush, ..) eine eigen Überladung für operator<< machen, da sonst template einspringt, aber für die entsprechenden Klassen keine Regeln existieren... -> Compilerfehler (wenn man sie nicht explizit schreibt)
• wirft komischerweise fehler, wenn man gleich nach der klassendefinition eine instanz erzeugt, deswegen hier ein umweg über static member variablen.
• echo::flush und e.flush() können nicht koexistieren, da sie den glecihen namen besitzen -> Compilerfehler ABER: wie wurde das bei cout gehandelt? da geht das ja "irgendwie"
Danke für alle lösungsvorschläge
/* * echo.hpp * * Created on: 25.06.2009 * Author: Schuster Bernhard * License: GPLv2 or later */ #ifndef ECHO_HPP_ #define ECHO_HPP_ #include <boost/thread/mutex.hpp> #include <sstream> #include <iostream> class echo { private: std::stringstream os; static boost::mutex locker; public: ~echo() { flush2(); } // actions struct base_manipulator { public: virtual void action(echo *e) = 0; }; /* * FLUSH */ void flush2() { boost::mutex::scoped_lock(locker); std::cout.flush(); std::cout << os.rdbuf()->str(); os.flush(); os.clear(); std::cout.flush(); std::cout.clear(); } class echo_flush : base_manipulator { public: void action(echo *e) { e->flush2(); } }; static echo_flush flush; /* * ENDL */ void endl2() { boost::mutex::scoped_lock(locker); os << std::endl; } class echo_endl: base_manipulator { public: void action(echo *e) { e->endl2(); } }; static echo_endl endl; /* * NARROW * echo& narrow() { boost::mutex::scoped_lock(locker); std::cout.narrow(); return *this; } class echo_narrow : public base_manipulator { public: void action(echo *e) { e->flush(); } }; * WIDEN * echo& widen() { boost::mutex::scoped_lock(locker); std::cout.widen(); return *this; } class echo_widen : public base_manipulator { public: void action(echo *e) { e->flush(); } };*/ /* * WIDTH */ static void width(const int &w) { boost::mutex::scoped_lock(locker); std::cout.width(w); } template <class T> echo& operator<<(T &x) { os << x; return *this; } echo& operator<< (echo &x) { //std::cout << "Ignored" << std::endl; return *this; } echo& operator << (base_manipulator &b) { b.action(this); return *this; } echo& operator << (echo_flush &b) { b.action(this); return *this; }echo& operator << (echo_endl &b) { b.action(this); return *this; } }; echo::echo_endl echo::endl; echo::echo_flush echo::flush; #endif /* ECHO_HPP_ */
-
Note: Die width funktion is noch blödsinnig -> einfach ignorieren.