[C] thread safety
-
Bonjour!
ich hoffe, ihr könnt mir bei folgendem Problem helfen:
library.c:int global_file_descriptor void foo(foo_args...) { global_file_descriptor = open(...); } void bar(bar_args...) { write(global_file_descriptor, ... ); }
so, daraus soll jetzt was thread-safes werden
und jeder thread soll eine eigene datei bekommen. will heißen: in foo() muss ich jeweils eine neue datei pro thread öffnen und in ein array (oder so) packen. und in bar ... muss ich dann rausfinden, in welchem thread ich bin und mir den dazugehörigen file_descriptor beschaffen.
wie würde das am geschicktesten gehen?
-
Kann denn nicht jeder Thread seine Dateien selber öffnen, ohne irgendwas in globale Variablen zu speichern? Dann kommen sich die Threads gar nicht erst ins Gehege.
Ansonsten musst du einfach das Array bzw. alles, auf das mehrere Threads gleichzeitig zugreifen könnten, mit Mutexen schützen.
-
Am geschicktesten würde es Objektorientiert gehen.
Also z.B.struct blubb; blubb* blubb_create(); void blubb_delete(blubb* theBlubb) void blubb_foo(blubb* theBlubb, args...); void blubb_bar(blubb* theBlubb, args...); // etc.
In der struct speicherst du dann deinen File-Descriptor.
Das hat den Vorteil dass du den "Context" (das "blubb") nicht an einen Thread bindest. Das kann an vielen Stellen von Vorteil sein.Ansonsten kannst du mit einigen Compilern __declspec(thread) verwenden:
__declspec(thread) int blubb_file_descriptor = 0;
(Falls es so nicht geht schreib das __declspec(thread) zwischen int und den Variablennamen -- ich merke mir nie wo das genau hingehört)
Dann "hat" einfach jeder Thread seine eigene Kopie von "blubb_file_descriptor", ganz automatisch und wie durch Magie.Und dann gibt's je nach OS auch noch manuelle Varianten. Unter Windows kann man z.B. mit den Funktionen TlsAlloc/TlsFree/TlsGetValue/TlsSetValue arbeiten:
DWORD blubb_tls_slot = TLS_OUT_OF_INDEXES; void blubb_init() { blubb_tls_slot = TlsAlloc(); if (blubb_tls_slot == TLS_OUT_OF_INDEXES) FEHLER(); } void blubb_cleanup() { if (blubb_tls_slot != TLS_OUT_OF_INDEXES) { TlsFree(blubb_tls_slot); blubb_tls_slot = TLS_OUT_OF_INDEXES; } } void foo() { int fd = open(...); TlsSetValue(blubb_tls_slot, (LPVOID) fd); } void bar() { int fd = (int) TlsGetValue(blubb_tls_slot); // ... }
Die "Schwierigkeit" liegt hier dann darin dass du sicherstellen musst dass bevor der Code verwendet wird genau 1x blubb_init() aufgerufen wird, und danach (=wenn kein einziger Thread mehr eine der "Blubb-Funktionen" braucht) wenns geht genau 1x blubb_cleanup().
Natürlich kann man das weitgehend automatisieren (auch Thread-Safe), aber das führt mir jetzt etwas zu weit.
-
__declspec(thread)
Und wenn es nicht Windows sein soll? Ich empfehle: pthreads.
-
hustbaer schrieb:
Am geschicktesten würde es Objektorientiert gehen.
nö, geschickter ist das, was dein vorposter geschrieben hat. auch wenn man threads/tasks und synchronisationsobjekte als 'objekte' behandeln kann, ist OOP in verbindung mit multithreading meistens overhead, der keinen gewinn bringt.
-
~fricky schrieb:
hustbaer schrieb:
Am geschicktesten würde es Objektorientiert gehen.
nö, geschickter ist das, was dein vorposter geschrieben hat. auch wenn man threads/tasks und synchronisationsobjekte als 'objekte' behandeln kann, ist OOP in verbindung mit multithreading meistens overhead, der keinen gewinn bringt.
Lesen, denken, verstehen, nicht sinnlose Antworten schreiben.
Danke.
-
hustbaer schrieb:
Lesen, denken, verstehen, nicht sinnlose Antworten schreiben.
Danke.sorry, ich kann ja nicht ahnen, dass du mit dem satz: 'Am geschicktesten würde es Objektorientiert gehen', was anders meinst als: 'Am geschicktesten würde es Objektorientiert gehen'.
-
~fricky schrieb:
ist OOP in verbindung mit multithreading meistens overhead, der keinen gewinn bringt.
Schwachsinn, was hat OOP mit Overhead bei Multithreading zu tun? Gerade ein LockMutex-Objekt in C++ macht es sehr einfach, "thread safe" zu programmieren. Diesen bequemen Mechanismus habe ich in C nicht.
-
...
-
@~fricky:
Ich meine genau das was dort steht, nur du verstehst es nicht.Er soll die globale Variable eliminieren, und sie stattdessen zum Teil eines Objekts machen. Dann soll sich der Client-Code darum kümmern dass es Thread-Safe ist, indem jeder Thread ein eigenes Objekt erstellt. Oder übergeben bekommt. Oder was auch immer.
Was ist daran so schwer zu verstehen?
-
Hi,
ich weiß jetzt nicht, welche thread-library du verwendest.
Bei pthreads (POSIX Thread) gibt es sogenannte Thread-Specific Data.Funktionen zu diesem Theam:
pthread_key_create pthread_key_delete pthread_getspecific pthread_setspecific
Mit pthread_key_create erzeugst du die dann eine globale
Variable.
Beispiel:static pthread_key_t key; // nicht thread-safe Funktion! static void init_key() { pthread_key_create(&key, NULL); }
Anschließend kann jeder Thread mit pthread_setspecific
einen Pointer unter diesen Namen hinterlegen.static void add_to_key(void *file) { pthread_setspecific(key, file); }
Anschließend kann dann mit pthread_getspecific auf diesen
Pointer von jeder Stelle im Code, wo key bekannt ist,
zugegriffen werden.static void* get_file() { return pthread_getspecific(key); }
Dabei gibt pthread_getspecific einen Null-Pointer zurück,
wenn der Thread vorher keinen Pointer hinterlegt hat.Ich hoffe, damit kommst du weiter.
Gruß mcrPS: Bei der POSIX Thread-Library gibt es auch Mutexe.
EDIT: Ein wenig mehr Informationen, auf welchem System du
programmierst, wäre wünschenswert.-- Ich weiß, dass in meinem Beispiel keinerlei Checks implementiert sind --
-
knivil schrieb:
Schwachsinn, was hat OOP mit Overhead bei Multithreading zu tun?
na, z.b. dass man versucht ist, dinge in klassen bzw. in ein oo-design zu pressen, obwohl es keinen nennenswerten vorteil bringt. der overhead kommt z.b. durch's instanziieren solcher klassen und oo-gerechten austauch von 'messages'. das ist dann quasi vorprogrammiert.
knivil schrieb:
Gerade ein LockMutex-Objekt in C++ macht es sehr einfach, "thread safe" zu programmieren. Diesen bequemen Mechanismus habe ich in C nicht.
so'n lockmutex-objekt ist doch ziemlich unflexibel, weil der lock erst freigegeben wird, wenn die funktion verlassen wird.
in C machste einfach:lock(); mach_was(); unlock();
^^ irgendwo im code und gut is. das ist überhaupt nicht unbequem.
hustbaer schrieb:
Was ist daran so schwer zu verstehen?
nix, es ist nur viel zu komplex, für das, was es machen soll.
-
*fricky schrieb:
knivil schrieb:
Schwachsinn, was hat OOP mit Overhead bei Multithreading zu tun?
na, z.b. dass man versucht ist, dinge in klassen bzw. in ein oo-design zu pressen, obwohl es keinen nennenswerten vorteil bringt.
du verstehst den vorteil wohl nur nicht. gerade in bezug auf multithreading ist es wichtig guten code zu haben, OOP erlaubt dir automatische dinge zu nutzen die fehler ausschliessen, z.b. verzweigung aus einer funktion ohne unlock vom mutex. das ist bei threading entsprechend fatal -> deadlock.
der overhead kommt z.b. durch's instanziieren solcher klassen und oo-gerechten austauch von 'messages'. das ist dann quasi vorprogrammiert.
das ist unsinn, oop heisst nicht dass es initialisierungsoverhead gibt, hast du keinen c-tor oder ist der leer, wird entsprechend nichts gemacht, falls du den inlinest. falls es doch was zu tun gibt beim initialisieren, dann musst du das auch in c machen.
knivil schrieb:
Gerade ein LockMutex-Objekt in C++ macht es sehr einfach, "thread safe" zu programmieren. Diesen bequemen Mechanismus habe ich in C nicht.
so'n lockmutex-objekt ist doch ziemlich unflexibel, weil der lock erst freigegeben wird, wenn die funktion verlassen wird.
das stimmt auch nicht, das lock wird freigegeben wenn der scope verlassen wird, den kannst du mit { } setzen, also auch innerhalb einer funktion lokal wenn du das moechtest.
in C machste einfach:
lock(); mach_was(); unlock();
^^ irgendwo im code und gut is. das ist überhaupt nicht unbequem.
und dann fuegt jemand mal etwas ein
lock(); if(!muss_was_machen()) return; mach_was(); unlock();
und er uebersieht deinen mutex und schon haste manchmal nen deadlock. und der passiert nicht an der stelle wo er es eingebaut hat, die folgen koennen an jeder stelle an dem du den mutex verwendest dann auftauchen.
hustbaer schrieb:
Was ist daran so schwer zu verstehen?
nix, es ist nur viel zu komplex, für das, was es machen soll.
eigentlich ist es eine vereinfachung, es hat keinen laufzeit overhead und erlaubt dir schneller und fehlerfreier code zu schreiben.
-
*fricky schrieb:
...der overhead kommt z.b. durch's instanziieren solcher klassen und oo-gerechten austauch von 'messages'. das ist dann quasi vorprogrammiert...
*fricky schrieb:
...in C machste einfach:...
*fricky schrieb:
...das ist überhaupt nicht unbequem.
*fricky schrieb:
...es ist nur viel zu komplex, für das, was es machen soll.
Selten so viel Unsinn gelesen. Manche Leute haben OO eben noch nicht so recht kapiert. Die frickeln lieber spaghettimäßig rum (hier ist der Nickname wohl Programm). Ist wohl ein Generationenproblem. Aber es ging ja eh nur um C.
-
*fricky schrieb:
so'n lockmutex-objekt ist doch ziemlich unflexibel, weil der lock erst freigegeben wird, wenn die funktion verlassen wird.
in C machste einfach:lock(); mach_was(); unlock();
^^ irgendwo im code und gut is. das ist überhaupt nicht unbequem.
In Objektorientierten Sprachen, kann ich das imho noch leichter machen, und weniger Fehleranfällig:
void Bar::Foo() { mach_was_ohne_lock(); { Lock l; if(!mach_was()) return; // Ohne umständliche Freigaben... mach_was_anderes(); } mach_was_ohne_lock(); }
Ist zudem auch gegen return und ähnliches sicher (Ich liebe RAII)...
-
warum reden hier eigentlich alle ueber Locks etc? Alles was der Benutzer brauch ist eine getThreadNumber() und ein Array statt einer globalen Variable. Dann kann er sich jegliche Synchronisation auch komplett ersparen
-
asc schrieb:
In Objektorientierten Sprachen, kann ich das imho noch leichter machen, und weniger Fehleranfällig:
Genau davon ist doch die ganze Zeit die Rede.
Allgemein "in Objektorientierten Sprachen" wäre ich damit aber vorsichtig, da z.B. in Java der Destruktor nicht sofort bei Ende des Scopes aufgerufen wird, sondern eventuell irgendwann später. Dort sollte man finally verwenden.
So ein RAII-Lock-Objekt ist letztendlich nur eine syntaktische Abkürzung; sicherlich komfortabel, aber kein konzeptioneller Vorteil von OOP. Ich würde ein einfaches lock/unlock in C trotzdem vorziehen, weil es klarer verständlich ist, als wenn sich das unlock hinter einer geschweiften Klammer versteckt; gerade bei Nebenläufigkeit halte ich es für keine keine gute Idee, Dinge wegzukapseln, die der Programmierer besser im Blick haben sollte.
Wenn euch jemand an eurem Code rumpfuscht und mittendrin returns einfügt, ohne Ressourcen freizugeben, habt ihr ganz andere Probleme.
Wenn man C++ programmiert, kann man aber nicht einfach lock/unlock aufrufen, eben weil z.B. unerwartet geworfene Exceptions das unlock umgehen könnten, sondern muss RAII verwenden. C++-Fans spinnen RAII aber immer gerne als großen Vorteil.
-
rapso schrieb:
*fricky schrieb:
knivil schrieb:
Schwachsinn, was hat OOP mit Overhead bei Multithreading zu tun?
na, z.b. dass man versucht ist, dinge in klassen bzw. in ein oo-design zu pressen, obwohl es keinen nennenswerten vorteil bringt.
du verstehst den vorteil wohl nur nicht. gerade in bezug auf multithreading ist es wichtig guten code zu haben
es ist doch oft wichtig, guten code zu haben. das ist bei multithreading auch nicht anders.
rapso schrieb:
OOP erlaubt dir automatische dinge zu nutzen die fehler ausschliessen, z.b. verzweigung aus einer funktion ohne unlock vom mutex.
aber das ist doch ein völlig banaler fehler, den man meistens schon durch scharfes hinschauen erkennt. aber gut, mag ja sein, dass verhindern von trivialfehlern manchmal der einzige grund ist, oop einzusetzen.
asc schrieb:
void Bar::Foo() { mach_was_ohne_lock(); { Lock l; if(!mach_was()) return; // Ohne umständliche Freigaben... mach_was_anderes(); } mach_was_ohne_lock(); }
Ist zudem auch gegen return und ähnliches sicher (Ich liebe RAII)...
hihi, und dann kommt einer und wundert sich, was die geschweiften klammern da sollen (weil ja weder ein if/while/for/sonstwas davor steht) und löscht sie weg.
-
Thread Saviour schrieb:
Bonjour!
und in bar ... muss ich dann rausfinden, in welchem thread ich bin und mir den dazugehörigen file_descriptor beschaffen.
wie würde das am geschicktesten gehen?Lass foo den Filedescriptor als Funktionsergebnis liefern und gib ihn dann bar als weiteres Argument mit. Du brauchst den dann nicht global, sondern nur als lokale Variable in der Threadfunktion anzulegen.
-
Ciao
Blue-Tiger schrieb:
warum reden hier eigentlich alle ueber Locks etc? Alles was der Benutzer brauch ist eine getThreadNumber() und ein Array statt einer globalen Variable. Dann kann er sich jegliche Synchronisation auch komplett ersparen
Ja, ich denke darauf wird es hinauslaufen. Oder ich benutze tls, das gibts wohl für den gcc (den ich benutze) auch.
(und über locks reden die anderen, weil sie wieder mal einen der heiligen kriege der informatik auskämpfen)
@Belli:
ich kann leider die deklaration der funktionen nicht ändern.