2012-01-26 17 views
7

Prima di tutto, mi sto ancora familiarizzando con il multi-threading e non conosco molta terminologia. Devo assicurarmi che lo stia facendo bene, perché è un argomento delicato.Gestione del numero dinamico di thread

Specifiche

Quello che sto costruendo è un componente che conterrà una serie dinamica di fili. Ciascuno di questi thread viene riutilizzato per l'esecuzione di un numero di richieste. Posso fornire tutti i dettagli necessari al thread mentre lo creo e prima di eseguirlo, oltre a fornire gestori di eventi. Una volta eseguito, ho praticamente finito con una richiesta e ho inserito un'altra richiesta. Le richieste vengono inserite in questi thread da un altro thread in background autonomo che elabora costantemente una coda di richieste. Quindi questo sistema ha due liste: 1) Elenco dei record delle richieste e 2) Elenco dei puntatori del thread.

Sto utilizzando i discendenti della classe TThread (almeno questo è il metodo di threading con cui ho familiarità). Ricevo feedback dai thread sincronizzando i trigger di eventi che ho assegnato quando sono stati creati i thread. I thread stanno caricando e salvando i dati in background e, una volta terminati, si resettano pronti per elaborare la richiesta successiva.

Problema

Ora il problema inizia al momento di decidere come gestire il caso di cambiare il numero di thread consentiti (tramite una proprietà del componente ActiveThreads: TActiveThreadRange che TActiveThreadRange = 1..20). Pertanto, ci può essere ovunque tra 1 e 20 thread creati alla volta. Ma quando, diciamo, l'applicazione che usa questo componente cambia questa proprietà da 5 a 3. In questo momento, ci sono già 5 thread creati, e non voglio forzatamente liberare quel thread se è occupato. Devo aspettare che sia fatto prima che lo liberi. E d'altra parte, se la proprietà viene modificata da 3 a 5, quindi ho bisogno di creare 2 nuovi thread. Devo conoscere l'approccio corretto per "tenere traccia" di questi thread in questo scenario.

possibilità

Ecco alcuni modi possibili che posso pensare a 'traccia' questi fili ...

  • mantenere un TList contenente ogni thread creato - facile da gestire
  • Creare un wrapper o discendente TList contenente ciascun thread creato - più facile da gestire, ma più lavoro
  • Mantieni un contatore array ning ogni thread creato - Sarebbe meglio di uno TList?
  • Creare un involucro matrice contenente ogni thread creato

Ma poi di nuovo al mio problema originale - Cosa fare con fili occupati esistenti quando la proprietà ActiveThreads è diminuita? La loro creazione non è un problema, ma rilasciandoli sta diventando confuso. Solitamente realizzo fili che si liberano, ma questa è la prima volta che ne faccio uno che viene riutilizzato. Ho solo bisogno di conoscere il metodo corretto per distruggere questi thread senza interrompere i loro compiti.

Aggiornamento

Sulla base del feedback, ho acquistato e iniziato ad attuare l'OmniThreadLibrary (così come il FastMM lungo necessario). Ho anche cambiato il mio approccio un po '- Un modo che io possa creare questi processi filettate senza riuscire di loro e senza un altro thread per elaborare la coda ...

  • 1 metodo master per generare un nuovo processo
    • function NewProcess(const Request: TProcessRequest): TProcessInfo;
    • TProcessRequest è un record con le specifiche di quello che sta per essere fatto (Nome file, Opzioni, etc.)
    • TProcessInfo è un record che passa di nuovo alcune informazioni di stato.
  • Feed in un gestore di eventi per l'evento di essere "terminato" con la sua attività durante la creazione di un nuovo processo. Quando il componente riceve questo messaggio, controllerà la coda.
    • Se comando è in coda, confronta il limite del processo attivo con processo corrente conteggio
    • > Se supera limite, basta fermarsi e processo successivo completamento farà effettuare stesso controllo
    • > Se entro limiti, kick off un'altra nuovo processo (dopo aver verificato processo precedente viene svolto)
    • Se nessun comando vengono accodati, quindi basta smettere
  • Ogni processo può morire da solo dopo aver svolto il suo compito (senza filettatura keep-alive)
  • Io non devono preoccuparsi di un altro timer o filo per continuamente scorrere
    • Invece ogni processo distrugge la sua auto e controlli per le nuove richieste prima di farlo

Un altro aggiornamento

In realtà sono tornato a utilizzare uno TThread, in quanto l'OTL è molto scomodo da utilizzare. Mi piace mantenere le cose avvolte e organizzate nella sua stessa classe.

