I thread sono in attesa su un blocco FIFO?

Diciamo che ho il seguente codice

static class ... { static object myobj = new object(); static void mymethod() { lock(myobj) { // my code.... } } } 

Quindi diciamo che mentre thread1 ha il lock thread2 tenta di eseguire mymethod. Attenderà il rilascio del blocco o genererà un’eccezione?

Se aspetta, l’ordine è assicurato in modo che se i thread aggiuntivi entrano sono FIFO?

Aggiornate la mia risposta: sono in coda, ma l’ordine non è garantito come FIFO.

Dai un’occhiata a questo link: http://www.albahari.com/threading/part2.aspx

Non è chiaro dal tuo codice come myobj possa essere visibile all’interno di mymethod . Sembra che var myobj sia una variabile stack locale all’ambito della dichiarazione (dato che è var ). In tal caso può accadere che ogni thread abbia un’istanza separata di esso e che il mymethod non blocchi.

Aggiornare

Riguardo l’intero argomento FIFO, alcune informazioni di base sono necessarie: il CLR non fornisce la sincronizzazione. È l’ host CLR che fornisce questo come servizio per il runtime CLR. L’host implementa IHostSyncManager e altre interfacce e fornisce le varie primitive di sincronizzazione. Questo può sembrare irrilevante in quanto l’host più comune è l’host di applicazione tipico (ad esempio, si compila in e exe) e questo interrompe tutta la sincronizzazione al sistema operativo (le vecchie primitive di libro Petzold nell’API Win32). Tuttavia ci sono almeno altri due ambienti di hosting principali: quello di ASP.Net (non sono sicuro di ciò che fa) e SQL Server. Quello che posso dire con certezza è che SQL Server fornisce tutte le primitive sul toup del SOS (che è fondamentalmente un utente più sistema operativo), mai toccando le primitive del sistema operativo, e le primitive SOS sono ingiuste per evitare convessioni di blocco (es. garantito senza FIFO). Come già sottolineato nel link dell’altra risposta, i primitivi del sistema operativo hanno anche iniziato a fornire comportamenti scorretti, per la stessa ragione di evitare convogli di blocco.

Per maggiori informazioni sui convogli di blocco, leggere gli articoli di Rick Vicik su Designing Applications for High Performance :

Lock Convoy

I lucchetti FIFO garantiscono equità e avanzamento dei progressi a scapito della creazione di convogli di blocco. Il termine originariamente significava diversi thread che eseguivano la stessa parte del codice di un gruppo risultando in collisioni più alte rispetto a se fossero distribuiti casualmente nel codice (proprio come le automobili raggruppate in pacchetti dai semafori). Il particolare fenomeno di cui sto parlando è peggio perché una volta formato il passaggio implicito della proprietà del lock mantiene i thread in lock-step.

Per illustrare, considera l’esempio in cui un thread contiene un lucchetto e viene preventivamente trattenuto mentre tiene il lucchetto. Il risultato è che tutti gli altri thread si accumuleranno nella lista di attesa per quel blocco. Quando il thread preempted (proprietario del lock in questo momento) viene eseguito nuovamente e rilascia il blocco, passa automaticamente la proprietà del blocco al primo thread nell’elenco di attesa. Quel thread potrebbe non funzionare per un po ‘di tempo, ma il tempo di “hold time” sta spuntando. Il precedente proprietario di solito richiede nuovamente il blocco prima che la lista di attesa venga cancellata, perpetuando il convoglio

Un semplice esempio ci dice che l’ordine non è garantito come FIFO

  using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; namespace ConsoleApplication { class Program { private static Info info = new Info(); static void Main(string[] args) { Thread[] t1 = new Thread[5]; for (int i = 0; i < 5; i++) { t1[i] = new Thread(info.DoWork); } Thread[] t2 = new Thread[5]; for (int i = 0; i < 5; i++) { t2[i] = new Thread(info.Process); } for (int i = 0; i < 5; i++) { t1[i].Start(); t2[i].Start(); } Console.ReadKey(); } } class Info { public object SynObject = new object(); public void DoWork() { Debug.Print("DoWork Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId); lock (this.SynObject) { Debug.Print("Thread Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Debug.Print("Thread Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId); } } public void Process() { Debug.Print("Process Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId); lock (this.SynObject) { Debug.Print("Process Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Debug.Print("Process Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId); } } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; namespace ConsoleApplication { class Program { private static Info info = new Info(); static void Main(string[] args) { Thread[] t1 = new Thread[5]; for (int i = 0; i < 5; i++) { t1[i] = new Thread(info.DoWork); } Thread[] t2 = new Thread[5]; for (int i = 0; i < 5; i++) { t2[i] = new Thread(info.Process); } for (int i = 0; i < 5; i++) { t1[i].Start(); t2[i].Start(); } Console.ReadKey(); } } class Info { public object SynObject = new object(); public void DoWork() { Debug.Print("DoWork Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId); lock (this.SynObject) { Debug.Print("Thread Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Debug.Print("Thread Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId); } } public void Process() { Debug.Print("Process Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId); lock (this.SynObject) { Debug.Print("Process Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Debug.Print("Process Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId); } } } } 

L'esecuzione procede in questo modo

 Process Lock Reached: 15 Process Lock Enter: 15 DoWork Lock Reached: 12 Process Lock Reached: 17 DoWork Lock Reached: 11 DoWork Lock Reached: 10 DoWork Lock Reached: 13 DoWork Lock Reached: 9 Process Lock Reached: 18 Process Lock Reached: 14 Process Lock Reached: 16 Process Lock Exit: 15 Thread Lock Enter: 9 Thread Lock Exit: 9 Process Lock Enter: 14 Process Lock Exit: 14 Thread Lock Enter: 10 Thread Lock Exit: 10 Thread Lock Enter: 11 Thread Lock Exit: 11 Process Lock Enter: 16 Process Lock Exit: 16 Thread Lock Enter: 12 Thread Lock Exit: 12 Process Lock Enter: 17 Process Lock Exit: 17 Thread Lock Enter: 13 Thread Lock Exit: 13 Process Lock Enter: 18 Process Lock Exit: 18 

Come puoi vedere, il processo di blocco della copertura è diverso da quello inserito.

Windows e CLR fanno del loro meglio per garantire l’equità (l’ordine FIFO) dell’attesa. Tuttavia, ci sono alcuni scenari in cui l’ordine dei thread in attesa su un lock può essere modificato, per lo più ruotando attorno a wait alertable e tutti i thread thread CLR mettono il thread in stato alertabile.

Per tutti gli scopi pratici, si può presumere che l’ordine sarà FIFO; tuttavia, essere a conoscenza di questo problema.

Aspetterà, e NON saranno nello stesso ordine.

A seconda delle esigenze, potresti avere più prestazioni se guardi qualcosa come un ReaderWriterLock o qualcosa di diverso dal semplice lock