Hoe een win32-thread stoppen die blokkeert?

Ik heb een aangepaste ThreadPool gemaakt die een aantal win32-threads start met _beginthreadex() . De threads draaien op een eenvoudige lus die probeert taken uit een blokkeerwachtrij te verwijderen, maar soms moet ik de threads stoppen en als ze zijn geblokkeerd op Dequeue weet ik niet hoe ik de code moet ophalen threads uit die blokkeringstoestand.

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
            continue;
        }
    }
}

Mijn idee was om hetzelfde aantal speciale taken in te voeren (laten we ze "beëindigingstaken" noemen) omdat er threads in de pool zijn en elke "beëindigingstaak" _endthreadex (0) zal aanroepen om te sluiten de draad. Als er andere taken in de blokkeerwachtrij staan, zal het me niet echt schelen, want zodra ik een taak uitschakel, voer ik deze uit en controleer ik de vlag _running om te bepalen of de thread nodig heeft om nog meer taken op te schorten.

void TerminationTask::Run()
{
    _endthreadex(0);
}

Ik heb verschillende zorgen over deze aanpak; voornamelijk, als ik een niet-beëindigende taak heb verwerkt en de vlag _running is ingesteld op false , zal mijn thread _endthreadex (0) niet aanroepen wanneer het de lus verlaat. Ik vroeg me af of ik aan het einde van de lus _endthreadex (0) kon bellen als volgt:

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
            continue;
        }
    }
    _endthreadex(0);
}

Zal dit een conflict veroorzaken met mijn TerminationTask of zal de thread de lus direct verlaten na het uitvoeren van TerminationTask :: Run() (dwz het zal _endthreadex (niet aanroepen) 0) twee keer)? Verder is er een betere aanpak dan dit?

5

2 antwoord

Aan het einde van de thread-methode is het aanroepen van _endthreadex (0) prima. Het is ook optioneel. Als u de draadmethode gewoon verlaat, wordt _endthreadex (0) voor u opgeroepen.

Je kunt _endthread of _endthreadex expliciet aanroepen om een ​​thread te beëindigen; _endthread of _endthreadex wordt echter automatisch aangeroepen wanneer de thread terugkeert uit de routine die als een parameter is doorgegeven aan _beginthread of _beginthreadex. ref

Het verzenden van een beëindigingstaak is de juiste manier om een ​​thread met geblokkeerde thread te deblokkeren en te stoppen.

Dus om samen te vatten:

  1. Je strategie is goed en de implementatie van TerminationTask :: Run is correct.
  2. U kunt de onschadelijke aanroep verwijderen naar _endthreadex (0) aan het einde van ThreadPool :: Loop .
6
toegevoegd
Ah, snap het ... ik zal de onnodige _endthreadex (0) verwijderen! Bedankt voor de bevestiging!
toegevoegd de auteur Kiril, de bron
Ik heb de vlag _running nodig voor het geval ik een hoop taken in de wachtrij heb staan ​​en ik wil stoppen voordat alle taken zijn verwerkt. De beëindigingstaak is alleen nuttig als ik geen taken in de wachtrij heb en ik wil de blokkeringsstatus verlaten. Met andere woorden: ik kan een ThreadPool :: Terminate hebben (waarmee alle taken kunnen worden verwerkt door simpelweg een TerminateTask aan het einde van de blokkeerwachtrij toe te voegen en de in wachtrij) en ik zou een ThreadPool :: TerminateNow -methode kunnen hebben die verwerkingstaken eenvoudigweg stopt, ongeacht wat er in de wachtrij staat.
toegevoegd de auteur Kiril, de bron
@JohnDibling kun je uitleggen hoe je dat moet doen? Uw opmerking bracht me aan het denken toen de Runnable -taak een blokkeercode had ... Ik heb daar geen controle over, dus zou het een goed beleid zijn om de Runnable ook? Misschien moet ik een soort van beleid implementeren voor de Runnable zodat het een "interrupt" -methode heeft die het uit een blokkerende toestand haalt (wat de verantwoordelijkheid is van de persoon die de interface implementeert om te garanderen dat).
toegevoegd de auteur Kiril, de bron
@David, om de een of andere reden had ik geconcludeerd dat Windows ThreadPool op de een of andere manier onvoldoende was, dus besloot ik om mijn eigen ThreadPool te schrijven die het gedrag van de Java ThreadPool (ik vind gewoon de manier waarop het werkt in Java). Ik zal echter de Winapi opnieuw bekijken en ik zal proberen te zien of ik echt een geldige reden had om de Winapi Thread Pool niet te gebruiken.
toegevoegd de auteur Kiril, de bron
+1 maar een paar opties zijn mogelijk 1) stel een "doodsgebeurtenis" in om een ​​draad geblokkeerd in WaitForMultipleObjects te wekken of 2) Stuur een "doodstraf" via QueueUserAPC
toegevoegd de auteur John Dibling, de bron
Als terzijde, heb je de lus while (_running) nodig? Wanneer ik dit heb gedaan, heb ik while (true) geschreven en vervolgens een beëindigingstaak verzonden om een ​​einde te maken aan de procedure. Aangezien u een beëindigingsmechanisme heeft, lijkt het oneigenlijk om nog een ander mechanisme te hebben in de vlag _running . Inderdaad, ik vermoed dat je code zo onnodig ingewikkeld zou zijn en het potentieel zou hebben voor raceomstandigheden.
toegevoegd de auteur David Heffernan, de bron
OK ik begrijp het!
toegevoegd de auteur David Heffernan, de bron
Best moeilijk om asynchroon opheffen te implementeren. Frameworks zoals .net bieden dat niveau van functionaliteit. Een andere gedachte, doet de standaard Windows threadpool niet wat je nodig hebt. Je hebt toch de vraag winapi gecodeerd.
toegevoegd de auteur David Heffernan, de bron

Een taak in de wachtrij in de wachtrij plaatsen is correct. Ik zou echter een andere benadering proberen om het probleem aan te pakken:

class TerminateThreadNow {};

void TerminationTask::Run()
{
    throw TerminateThreadNow();
} 

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(TerminateThreadNow&)
        {
            _running = false;
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
        }
    }
} 
0
toegevoegd