Como se mencionó anteriormente, cada hilo tiene sus propios recursos, pero el área de código es compartida, es decir, cada hilo puede ejecutar la misma función. El problema que esto puede ocasionar es que varios hilos ejecuten una función al mismo tiempo, provocando confusión de datos y resultados impredecibles, por lo que debemos evitar esta situación.
C# proporciona un bloqueo de palabra clave, que puede definir una sección de código como una sección mutuamente excluyente (sección crítica). La sección mutuamente excluyente permite que solo un subproceso entre en ejecución a la vez, mientras que otros subprocesos deben esperar. En C#, el bloqueo de palabras clave se define de la siguiente manera:
bloquear (expresión) declaración_bloque
La expresión representa el objeto que desea rastrear, generalmente una referencia de objeto.
Statement_block es el código de la sección mutuamente excluyente. Este código solo puede ser ejecutado por un hilo a la vez.
El siguiente es un ejemplo típico del uso de la palabra clave lock. El uso y propósito de la palabra clave lock se explican en los comentarios.
Los ejemplos son los siguientes:
Código
usando Sistema;
usando System.Threading;
espacio de nombres ThreadSimple
{
cuenta de clase interna
{
equilibrio internacional;
Aleatorio r = nuevo Aleatorio();
Cuenta interna (int inicial)
{
saldo = inicial;
}
retiro int interno (cantidad int)
{
si (saldo < 0)
{
//Si el saldo es menor que 0, lanza una excepción
lanzar una nueva excepción ("Saldo negativo");
}
// Se garantiza que el siguiente código se completará antes de que el hilo actual modifique el valor del saldo.
//Ningún otro hilo ejecutará este código para modificar el valor del saldo
//Por lo tanto, el valor del saldo no puede ser menor que 0
bloquear (esto)
{
Console.WriteLine("Subproceso actual:"+Thread.CurrentThread.Name);
// Si no hay protección para la palabra clave lock, puede ser después de ejecutar el juicio condicional if.
//Otro hilo ejecutó saldo=cantidad-saldo y modificó el valor del saldo.
//Esta modificación es invisible para este hilo, por lo que puede provocar que la condición if ya no se cumpla.
//Sin embargo, este hilo continúa ejecutando saldo=cantidad-saldo, por lo que el saldo puede ser menor que 0
si (saldo >= importe)
{
Hilo.Sleep(5);
saldo = saldo - monto;
monto de devolución;
}
demás
{
devolver 0; // transacción rechazada
}
}
}
vacío interno DoTransactions()
{
para (int i = 0; i < 100; i++)
Retirar(r.Siguiente(-50, 100));
}
}
prueba de clase interna
{
Hilo interno estático [] hilos = nuevo hilo [10];
vacío estático público principal ()
{
Cuenta cuenta = nueva cuenta (0);
para (int i = 0; i < 10; i++)
{
Hilo t = nuevo hilo (nuevo ThreadStart (acc.DoTransactions));
hilos[i] = t;
}
para (int i = 0; i < 10; i++)
hilos[i].Name=i.ToString();
para (int i = 0; i < 10; i++)
hilos[i].Inicio();
Consola.ReadLine();
}
}
}La clase Monitor bloquea un objeto
Cuando varios subprocesos comparten un objeto, también ocurrirán problemas similares al código común. Para este tipo de problema, no se debe usar la palabra clave lock en System.Threading. Aquí podemos llamarlo monitor. , Monitor proporciona una solución para que los subprocesos compartan recursos.
La clase Monitor puede bloquear un objeto y un subproceso puede operar en el objeto sólo si obtiene el bloqueo. El mecanismo de bloqueo de objetos garantiza que solo un subproceso pueda acceder a este objeto a la vez en situaciones que puedan causar caos. El monitor debe estar asociado con un objeto específico, pero debido a que es una clase estática, no se puede usar para definir objetos y todos sus métodos son estáticos y no se puede hacer referencia a ellos mediante objetos. El siguiente código ilustra el uso de Monitor para bloquear un objeto:
...
Cola oQueue=nueva cola();
...
Monitor.Enter(oQueue);
......//Ahora el objeto oQueue solo puede ser manipulado por el hilo actual
Monitor.Exit(oQueue);//Libera el bloqueo
Como se muestra arriba, cuando un hilo llama al método Monitor.Enter() para bloquear un objeto, el objeto es de su propiedad. Si otros hilos quieren acceder a este objeto, solo pueden esperar a que use Monitor.Exit(). método para liberar el bloqueo. Para garantizar que el hilo finalmente libere el bloqueo, puede escribir el método Monitor.Exit() en el bloque de código finalmente en la estructura try-catch-finally.
Para cualquier objeto bloqueado por Monitor, cierta información relacionada con él se almacena en la memoria:
Una es una referencia al hilo que actualmente contiene el candado;
La segunda es una cola de preparación, que almacena subprocesos que están listos para adquirir bloqueos;
La tercera es una cola de espera, que contiene una referencia a la cola que actualmente está esperando que cambie el estado del objeto.
Cuando el subproceso propietario del bloqueo del objeto está listo para liberar el bloqueo, utiliza el método Monitor.Pulse() para notificar al primer subproceso en la cola de espera, de modo que el subproceso se transfiera a la cola de preparación cuando se libera el bloqueo del objeto. , En la cola de preparación, el hilo puede adquirir inmediatamente el bloqueo del objeto.
El siguiente es un ejemplo que muestra cómo usar la palabra clave lock y la clase Monitor para implementar la sincronización y comunicación de subprocesos. También es un problema típico de productores y consumidores.
En esta rutina, el hilo productor y el hilo consumidor se alternan. El productor escribe un número y el consumidor lo lee y muestra inmediatamente (la esencia del programa se presenta en los comentarios).
Los espacios de nombres del sistema utilizados son los siguientes:
usando Sistema;
usando System.Threading;
Primero, defina una clase Cell del objeto que se está operando. En esta clase, hay dos métodos: ReadFromCell() y WriteToCell. El hilo del consumidor llamará a ReadFromCell() para leer el contenido de cellContents y mostrarlo, y el proceso productor llamará al método WriteToCell() para escribir datos en cellContents.
Los ejemplos son los siguientes:
Código
celular de clase pública
{
int cellContents; //Contenido del objeto Cell
bool ReaderFlag = false; // Indicador de estado, cuando es verdadero, se puede leer, cuando es falso, se escribe
público int ReadFromCell ()
{
lock(this) // ¿Qué garantiza la palabra clave Lock? Lea la introducción anterior a lock.
{
if (!readerFlag)//Si no es legible ahora
{
intentar
{
//Espera a que se llame al método Monitor.Pulse() en el método WriteToCell
Monitorear.Esperar(esto);
}
captura (SynchronizationLockException e)
{
Consola.WriteLine(e);
}
captura (ThreadInterruptedException e)
{
Consola.WriteLine(e);
}
}
Console.WriteLine("Consumir: {0}",cellContents);
banderalector = falso;
//Restablece el indicador ReaderFlag para indicar que el comportamiento de consumo se ha completado
Monitor.Pulse(esto);
//Notificar al método WriteToCell() (este método se ejecuta en otro hilo y está en espera)
}
devolver contenido de celda;
}
escritura vacía pública en celda (int n)
{
bloquear (esto)
{
si (bandera de lector)
{
intentar
{
Monitorear.Esperar(esto);
}
captura (SynchronizationLockException e)
{
//Cuando se llaman métodos sincronizados (que se refieren a métodos de la clase Monitor excepto Enter) en áreas de código asíncrono
Consola.WriteLine(e);
}
captura (ThreadInterruptedException e)
{
//Abortar cuando el hilo está en estado de espera
Consola.WriteLine(e);
}
}
contenido de celda = n;
Console.WriteLine("Producir: {0}",cellContents);
banderalector = verdadero;
Monitor.Pulse(esto);
//Notificar el método ReadFromCell() en espera en otro hilo
}
}
}
La clase productora CellProd y la clase consumidora CellCons se definen a continuación. Ambas tienen solo un método ThreadRun(), de modo que el objeto proxy ThreadStart proporcionado al subproceso en la función Main() sirve como entrada al subproceso.
clase pública CellProd
{
Cell cell; // Objeto de celda en el que se está operando
int cantidad = 1; // El número de veces que produce el productor, inicializado en 1
CellProd público (cuadro de celda, solicitud int)
{
//Constructor
celda = caja;
cantidad = solicitud;
}
ThreadRun público vacío ()
{
for(int looper=1; looper<=cantidad; looper++)
cell.WriteToCell(looper); //El productor escribe información en el objeto de operación.
}
}
CellCons de clase pública
{
Célula celular;
cantidad interna = 1;
CellCons públicos (cuadro de celda, solicitud int)
{
//Constructor
celda = caja;
cantidad = solicitud;
}
ThreadRun público vacío ()
{
int valDevuelto;
for(int looper=1; looper<=cantidad; looper++)
valReturned=cell.ReadFromCell();//El consumidor lee información del objeto de operación
}
}
Luego, en la función Main() de la clase MonitorSample a continuación, lo que tenemos que hacer es crear dos hilos como productores y consumidores, y usar el método CellProd.ThreadRun() y el método CellCons.ThreadRun() para realizar operaciones en los mismos. Objeto celular operar.
Código
MonitorSample de clase pública
{
vacío estático público principal (cadena [] argumentos)
{
int result = 0; // Un bit de bandera Si es 0, significa que no hay error en el programa. Si es 1, significa que hay un error.
Celda celda = nueva celda( );
// Lo siguiente usa celda para inicializar las clases CellProd y CellCons. Los tiempos de producción y consumo son 20 veces.
CellProd prod = nuevo CellProd(celda, 20);
Contras de CellCons = nuevos CellCons(celda, 20);
Productor de subprocesos = new Thread(new ThreadStart(prod.ThreadRun));
Consumidor de subprocesos = new Thread(new ThreadStart(cons.ThreadRun));
// Se han creado tanto el hilo productor como el hilo consumidor, pero la ejecución no ha comenzado.
intentar
{
productor.Inicio( );
consumidor.Inicio( );
productor.Unirse( );
consumidor.Unirse( );
Consola.ReadLine();
}
captura (ThreadStateException e)
{
//Cuando el hilo no puede realizar la operación solicitada debido a su estado
Consola.WriteLine(e);
resultado = 1;
}
captura (ThreadInterruptedException e)
{
//Abortar cuando el hilo está en estado de espera
Consola.WriteLine(e);
resultado = 1;
}
//Aunque la función Main() no devuelve un valor, la siguiente declaración puede devolver el resultado de la ejecución al proceso principal
Entorno.ExitCode = resultado;
}
}
En la rutina anterior, la sincronización se realiza esperando Monitor.Pulse(). Primero, el productor produce un valor y, al mismo tiempo, el consumidor está en estado de espera hasta que recibe un "Pulso" del productor para notificarle que la producción se ha completado. Después de eso, el consumidor ingresa al estado de consumo. y el productor comienza a esperar a que el consumidor complete. El "pulso" emitido por Monitor.Pulese() se llamará después de la operación.
El resultado de su ejecución es simple:
Producir: 1
Consumir: 1
Producir: 2
Consumir: 2
Producir: 3
Consumir: 3
...
...
Producir: 20
Consumir: 20
De hecho, este simple ejemplo nos ha ayudado a resolver grandes problemas que pueden surgir en aplicaciones multiproceso. Siempre que comprenda los métodos básicos para resolver conflictos entre subprocesos, es fácil aplicarlo a programas más complejos.
-