A motivação
partiu do contato com o modelo de programação multithread (processo), e o que aprendi foram as primitivas de sincronização relacionadas ao semáforo (Semaphore). Não sei por que não há material correspondente no .Net Framework. O terrível é que eu a usei para implementar muitos códigos C++ testados pelo tempo. Para evitar que os mártires revolucionários tomassem o remédio e sangrassem em vão, eu mesmo tive que dar à luz um.
O que é um semáforo
Se você já entende o conceito de semáforo, pule esta seção.
Semáforo é um recurso usado em um ambiente multithread. Ele é responsável por coordenar vários threads para garantir que eles possam usar os recursos públicos de maneira correta e razoável.
Vamos dar uma olhada em como funciona um estacionamento. Para simplificar, suponha que o estacionamento tenha apenas três vagas e que todas as três estejam vazias no início. Isto é, se cinco carros vierem ao mesmo tempo, e o porteiro permitir que três deles entrem sem impedimentos, e depois largar o bloqueio de carros, os carros restantes deverão esperar na entrada, e os carros subsequentes também terão que esperar no Entrada. Nesse momento, um carro saiu do estacionamento. Depois que o porteiro soube disso, ele abriu a barreira do carro e colocou um carro. Se saíssem mais dois carros, ele poderia colocar mais dois carros e assim por diante.
Nesse sistema de estacionamento, as vagas são recursos públicos, cada carro é como um fio e o porteiro funciona como um semáforo.
Além disso, as características do semáforo são as seguintes: o semáforo é um número inteiro não negativo (o número de estacionamentos), e todos os threads (veículos) que passam por ele reduzirão o número inteiro em um (passá-lo é, claro, para usar recursos ). Quando o valor inteiro for zero, todos os threads que tentarem passá-lo estarão em estado de espera. Definimos duas operações no semáforo: Wait e Release. Quando um thread chama a operação Wait, ele passa e depois diminui o semáforo em um ou espera até que o semáforo seja maior que um ou expire. Release na verdade executa uma operação add no semáforo, que corresponde à saída do veículo do estacionamento. A razão pela qual essa operação é chamada de "release" é que a operação add realmente libera os recursos guardados pelo semáforo.
concluir
Como todos sabemos, os recursos de sincronização de threads fornecidos na biblioteca de classes .Net Framework incluem:
Monitor, AutoResetEvent, ManualResetEvent, Mutex, ReadWriteLock e InterLock. Entre eles, AutoResetEvent, ManualResetEvent e Mutex são derivados de WaitHandler e, na verdade, encapsulam os objetos do kernel fornecidos pelo sistema operacional. Outros devem ser nativos da máquina virtual .Net. Obviamente, os recursos dos objetos do kernel do sistema operacional são menos eficientes de usar. Entretanto, eficiência não é uma questão que devemos considerar aqui. Usaremos dois Monitores e um objeto ManualResetEvent para simular um semáforo.
O código é o seguinte:
classe pública Semáforo
{
private ManualResetEvent waitEvent = novo ManualResetEvent(false);
objeto privado syncObjWait = novo objeto();
private int maxCount = 1; //Número máximo de recursos
private int currentCount = 0; //O número atual de recursos
public Semaphore()
{
}
semáforo público (int maxCount)
{
this.maxCount = maxCount;
}
public bool Espera()
{
lock(syncObjWait) //Apenas um thread pode inserir o seguinte código
{
bool waitResult = this.waitEvent.WaitOne();/O número de recursos aguardando aqui é maior que zero
if(esperaResultado)
{
bloquear (este)
{
if(contagematual > 0)
{
ContagemAtual--;
if(contagematual == 0)
{
this.waitEvent.Reset();
}
}
outro
{
System.Diagnostics.Debug.Assert( false, "Semáforo não permite contagem atual <0");
}
}
}
return waitResult;
}
}
/**//// <resumo>
/// Operação de espera que permite retorno de timeout
/// </sumário>
/// <param name="millisecondsTimeout"></param>
/// <retorna></retorna>
public bool Wait(int milissegundosTimeout)
{
lock(syncObjWait) // O monitor garante que o código da classe de escopo esteja dentro da seção crítica
{
bool waitResult = this.waitEvent.WaitOne(milissegundosTimeout,false);
if(esperaResultado)
{
bloquear (isto)
{
if(contagematual > 0)
{
ContagemAtual--;
if(contagematual == 0)
{
this.waitEvent.Reset();
}
}
outro
{
System.Diagnostics.Debug.Assert( false, "Semáforo não permite contagem atual <0");
}
}
}
return waitResult;
}
}
lançamento bool público ()
{
lock( this ) // O monitor garante que o código da classe do escopo esteja dentro da seção crítica
{
ContagemAtual++;
if( currentCount > this.maxCount )
{
contagem atual = this.maxCount;
retornar falso;
}
this.waitEvent.Set(); //Permitir que o thread que está chamando Wait entre
}
retornar verdadeiro;
}
}