Hallo,
ich hänge hier gerade an einer konzeptionellen Überlegung. Für eine
Maschinensteuerung (Windows CE 5.0) soll ein Prozess (erstellt mit
EVC++4.0) in mehrere Threads aufgeteilt werden. Der Prozess ist eine
reine "Konsolenanwendung", d.h. hat keine Bedienoberfläche, sondern
kommuniziert vie TCP mit einer anderen Anwendung.
Nun möchte ich im groben folgendes realisieren:
- Ein Thread kümmert sich um die TCP-Kommunikation, d.h. wartet auf eine
Verbindung vom entfernten Programm und tauscht nach Verbindungsaufbau
zyklisch Steuerdaten aus
- Ein Thread realisiert eine/mehrere Schrittketten für die
Ablaufsteuerung an der Maschine
Somit existiert zu jeder "Funktionalität" nur genau ein Thread mit
völlig unterschiedlicher Struktur. Die Threads sollen zur
Ablaufkoordination lediglich beide Zugriff auf ein globales Struct
haben.
Nun meine Überlegungen:
- Da die Boost-Libs meiner Recherche nach nicht unter CE laufen, würde
ich die Threads mit den WIN32-C-Funktionen realisieren:
http://msdn.microsoft.com/en-us/library/y6h8hye8.aspx
- In die Struct, auf die beide Threads Zugriff erhalten sollen, würde
ich ein CMutex einbauen und vor jedem Zugriff locken
Meine Fragen:
- Ist das prinzipiell ein brauchbares Konzept, oder ist da ein grober
Denkfehler drin?
- Sehe ich das richtig, dass ich für wirklich alle Funktionen, die ich
aus BEIDEN Threads aufrufe, sicherstellen muss, dass diese
Multithreading-Safe sind? Die C-Standard-Library und die WIN32-API
sollten doch prinzipiell sicher sein, wenn ich _beginthread() an Stelle
von CreateProcess() verwende?
- Kann ich innerhalb jedes Threads weiterhin wie gewohnt mit new/delete
Objekte erzeugen, ohne dass ich diese separat schützen muss (kein
gemeinsamer Zugriff)
- Muss ich die Threads noch irgendwie voreinander schützen?
Ich würde mich freuen, wenn ich ein paar Kommentare zu obigen Zeilen
bekommen könnte, um sicherzugehen, dass das Konzept halbwegs passt.
Peter
> - Ist das prinzipiell ein brauchbares Konzept, oder ist da ein grober> Denkfehler drin?
Das sollte so schon gehen.
> - Sehe ich das richtig, dass ich für wirklich alle Funktionen,> die ich aus BEIDEN Threads aufrufe, sicherstellen muss, dass diese> Multithreading-Safe sind? Die C-Standard-Library und die WIN32-API> sollten doch prinzipiell sicher sein, wenn ich _beginthread() an> Stelle von CreateProcess() verwende?
Du meinst vermutlich "an Stelle von CreateThread()". Aber auch das wird
überbewertet. Sofern man nicht die ganz perversen C-Funktionen
verwendet, die interne Statusvariablen nutzen (strtok ist wohl ein
Kandidat), kann man auch ohne Probleme CreateThread verwenden. Ich mache
das unter NT (2K/XP/2K3) seit 15 Jahren in auch ziemlich großen Systemen
so, ohne auch nur ein einziges Mal ein _beginthread-Problem gehabt zu
haben.
> - Kann ich innerhalb jedes Threads weiterhin wie gewohnt mit> new/delete Objekte erzeugen, ohne dass ich diese separat> schützen muss (kein gemeinsamer Zugriff)
Aber ja doch, sofern Du mit der geeigneten Multithreaded-Runtimelibrary
linkst.
- Muss ich die Threads noch irgendwie voreinander schützen?
Nö, sofern Du Zugriffe auf gemeinsam genutzte Dinge mit "critical
sections" oder anderen Synchronisationsobjekten schützt und ansonsten
Threads mit Events o.ä. aufeinander warten lässt, ist alles wesentliche
getan.
Da der C-Compiler selbst nichts von Multithreading weiß, ist es
sinnvoll, von mehreren Threads gemeinsam genutzte Variablen als
volatile zu deklarieren, genau so, wie man es auch bei Nutzung von
Interruptcode machen würde.
Hallo Rufus,
vielen Dank für deine schnelle und verständliche Antwort!
> > - Kann ich innerhalb jedes Threads weiterhin wie gewohnt mit> > new/delete Objekte erzeugen, ohne dass ich diese separat> > schützen muss (kein gemeinsamer Zugriff)>> Aber ja doch, sofern Du mit der geeigneten Multithreaded-Runtimelibrary> linkst.
Das ist mir nicht ganz klar, new und delete gehören doch zum
Sprachumfang von C++. Gegen welche Runtimelibrarys linke ich denn da?
Ich vermute irgendwelche Systemaufrufe.
Aber bei einem Multitaskingfühigen SDK sollte doch alles passen?!
> Da der C-Compiler selbst nichts von Multithreading weiß, ist es> sinnvoll, von mehreren Threads gemeinsam genutzte Variablen als> volatile zu deklarieren, genau so, wie man es auch bei Nutzung von> Interruptcode machen würde.
Das habe ich noch nirgends gelesen, danke für den Tipp!
Peter
> Das ist mir nicht ganz klar, new und delete gehören doch zum> Sprachumfang von C++. Gegen welche Runtimelibrarys linke ich denn da?
Na, die, in denen die Implementierung der C/C++-Standardfunktionen
enthalten ist. Sieh Dir einfach mal den Inhalt des
Library-Verzeichnisses Deines Compilers an, da wirst Du mehrere
Libraries mit fast gleichem Namen finden.
Bei VC6 für Win32 beispielsweise ist ein angehängtes "mt" der Hinweis
auf die Multithreading-Variante, ein angehängtes "d" steht für die
Debugversion etc.
Das wird bei EVC++ nicht sehr anders aussehen. Du musst Dich
glücklicherweise nicht damit auseinandersetzen, denn die IDE bietet
Einstellmöglichkeiten, ob single- oder multithreading-Code erzeugt
werden soll. Und das entscheidet dann unter anderem auch, welche Library
verwendet wird.
Rufus t. Firefly wrote:
> Du meinst vermutlich "an Stelle von CreateThread()". Aber auch das wird> überbewertet. Sofern man nicht die ganz perversen C-Funktionen> verwendet, die interne Statusvariablen nutzen (strtok ist wohl ein
Man kann es meiner Meinung nach nicht laut genug sagen: Finger weg von
CreateThread!
Man muss schon ganz genau wissen was man macht, wenn man auf
CreateThread setzt. Ich vermute mal, dass Du in Deinem "großen" Systen
gegen die DLL-Version der CRT linkst. Hier ist es natürlich kein
Problem, da die DLL eventuell fehlende per-thread-data in DllMain bei
DLL_THREAD_ATTACH nachträglich allokieren kann und das bei Bedarf auch
macht. Problematisch wird es aber in kleineren Progrämmchen, die
statisch linken (und dann wird auch ein DisableThreadLibraryCalls zum
Abenteuer). Und Peter wird ziemlich sicher statisch linken...
Und so "pervers" müssen die Funktionen gar nicht sein. Schließlich ist
auch alles das betroffen, was in irgendeiner Art und Weise
locale-sensitive ist.
Peter, bitte verwende auf alle Fälle _beginthreadex, denn damit
funktioniert es immer. Alles andere ist viel zu unsicher.
> Ich vermute mal, dass Du in Deinem "großen" Systen> gegen die DLL-Version der CRT linkst.
Nein, ich linke statisch.
Interessieren würde mich ein Beleg, der tatsächlich zeigt, daß
CreateThread Probleme verursacht, nicht nur Zitate von Warnungen in der
Dokumentationen und allgemeinen Annahmen. Wäre ja schon interessant, zu
sehen, warum es auch funktionieren kann.
Wer hat eigentlich diese merkwürdige Formulierung "gegen etwas linken"
erfunden?
Danke für eure weiteren Beiträge (und Bedenken)!
> Du musst Dich glücklicherweise nicht damit auseinandersetzen,> denn die IDE bietet Einstellmöglichkeiten, ob single- oder> multithreading-Code erzeugt werden soll.
Hm, hab grade mal sämtliche Compiler/Linkeroptionen in einem
VS2008-Projekt überflogen, und keinen derartigen Schalter gesehen. Oder
macht das der Compiler automatisch?
> Und Peter wird ziemlich sicher statisch linken...
Jein, sowohl statisch als auch DLLs die erst zur Laufzeit geladen werden
(über getprocaddress()). Verstehe ich dich richtig, das das unkritisch
sein sollte? Erstellt eine DLL für jeden Prozess oder sogar für jeden
Thread einen eigenen Stack/Heap? Kann ich eine DLL überhaupt mehrfach
(also für jeden Thread) innerhalb eines Prozesses laden? Oder muss ich
die Funktionen global bereitstellen?
Also wäre das ja ein Grund, gemeinsam genutzte Funktionen in eine DLL
auszulagern, auch wenn das gar nicht nötig wäre.
> Peter, bitte verwende auf alle Fälle _beginthreadex, denn damit> funktioniert es immer. Alles andere ist viel zu unsicher.
Gern, spricht meiner bisherigen Recherche nach ja auch nichts dagegen
> Wer hat eigentlich diese merkwürdige Formulierung "gegen etwas linken"> erfunden?
Vermutlich ein Rechter mit Grammatikschwäche ;-)
Peter
Rufus t. Firefly wrote:
> Interessieren würde mich ein Beleg, der tatsächlich zeigt, daß> CreateThread Probleme verursacht, nicht nur Zitate von Warnungen in der
Nun, intern werden nun mal auf den Thread bezogene Daten gehalten. Diese
Daten müssen irgendwo gespeichert werden. Rufst Du eine perverse
Funktion auf, z.B. isspace (locale-sensitive), wird der Platz für diese
Daten allokiert, so es nicht bereits geschehen ist. Soweit ist das also
kein Problem.
Wenn Du nun den Thread in Deiner statischen Variante enden lässt, ist da
niemand, der den Platz für diese Daten wieder freigibt. Solche Lecks
sind in meinen Augen schon ein Problem, unabhängig der Aussagen der
Marketings.
Nimmst Du hingegen _beginthreadex, passiert das Freigeben automatisch:
Kein Leck, kein Problem (solange man die Finger von der ebenfalls
"verbotenen" Funktion ExitThread lässt).
Peter wrote:
>> Du musst Dich glücklicherweise nicht damit auseinandersetzen,>> denn die IDE bietet Einstellmöglichkeiten, ob single- oder>> multithreading-Code erzeugt werden soll.> Hm, hab grade mal sämtliche Compiler/Linkeroptionen in einem> VS2008-Projekt überflogen, und keinen derartigen Schalter gesehen. Oder> macht das der Compiler automatisch?
Rufus meint das, was Du unter Konfigurationseigenschaften -> C++ ->
Codegenerierung -> Laufzeitbiliothek findest. Und nein, hier man man
nicht (mehr) auf single-threaded umstellen. Das war zu Zeiten des VC6
noch anders. Mit Deinem VS2008 hingegen bist Du hier immer auf der
sicheren Seite.
> Jein, sowohl statisch als auch DLLs die erst zur Laufzeit geladen werden> (über getprocaddress()). Verstehe ich dich richtig, das das unkritisch> sein sollte? Erstellt eine DLL für jeden Prozess oder sogar für jeden> Thread einen eigenen Stack/Heap? Kann ich eine DLL überhaupt mehrfach
Der Stack liegt in der Hand des Threads. Das ist also immer der selbe,
egal ob DLL oder EXE. Beim Heap sieht das aber anders aus:
- Linkst Du ein Modul (EXE oder DLL) gegen die statische Version der
CRT, benutzt diese Modul auch einen eigenen Heap. Das free muss demnach
dort passieren, wo das malloc passiert ist. Modul A kann den von Modul B
angeforderten Speicher nicht freigeben.
- Linkst Du gegen die DLL-Version der CRT, passiert das malloc und free
automatisch an der richtigen Stelle. Hier kannst Du Speicher von Modul A
in Modul B freigeben.
- Sind Modul A und Modul B gegen die DLL-Version der CRT gelinkt, dies
aber in unterschiedlichen Versionen (z.B. EXE mit dem VS2008, DLL noch
auf dem Stand VC6), musst Du mit den Freigaben natürlich wieder
aufpassen.
Am Besten ist es aber, sich auf nichts und niemanden zu verlassen. Das
Modul, das Speicher anfordert, gibt diesen wieder frei, niemand anders.
> (also für jeden Thread) innerhalb eines Prozesses laden? Oder muss ich> die Funktionen global bereitstellen?> Also wäre das ja ein Grund, gemeinsam genutzte Funktionen in eine DLL> auszulagern, auch wenn das gar nicht nötig wäre.
Eine DLL wird nicht in einen Thread geladen, sondern in den Adressraum
des Prozesses. Wenn Du die DLL erst einmal geladen hast, können alle
Threads des Prozesses auf deren Funktionen nach belieben eindreschen.