Multithreading vs DLL-Funktionen ( Threadsafe )
-
Hallo,
Ich erstelle gerade eine DLL für eine Anwendungssoftware, der Hersteller hat für die DLL-Erstellen auch ein SDK mitgeliefert, bisher funktioniert das ganze auch recht gut. Zukünftig wird die Anwendung auch Multithreading unterstützen, das bringt mich und meine C++ Kenntnisse ziemlich in schwitzen, da ich eigentlich in ABAP entwickle (und da zu 95% nur prozedural). Nach diversen Recherchen und "Petzold" lesen bin ich noch nicht schlauer geworden, bzw die Unsicherheit wurde größer.
Mir geht es grundsätzlich um die Frage wie ich ich meine Funktionen in der DLL threadsicher bekomme.
Mein Kenntnissstand:
- threadsicher sind lediglich lokale Variablen, da diese auf dem Stack liegen und jeder thread seinen eigen Stack besitzt
- Global / Static Variablen kann man via __declspec(thread) threadsicher bekommen
- Man kann auch TLS verwenden ( wobei mir __declspec erstmal sympatischer ist .. *g )BSP 1:
ich verwende in der Funktion ein simples integer array das ich via pointer befülle und auslese:
// pa_laenge = maximale Werte für Array, Parameter aus Anwendung an DLL int *pit = new int[pa_laenge];
die Pointervariable pit ist somit threadsicher da sie ja auf dem Stack liegt, aber was ist mit dem Array ??? dies befindet sich ja auf dem Heap. Liege ich mit folgender Vermutung richtig:
Thread-1: erzeught ein Array bei $1000 mit 10 Einträgen. -> pit referenziert auf $1000
Thread-2: erzeught ein Array bei $2000 mit 30 Einträgen. -> pit referenziert auf $2000-> hmm sieht auf den ersten Blick so aus als wäre das Array auch Threadsicher
BSP 2:
vector aus integer bzw Container aus der STL
vector<int> it_gaps(500); it_gaps.clear();
tendenziell würde ich sagen das der vector "it_gaps" threadsicher ist, da es sich um eine lokal definierte Variable handelt, jedoch habe ich hier keine eindeutige Aussage im Netz gefunden ...
gruss und danke ...
-
Generell gilt: Kein Objekt ist Thread-safe, auch keine Schleifen und Bedingungen!
Um Threadsicherheit zu bekommen, musst du synchronisieren, z.B. mit Mutexes, Semaphores, Condition Variables, Critical Sections o.ä..
-
Im Prinzip musst du immer dann, wenn mehrere Threads gleichzeitig auf den selben Speicher zugreifen, dafür sorgen, dass es keine Konflikte gibt.
Ob die Variablen auf dem Heap oder Stack liegen ist dabei relativ egal. Du kannst auch eine lokale Variable erstellen und dann zwei Threads starten und jedem einen Pointer auf diese Variable übergeben.
Immer wenn ein Thread Daten ändert, die ein anderer auch verwendet, musst du dafür sorgen, dass der andere Thread diese während dieser Zeit nicht verwendet, siehe Mutex usw.
Wenn alle Threads die Daten nur lesen, sollte es keine Probleme geben, zumindest auf der Hardware die mir so bekannt ist.
-
Zu den lokalen Variablen: Wenn zwei Threads eine Funktion aufrufen und diese intern lokale Variablen erstellt, sind das keine gemeinsam genutzen Daten, weil eben jeder Thread einen eigenen Stack hat. Gleiches gilt für dein Array. __declspec(thread) kenn ich nicht, sieht mir Compilerspezifisch aus. Allgemein sollte man sowieso auf globale Variablen verzichten.
-
@314159265358979:
Wow. Du hast es geschafft eine richtige Aussage zu machen, die aber im Zusammenhang mit der Frage des OP totaler Quatsch ist. Weiter so!
-
hustbaer schrieb:
@314159265358979:
Wow. Du hast es geschafft eine richtige Aussage zu machen, die aber im Zusammenhang mit der Frage des OP totaler Quatsch ist. Weiter so!Dude what?
-
Lies die Frage nochmal.
Er schreibt von lokalen Objekten, d.h. jeder Thread hat sein eigenes Objekt.
Da muss man nix synchronisieren, es sei denn die Objekte würden irgendwo intern selbst static/globale Daten verwenden. Dann ist es allerdings üblich, dass die Objekte sich selbst darum kümmern die Zugriffe auf diese zu synchronisieren.Und ausgehend von dieser Frage, ist deine Antwort Quatsch.
BTW: Schleifen und Bedingungen sind Code-Konstrukte, und die sind bei allen mir bekannten Implementierungen natürlich thread-safe. Die Zugriffe auf Daten die durch diese Konstrukte gemacht werden sind natürlich was anderes. Nur bei Zugriffen auf Daten hat es wieder nix damit zu tun durch welche Code-Konstrukte diese erzeugt werden.
-
Also die Verwendung eines Arrays wie du sie beschrieben hast ist natürlich
threadsicher.Was macht denn deine DLL?
Sind das einzelne unabhängige Funktionen, die etwas berechnen?Wenn du unabhängige Funktionen hast, dann kannst du keine Berechnungsergebnisse
auf dem Stack ablegen. Alle Objekte, die du ausgehend von einer
Stack-Variable erstellst (und auch wieder löschst), stehen keinem anderen Thread
zur Verfügung, solange du sie nicht explizit zu einem anderen Thread transferierst
oder über ein statisches Feld verfügbar machst. (Bitte keine Speicherspielerei-Einwände... Sowas macht kein vernünftiger Programmierer)
Sobald also statische Variablen involviert sind, hast du (ohne zusätzliche Vorkehrungen)
ein Problem.Angenommen du möchtest jetzt die Funktionen deiner DLL in verschiedenen
Reihenfolgen aufrufen können. Da kann es vorkommen, dass eine Funktion
die gleichen Informationen benötigt, die innerhalb einer anderen Funktion
vorher schonmal berechnet wurde. In dem Fall würde z.B. ein
Calculation-Context anbieten, der natürlich ebenfalls ein Stack-Objekt sein sollte/muss.Wenn du jetzt allerdings verschiedene Threads auf Basis gemeinsamer Daten
arbeiten lassen möchtest, dann wirst du die Zugriffe auf die gemeinsame
Datenbasis synchronisieren müssen. Hier kannst du z.B. einen statischen
Mutex verwenden oder einen, der an ein Datenbasis-Objekt gebunden ist.Gruß,
XSpille
-
zu deiner Frage: was macht die DLL:
ganz grob folgendens, die DLL bekommt aus dem Program Messdaten die ich meiner Funktionen analysiere und ein Ergebniss zurückgebe. Für die Übergabe der Messdaten gibt es im SDK vom Hersteller eine Funktion die mir einen Pointer auf die Messdaten der Anwendung liefern.
Grundgedanke für die Verwendung der globalen Variable war eigentlich ... da es verschiedene Funktionen gibt die auf diese Messdaten zugreifen, kann ich die Pointer Übergabe auf die Messdaten bei meinen Analyse-Funktionen sparen. (*g da steckt wohl total der prozedurale Denkansatz dahinter hehe)-> der Sourcode ist hier stark vereinfacht, natürlich erzeuge ich temporär Vektoren für die Berechnungen in den Analyse-Funktionen etc ...
#include "test1." // Global __declspec( thread ) Anwendungs_daten obj_data; AmiVar FSMGTest2( int NumArgs, AmiVar *ArgsTable ) { // Numargs = Anzahl Parameter // *Argstable = die diversen Parameter AmiVar result; result = gSite.AllocArrayResult(); // SDK-Funktion speicheranforderung für Ergebnisss GetAnwendungsdaten(); // Aufruf SDK Mapping Der Anwendungsdaten + Init result[0] = Methode1( ArgsTable[ 1 ] ); result[1] = Methode2( ArgsTable[ 2 ] , ArgsTable[ 2 ]); result[2] = Methode3( ArgsTable[ 1 ] ); result[3] = Methode4( ArgsTable[ 2 ] , ArgsTable[ 2 ]); ........ return result; }
//
// noch das Header-file
//---------------------------------------#ifndef TEST1_H #define TEST1_H typedef struct Anwendungs_daten { int Size; float *high; float *low; } Anwendungs_daten; __declspec( thread ) extern Anwendungs_daten obj_data; #endif // TEST1_H
//
// und als Beispiel eine Methode
//---------------------------------------int Methode1( int pa_blub ) { int treffer = 0; for (int i = 0; i < obj_data.Size; i++) { if ( obj_data.High > pa_blub ) treffer++; exit; } return treffer; }
die "Hilfsfunktionen" Methode1 bis Methodex beziehen sich alle auf das Globale Messdatenobjekt, von daher denke ich das ich lediglich das Messobjekt Threadsafe bekommen muß. Das ich zwischen Threads informationen austauschen möchte kann ich definitv ausschliesen, genau das möchte ich vermeiden. Im Hauptprogramm existieren 1-n Fenster mit jeweils einem Messdatenobjekt und diese sind voneinander unabhängig.
-
Ich würde, wenn es möglich ist, auf jeden Fall vorziehen, allen Funktionen einen Zeiger auf den Datenblock mitzugeben, anstatt mit thread-local Variablen rumzuhantieren.
BTW: weiss jmd. wie das entsprechende Anti-Pattern heisst? Mir fällt der Name gerade nicht ein, und Google/Wikipedia hilft auch nicht weiter.
g_param1 = 123; // thread-local oder auch nicht p_param2 = 42; // --""-- DoIt(); // verwendet g_param1 und p_param2 als Parameter
Bzw. genau so wenn man die Zugriffe auf die globalen Variablen "versteckt"
SetParam1(123); SetParam2(42); DoIt(); // verwendet die zuvor mit SetParam1() und SetParam2() gesetzten Werte