Comportamento strano quando si modifica un oggetto bloccato in modo esclusivo - Monitor.Enter (x)

StackOverflow https://stackoverflow.com/questions/828014

  •  06-07-2019
  •  | 
  •  

Domanda

Volevo vedere cosa è successo se si modifica il riferimento di un oggetto bloccato esclusivamente da Monitor.Enter (). Come previsto, è stata generata una SynchronizationLockException. Ma sono stato sorpreso di vedere diversi thread superare il monitor prima che l'eccezione fosse lanciata.

Ecco cosa sta facendo il codice qui sotto.

  1. crea e avvia 100 thread
  2. fa attendere che tutto il thread sia impostato fino a quando viene impostato ManualResetEvent.
  3. imposta ManualResetEvent - Un po 'come sventolare la bandiera verde in una gara Indy.
  4. imposta un blocco esclusivo (Monitor.Enter (x)) su x
  5. cambia il riferimento di x.

A questo punto mi aspettavo che venisse generata una sorta di eccezione, ma ciò non accade fino a Monitor.Exit (x). La cosa davvero strana è che 10-20 thread sembrano essere in grado di superare il blocco prima che venga sollevata l'eccezione. Come succede? Non sembra che dovrebbe. Naturalmente cambiare il riferimento dell'oggetto bloccato esclusivamente è un no-no. Non lo farei mai in codice reale, ma sono rimasto sorpreso nel vedere gli altri thread oltrepassare il monitor. I vostri pensieri?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

 Output console, sembra che alcuni thread abbiano superato il monitor ??

È stato utile?

Soluzione

Quello che vedi è il comportamento previsto. Non c'è nulla di speciale sulla variabile effettiva utilizzata per passare un riferimento in Monitor.Enter () . La modifica del riferimento non dovrebbe impedire ad altri thread di acquisire un blocco esclusivo, poiché la variabile ha un nuovo valore e tale riferimento non è bloccato da nessuna parte.

Il tuo problema si presenta con Exit , perché il thread che chiama Exit non ha un blocco esclusivo sul riferimento che viene passato. Un altro thread potrebbe avere un blocco su ma il thread che stai eseguendo non lo fa.

Questo, come sai, è il motivo per cui è sempre meglio eseguire il blocco con una variabile il cui riferimento non cambierà mai. Se la variabile della tua risorsa potrebbe cambiare, usa un nuovo riferimento.

È abbastanza chiaro?

Altri suggerimenti

'x' è un riferimento a un oggetto; non è l'oggetto stesso. Quando lo fai

      x = Thread.CurrentThread.ManagedThreadId.ToString();

Hai gettato via l'oggetto bloccato a cui x si riferiva in precedenza e hai fatto in modo che x si riferisse a qualche altro oggetto. Ora quando lo fai

      Monitor.Exit(x);

Ottieni l'eccezione perché questo oggetto non è in realtà bloccato: l'oggetto che hai bloccato ora è spazzatura da raccogliere dal garbage collector.

La ragione di questo comportamento è che stai cambiando il valore di x qui:

x = Thread.CurrentThread.ManagedThreadId.ToString();

Quindi quando arrivi a

Monitor.Exit(x)

stai rilasciando il lucchetto con un altro oggetto. È come se metti un lucchetto con una chiave e provi a rimuovere il lucchetto con la chiave da un altro lucchetto.

Inoltre, Console.Writeline è un'istruzione costosa rispetto alle altre, quindi diversi thread entrano nel monitor prima che uno di essi si avvicini al traguardo.

Ecco un esempio:

Inizi un mucchio di discussioni.

  • Il thread T1 acquisisce il blocco con l'oggetto x .
  • T2 tenta di acquisire il blocco con l'oggetto x . Questo thread è sfortunato, poiché sappiamo che aspetterà per sempre.
  • T1 cambia x . Ora è un nuovo oggetto. Lo chiamerò x'1 .
  • T3 tenta di acquisire il blocco. Ma la variabile x fa effettivamente riferimento all'oggetto x'1 . Nessuno ha bloccato x'1 , quindi passa.
  • T3 cambia x . Ora è un nuovo oggetto chiamato x'3 .
  • T1 scrive sulla console.
  • T3 scrive sulla console.
  • T1 tenta di rilasciare il blocco con la variabile x , che punta all'oggetto ... x'3 .
  • L'oggetto Monitor dice: " Ehi, cosa pensi di fare? Non hai quel lucchetto! Mangia questa eccezione piccola succhia "
  • Fin
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top