+0

Quello che stai chiedendo è noto come "pool di thread"; forse questo ti aiuterà a trovare risorse. –

+5

Vedere [Delphi-threaded-list-of-thread-jobs-accodamento] (http://stackoverflow.com/questions/1805633/delphi-threaded-list-of-thread-jobs-queueing). Inoltre, non creare un pool di thread personalizzato, consulta [OTL-OmniThreadLibrary] (http://code.google.com/p/omnithreadlibrary/). –

+0

La mia esperienza personale è che è più facile lavorare direttamente con l'API di Windows piuttosto che fare affidamento su 'TThread' se si sta facendo un lavoro più sofisticato. In effetti, è * molto * facile iniziare a utilizzare le API di Windows. Inizia facendo semplici esperimenti con ['CreateThread'] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453 (v = vs.85) .aspx). Fai attenzione che tu, come sviluppatore Delphi, probabilmente dovresti usare il wrapper 'System.BeginThread' invece di' CreateThread', ma, naturalmente, la documentazione MSDN di 'CreateThread' è ancora valida. –

risposta

3

Informazioni sul problema con il decremento di thread attivi: scusate, ma dovete semplicemente decidere da soli. Liberare immediatamente i fili indesiderati (che li interrompe il prima possibile) o lasciarli correre finché non sono terminati (che li interrompe dopo che tutto il lavoro è terminato). È una tua scelta. Ovviamente è necessario separare la variabile per il numero desiderato da quello del numero effettivo di thread. Il problema dell'aggiornamento del numero effettivo di variabili thread (potrebbe essere semplicemente un List.Count) è esattamente lo stesso, poiché entrambe le soluzioni richiedono del tempo.

E sulla gestione di più thread: è possibile studiare this answer che memorizza i thread in un TList. Ha bisogno di un piccolo ritocco per la tua lista di desideri specifica, per favore urla in caso di bisogno di assistenza con quello. Inoltre, ci sono ovviamente più possibili implementazioni che possono essere derivate dall'uso del TThread predefinito. E si noti che esistono altre (multi) librerie di thread, ma non ho mai avuto la necessità di usarle.

+1

Non c'è differenza tra liberare immediatamente e aspettare che siano finiti. 'Destroy' chiama' WaitFor'. –

+2

FreeOnTerminate: = true. Passa un compito di pillola velenosa nella coda dei compiti. Nessuna flag, nessun TThread.WaitFor, non è necessario accedere all'istanza TThread, quindi non è necessario alcun elenco di thread, quindi non è necessaria la gestione delle liste di thread, quindi è stata eliminata la maggior parte del codice di generazione di deadlock. –

+0

@David Dipende dal compito della discussione. Se il thread ha un ciclo che controlla bene la flag Terminated, allora c'è. Se il thread fa una sola cosa e l'impostazione Terminated to False non ha alcuna influenza sul tempo di esecuzione, allora hai ragione, ma poi non capisco la domanda dell'OP. – NGLN

5

Come spiegato da @NGLN ecc., È necessario raggruppare alcuni thread e accettare che il modo più semplice per gestire i numeri di thread è quello di separare il numero effettivo di thread dal numero desiderato. Aggiungere thread al pool è semplice: basta creare un numero maggiore di istanze, (passando la coda di input del compito produttore-consumatore come parametro in modo che il thread sappia cosa attendere). Se il numero desiderato di thread è inferiore a quello attualmente esistente, è possibile mettere in coda un numero sufficiente di "pillole avvelenate" per eliminare i thread aggiuntivi.

Non tenere alcun elenco di puntatori di thread: è un carico di problemi di microgestione che non è necessario (e probabilmente andrà male). Tutto ciò che devi tenere è un conteggio del numero di thread desiderati nel pool in modo da sapere quale azione intraprendere quando qualcosa cambia la proprietà 'poolDepth'.

I trigger di evento vengono caricati nel modo migliore nei lavori rilasciati al pool: discendono tutti da una classe 'TpooledTask' che accetta un evento come parametro del costruttore e lo memorizza in alcuni TNotifyEvent 'FonComplete'. Il thread che esegue l'attività può chiamare FonComplete quando ha terminato il lavoro, (con TpooledTask come parametro del mittente) - non è necessario sapere quale thread ha eseguito l'attività.

Esempio:

unit ThreadPool; 

    interface 

    uses 
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
     Dialogs, StdCtrls, contnrs, syncobjs; 


    type 

    TpooledTask=class(TObject) 
    private 
     FonComplete:TNotifyEvent; 
    protected 
     Fparam:TObject; 
     procedure execute; virtual; abstract; 
    public 
     constructor create(onComplete:TNotifyEvent;param:TObject); 
    end; 

    TThreadPool=class(TObjectQueue) 
    private 
     access:TcriticalSection; 
     taskCounter:THandle; 
     threadCount:integer; 
    public 
     constructor create(initThreads:integer); 
     procedure addTask(aTask:TpooledTask); 
    end; 

    TpoolThread=class(Tthread) 
    private 
     FmyPool:TThreadPool; 
    protected 
     procedure Execute; override; 
    public 
     constructor create(pool:TThreadPool); 
    end; 

    implementation 

    { TpooledTask } 

    constructor TpooledTask.create(onComplete: TNotifyEvent; param: TObject); 
    begin 
     FonComplete:=onComplete; 
     Fparam:=param; 
    end; 

    { TThreadPool } 

    procedure TThreadPool.addTask(aTask: TpooledTask); 
    begin 
     access.acquire; 
     try 
     push(aTask); 
     finally 
     access.release; 
     end; 
     releaseSemaphore(taskCounter,1,nil); // release one unit to semaphore 
    end; 

    constructor TThreadPool.create(initThreads: integer); 
    begin 
     inherited create; 
     access:=TcriticalSection.create; 
     taskCounter:=createSemaphore(nil,0,maxInt,''); 
     while(threadCount<initThreads) do 
     begin 
     TpoolThread.create(self); 
     inc(threadCount); 
     end; 
    end; 

    { TpoolThread } 

    constructor TpoolThread.create(pool: TThreadPool); 
    begin 
     inherited create(true); 
     FmyPool:=pool; 
     FreeOnTerminate:=true; 
     resume; 
    end; 

procedure TpoolThread.execute; 
var thisTask:TpooledTask; 
begin 
    while (WAIT_OBJECT_0=waitForSingleObject(FmyPool.taskCounter,INFINITE)) do 
    begin 
    FmyPool.access.acquire; 
    try 
     thisTask:=TpooledTask(FmyPool.pop); 
    finally 
     FmyPool.access.release; 
    end; 
    thisTask.execute; 
    if assigned(thisTask.FonComplete) then thisTask.FonComplete(thisTask); 
    end; 
end; 

end. 
+1

@Jerry - in risposta alla tua prossima domanda 'Come posso chiamare TThread.Sychronize se non ho un'istanza di thread?' - Non farlo neanche io. Se sono necessari i risultati del thread principale, postMessage l'attività nel gestore OnComplete. –

+0

La maggior parte di questo è che devo riutilizzare questi thread. Ogni thread verrà creato e in esecuzione non-stop. Se non ha richieste da elaborare, sarà solo morto. Ma quando viene assegnato un compito da eseguire, continuerà a essere riutilizzato per ogni compito assegnato. Come accennato in precedenza, il tuo metodo di "dimenticanza" su di loro è quello a cui sono già abituato - ma il concetto di riutilizzarli è dove mi sto perdendo. –

+0

Jerry, di solito ho una lista di discussione sicura (o una coda se preferisci). Ogni thread ha un collegamento a questa coda e dorme quando è vuoto. Finché ci sono elementi da rimuovere dalla coda, i thread sono occupati. –

4

È possibile implementare FreeNotify messaggio nella vostra coda di richieste e quando thread di lavoro receieve questo messaggio affrancarsi. Nel tuo esempio quando riduci il numero di thread da 5 a 3 metti solo 2 messaggi FreeNotify nella tua coda e 2 thread di lavoro saranno liberi.

+1

Sì, il modo più semplice. Non terminare/WaitFor/OnTerminate, (cioè senza deadlock :). –

+0

+1 Suoni molto probabilmente una buona idea. Tutto quello che dovrei capire è come decidere * quali * 2 dei 5 sono più In altre parole, se ho 5 thread, 3 sono occupati e 2 sono inattivi, quindi voglio distruggere quelli 2 che sono inattivi. Non ho bisogno di aiuto con questo, solo per puntare fuori ... –

+1

Non devi capire un ything. Se hai 5 thread, 3 sono occupati e vuoi uccidere 3, spingere 3 pillole veleno. I due fili inutilizzati riceveranno immediatamente la loro richiesta di suicidio e la annideranno. L'altra pillola rimane in coda fino a quando un altro thread ha terminato il suo compito, ottiene l'ultima dose e muore. Questo lascia 2 thread - lavoro fatto! –