Wie wird mit einer Situation umgegangen, in der während eines blockierenden Anrufs eine Sperre aufgehoben werden muss?

8

Ich habe einige " FreeOnTerminate " Worker-Threads, die ihre Handles zu einem TThreadList hinzufügen, wenn sie mit der Ausführung beginnen und aus dem gleichen entfernen, wenn ihre Ausführung endet. Sie überprüfen auch ein globales Ereignisobjekt, das sie auffordert, ihre Arbeit abzubrechen.

Es folgt der Teil, der im Hauptthread ausgeführt wird, der das Ereignis signalisiert und darauf wartet, dass mögliche Worker-Threads enden. WorkerHandleList ist das globale ThreadList .

%Vor%


Dieses Design, denke ich, hat einen Fehler darin, dass ich die Liste entsperren muss, bevor ich auf den Handles der Threads warte, weil das einen Deadlock verursachen würde, da die Threads ihre Handles aus derselben Liste entfernen. Ohne eine Sperre könnte ein Kontextwechsel dazu führen, dass sich ein Thread selbst befreit, was dazu führt, dass WaitForMultipleObjects sofort mit WAIT_FAILED zurückkehrt. Ich kann auch keine andere Sperre verwenden, da WaitForMultipleObjects blockiert und ich die Sperre nicht vom Hauptthread lösen kann.

Ich kann diesen Entwurf auf mehrere Arten ändern, einschließlich der Verwendung von FreeOnTerminate threads, die gültige Handles garantieren würden, bis sie explizit freigegeben werden. Oder die Liste der Thread-Handles nur vom Hauptthread aus ändern. Oder wahrscheinlich andere ...

Aber was ich fragen möchte, ist, gibt es eine Lösung für diese Art von Problem, ohne das Design zu ändern? Würden Sie beispielsweise im Worker-Thread-Code schlafen, bevor sie ihre Handles aus der Liste entfernen, oder SwitchToThread aufrufen, weil alle nicht-Worker-Threads ausgeführt werden? Genug laufen?

    
Sertac Akyuz 06.09.2016, 19:00
quelle

2 Antworten

10

Ihre Verwendung von LockList() ist falsch und gefährlich. Sobald Sie UnlockList() aufrufen, ist TList nicht mehr geschützt und wird geändert, wenn sich die Worker-Threads aus der Liste entfernen. Das kann passieren, bevor Sie WaitForMultipleObjects() aufrufen können, oder schlechter WHILE den Aufruf-Stack dafür einrichten.

Sie müssen stattdessen die Liste sperren, die Handles in ein lokales Array kopieren , die Liste entsperren und dann auf das Array warten. Warten Sie nicht direkt auf das TList selbst.

%Vor%

Aber selbst das hat eine Wettlaufsituation. Einige der Worker-Threads sind möglicherweise bereits beendet und haben daher ihre Handles gelöscht, bevor WaitForMultipleObjects() tatsächlich eingegeben wurde. Und die verbleibenden Threads werden ihre Handles zerstören WHILE es läuft. Wie auch immer, es schlägt fehl. Sie können die Thread-Handles NICHT zerstören, während Sie aktiv auf sie warten.

FreeOnTerminate=True kann nur sicher für Threads verwendet werden, die Sie starten, und dann forget even exist . Es ist sehr gefährlich, FreeOnTerminate=True zu verwenden, wenn Sie immer noch auf die Threads für jeden Grund zugreifen müssen (vor allem wegen dieser Einschränkung tendiert TThread.WaitFor() zum Absturz, wenn FreeOnTerminate=True - das Thread-Handle) und selbst das TThread -Objekt selbst wird zerstört, solange es noch benutzt wird!).

Sie müssen Ihre Wartestrategie überdenken. Ich kann mir ein paar Alternativen vorstellen:

  1. Verwenden Sie WaitForMultipleObjects() überhaupt nicht. Es ist sicherer, aber weniger effizient, die Liste regelmäßig neu zu sperren und zu überprüfen, ob sie leer ist oder nicht:

    %Vor%
  2. Befreien Sie sich von WorkerHandleList und verwenden Sie stattdessen einen Semaphor oder einen verriegelten Zähler, um zu verfolgen, wie viele Threads erstellt und noch nicht zerstört wurden. Beenden Sie die Wartezeit, wenn der Semaphor / Zähler anzeigt, dass keine weiteren Threads vorhanden sind.

  3. Wie Ken B vorgeschlagen hat, benutze WorkerHandleList , warte aber auf ein manuelles Zurücksetzen-Ereignis, das zurückgesetzt wird, wenn der erste Thread der Liste hinzugefügt wird (mach das im Thread-Konstruktor, nicht in Execute() ) und signalisiert, wenn der letzte Thread aus der Liste entfernt wird (mach das im Thread-Destruktor, nicht in Execute() oder DoTerminate() ).

Remy Lebeau 06.09.2016, 20:11
quelle
5

Nehmen wir einige Dinge an (dass nur der Hauptthread unter anderem sekundäre starten würde), wäre der einfachste Weg, das Problem zu beheben, der folgende:

%Vor%

Wenn Sie einen CPU-Zyklus verschwenden oder länger als nötig warten, ist das nicht akzeptabel. Sie könnten stattdessen ein Event erstellen und darauf warten und alle Threads würden sich durch die gleiche Funktion aus der Liste entfernen.

%Vor%

In diesem Fall möchten Sie wahrscheinlich ein Ereignis zum manuellen Zurücksetzen verwenden und es in einer Prozedur "AddHandleToList" zurücksetzen.

    
Ken Bourassa 06.09.2016 20:02
quelle