sistemas recursos que prevencion operativos modelacion introduccion interbloqueos interbloqueo grafo espera datos bloqueos banquero asignación algoritmo c# .net multithreading deadlock

c# - recursos - Código de muestra para ilustrar un interbloqueo utilizando el bloqueo(esto)



prevencion de interbloqueo(sistemas operativos) (6)

He leído varios artículos y publicaciones que dicen que el lock(this) , el lock(typeof(MyType)) , el lock("a string") son todas malas prácticas porque otro hilo podría bloquearse en la misma clave y provocar un interbloqueo. Para comprender este problema, estaba intentando crear un código de ejemplo para ilustrar el punto muerto, pero no he podido envolver mi cabeza en torno a esto.

¿Puede alguien escribir un poco de código conciso que ilustre este problema clásico? Por favor, sea breve, solo puedo digerir el código en trozos más pequeños.

Edit: creo que lassevk lo resume bien; que el verdadero problema es que has perdido el control sobre tus bloqueos. Una vez que eso sucede, no puede controlar el orden en que se llaman los bloqueos, y está permitiendo una situación de punto muerto potencial.

lock(this) , lock(typeof(MyType)) , etc. todas son situaciones en las que ha elegido un bloqueo que es imposible de controlar.


Claro aquí tienes.

Tenga en cuenta que el ejemplo común para un interbloqueo es cuando adquiere varios bloqueos y dos o más subprocesos terminan esperándose uno al otro.

Por ejemplo, dos hilos que se cierran así:

Thread 1 Thread 2 Lock "A" Lock "B" Lock "B" Lock "A" <-- both threads will stop dead here waiting for the lock to be come available.

Sin embargo, en este ejemplo no me molesté en eso, solo dejé que un hilo se bloquee indefinidamente. Realmente no quieres perder el control sobre tus bloqueos, así que, si bien este es un ejemplo idóneo, el hecho de que el hilo de fondo pueda bloquear completamente el hilo principal de esta manera es malo.

using System; using System.Threading; namespace ConsoleApplication7 { public class Program { public static void Main(string[] args) { LockableClass lockable = new LockableClass(); new Thread(new ParameterizedThreadStart(BackgroundMethod)).Start(lockable); Thread.Sleep(500); Console.Out.WriteLine("calling Reset"); lockable.Reset(); } private static void BackgroundMethod(Object lockable) { lock (lockable) { Console.Out.WriteLine("background thread got lock now"); Thread.Sleep(Timeout.Infinite); } } } public class LockableClass { public Int32 Value1 { get; set; } public Int32 Value2 { get; set; } public void Reset() { Console.Out.WriteLine("attempting to lock on object"); lock (this) { Console.Out.WriteLine("main thread got lock now"); Value1 = 0; Value2 = 0; } } } }


El problema es que el bloqueo ("una cadena") se bloquea en un singleton. Esto significa que otros objetos que usan el mismo bloqueo podrían ser una espera infinita.

por ejemplo:

using System; using System.Threading; namespace ThreadLock { class Program { static void Main(string[] args) { lock ("my lock") { ManualResetEvent evt = new ManualResetEvent(false); WorkerObject worker = new WorkerObject(evt); Thread t = new Thread(new ThreadStart(worker.Work)); t.Start(); evt.WaitOne(); } } } class WorkerObject { private ManualResetEvent _evt; public WorkerObject(ManualResetEvent evt) { _evt = evt; } public void Work() { lock ("my lock") { Console.WriteLine("worked."); _evt.Set(); } } } }

En este caso, el código de llamada crea un bloqueo en una cadena y luego crea un objeto de trabajo. El objeto de trabajo en Work () se bloquea en la misma cadena, que es un singleton en C #. Termina en un punto muerto porque la persona que llama posee el bloqueo y está esperando una señal que nunca llegará.


Esta es la maldad bastante estándar. Arrancando las cerraduras fuera de orden y luego durmiendo con la cerradura. Dos cosas malas que hacer. :)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace DeadLock { public class Program { static void Main(string[] args) { var ddt = new DontDoThat(); ddt.Go(); } } public class DontDoThat { private int _badSharedState = 0; private readonly object _lock1 = new object(); private readonly object _lock2 = new object(); public void Go() { new Thread(BadGuy1).Start(); new Thread(BadGuy2).Start(); Console.WriteLine("Leaving Go!"); } public void BadGuy1() { lock (_lock1) { Thread.Sleep(100); // yeild with the lock is bad lock (_lock2) { _badSharedState++; Console.Write("From Bad Guy #1: {0})", _badSharedState ); } } } public void BadGuy2() { lock (_lock2) { lock (_lock1) { _badSharedState++; Console.Write("From Bad Guy #2: {0})", _badSharedState); } } } } }


La idea es que nunca debe bloquear algo a lo que no puede controlar quién tiene acceso.

Los objetos de tipo son singletones visibles para cada pieza de código .net y no puede controlar quién bloquea su "este" objeto desde el exterior.

Lo mismo ocurre con las cadenas: dado que las cadenas son inmutables, el marco guarda solo una instancia de cadenas "codificadas" y las coloca en un grupo (se dice que la cadena está internada), si escribe dos veces en su código la cadena " hola ", siempre obtendrás el mismo abyecto.

Considere el siguiente ejemplo: usted escribió solo Thread1 en su llamada súper privada, mientras que la biblioteca que está usando en un subproceso en segundo plano llama a Thread2 ...

void Thread1() { lock (typeof(int)) { Thread.Sleep(1000); lock (typeof(long)) // do something } } void Thread2() { lock (typeof(long)) { Thread.Sleep(1000); lock (typeof(int)) // do something } }


Sólo se producirá un interbloqueo si tiene más de un bloqueo. Necesita una situación donde ambos subprocesos contienen un recurso que el otro necesita (lo que significa que debe haber al menos dos recursos, y los dos subprocesos deben intentar adquirirlos en un orden diferente)

Así que un ejemplo simple:

// thread 1 lock(typeof(int)) { Thread.Sleep(1000); lock(typeof(float)) { Console.WriteLine("Thread 1 got both locks"); } } // thread 2 lock(typeof(float)) { Thread.Sleep(1000); lock(typeof(int)) { Console.WriteLine("Thread 2 got both locks"); } }

Suponiendo que ambos hilos se inician a menos de un segundo de cada uno, ambos tendrán tiempo de agarrar el primer bloqueo antes de que alguien llegue al bloqueo interno. Sin la llamada Sleep (), es probable que uno de los subprocesos tenga tiempo para obtener y liberar ambos bloqueos antes de que el otro subproceso comience.


class Character { public Character Other; public string Name; private object locker = new object(); public Character(string name) { Name = name; } public void Go() { lock (locker) { Thread.Sleep(1000); Console.WriteLine("go in {0}", Name); Other.Go(); } } } class Program { static void Main(string[] args) { Character a = new Character("A"); Character b = new Character("B"); a.Other = b; b.Other = a; new Thread(a.Go).Start(); b.Go(); Console.ReadLine(); } }