Posso produrre un deadlock con un singolo thread? aka. Cos'è un deadlock?

0

Espande " Deadlock in applicazione a thread singolo " su StackOverflow .

Quella domanda non è stata davvero conclusiva e penso che sia meglio appartenere qui.

Con la definizione di un deadlock, è tecnicamente possibile produrre un deadlock usando solo un singolo thread?

Questa è la definizione di un deadlock secondo Wikipedia :

A deadlock situation on a resource can arise if and only if all of the following conditions hold simultaneously in a system:

  1. Mutual exclusion: At least one resource must be held in a non-shareable mode. Otherwise, the processes would not be prevented from using the resource when necessary. Only one process can use the resource at any given instant of time.

  2. Hold and wait or resource holding: a process is currently holding at least one resource and requesting additional resources which are being held by other processes.

  3. No preemption: a resource can be released only voluntarily by the process holding it.
  4. Circular wait: each process must be waiting for a resource which is being held by another process, which in turn is waiting for the first process to release the resource.

Ho provato a creare un deadlock con un thread in C #, in cui la condizione 4 potrebbe non essere letteralmente soddisfatta, ma mi sembra ancora una situazione di stallo. Non sto pubblicando questo codice poiché so che sarebbe fuori tema per questo sito.

    
posta bitbonk 02.12.2018 - 22:09
fonte

3 risposte

4

"Deadlock" significa semplicemente che hai due azioni che si bloccano a vicenda in modo che nessuno dei due possa procedere.

Ovviamente per avere due cose in esecuzione allo stesso tempo normalmente avresti bisogno di più di un "thread".

Ma un thread è solo un livello astratto sulla CPU attuale. Puoi facilmente * scrivere il tuo livello di astrazione e creare deadlock in esso senza utilizzare gli oggetti thread della lingua.

* non così facilmente se vuoi che funzioni bene

lascia fare qualche pseudo codice

List<string> t1; //instruction set one
List<string> t2; //instruction set two

public void Run()
{
    while(1==1)
    {
        //attempt to run instructions one at a time, but timeout of they are 'locked'
        if(Execute(t1[i], timeout:1000)) { i++;}
        if(Execute(t2[j], timeout:1000)) { j++;}
    }
}

Ora, se ho alcune istruzioni t1, creo un blocco A e poi B, ma ho anche il tentativo t2 di bloccare B e poi A. Avrò un deadlock senza System.Threads

    
risposta data 02.12.2018 - 23:12
fonte
3

La definizione di Wikipedia di un deadlock richiede che ci siano più processi (o thread) in possesso del giusto insieme di risorse per far sì che si verifichi. Questa è una buona base per una discussione generale, ma trascura la realtà pratica che alcune implementazioni di mutex si comportano in modo diverso di fronte alla contesa e non si preoccupano di tale tecnicità.

La maggior parte dei sistemi operativi che supportano il threading sono conformi a POSIX, che definisce definisce tre tipi di mutex:

Il primo è il mutex normale , che è la classica implementazione che attenderà uno sblocco, indipendentemente da chi lo ha bloccato:

Mutex m(NORMAL)
m.lock()
m.lock()

La seconda chiamata a lock() attenderà sul thread che ha bloccato m per sbloccarlo. Dato che è lo stesso thread, ciò non accadrà mai e il thread si sarà effettivamente sbloccato. Questo è come produrre un deadlock all'interno di un thread, rendendo tecnicamente possibile ciò che chiedi. A causa di questo rischio, le linee guida di sviluppo tendono a raccomandare di non utilizzare mutex normali.

Il secondo è il mutex error-checking , che evita l'auto-deadlock trattando il tentativo di re-locking come un errore e restituendo il controllo al thread. Il codice del thread può decidere di procedere se un secondo tentativo è ok, ma c'è il rischio che ciò che ha reso la seconda chiamata possa anche chiamare unlock() e lasciare la parte critica vulnerabile all'alterazione da parte di un altro thread. (Questo sarebbe evitato non sbloccandosi se il blocco fallisce perché il mutex era già bloccato.)

Terzo è il mutex ricorsivo , che mantiene il conteggio del numero di volte in cui è stato bloccato dallo stesso thread e richiede un numero corrispondente di sblocchi prima che il mutex venga rilasciato. Questo si occupa del problema di blocco / sblocco multivello.

La documentazione POSIX ha una tabella dei comportamenti di tutti e tre , e tu si noti che il comportamento per relocking un mutex normale è esplicitamente che il thread dovrebbe deadlock.

    
risposta data 03.12.2018 - 02:46
fonte
1

Non in senso interessante. Esistono infiniti modi di scrivere codice che non si fermano. I deadlock sono un sottoinsieme di quelli con effetti particolarmente interessanti e perniciosi, quindi valgono la pena di essere distinti.

Non conosco C #, ma per il tuo esempio particolare, sembra che tu abbia semplicemente scritto una funzione infinitamente ricorsiva offuscata. Cioè, è davvero solo un modo complicato di chiamare

wait_untill_flag_is_true(boolean flag) {
    if (!flag) { 
      wait_untill_flag_is_true(false);
    }
  }

main() {
   wait_until_flag_is_true(false);
}

Questo funzionerà finché lo stack non si riempie. La chiamata ricorsiva è camuffata dietro la chiamata a "Invoke".

Nota che quando viene eseguito su un singolo thread il tuo programma è completamente deterministico e avrà sempre lo stesso risultato. La ragione per cui il deadlock è più sottile e interessante di altre forme di codice non-halting è che può essere stocastico. A volte il codice può funzionare correttamente e altre volte si blocca, a seconda di come il SO ha programmato l'esecuzione dei thread.

Come ho detto, non conosco C #, quindi è possibile che la chiamata a "Invoke" offra una sorta di magia multi-threading dietro le quinte, nel qual caso il commento di @candied_orange cis sul target, e hai semplicemente inventato la tua API di gestione dei thread.

    
risposta data 02.12.2018 - 23:12
fonte

Leggi altre domande sui tag