interacção aplicação multi-threaded com rosca registador
-
06-07-2019 - |
Pergunta
Aqui estou eu novamente com perguntas sobre multi-threading e um exercício da minha Programação Concorrente classe.
Eu tenho um servidor multi-threaded - implementado utilizando .NET Asynchronous Programming Modelo - com GET
( download ) e PUT
( Enviar ) serviços de arquivo. Esta parte é feito e testado.
Acontece que a declaração do problema diz que este servidor deve ter login atividade com o mínimo impacto sobre o tempo de resposta do servidor, e deve ser apoiada por uma baixa prioridade fio - fio logger - criado para este efeito. Todos Registro mensagens serão transmitidas pela tópicos que eles produzem a esta fio logger , usando um mecanismo de comunicação que não pode bloquear o fio que o invoca (além do bloqueio necessário para garantir a exclusão mútua) e assumindo que alguns registrar mensagens pode ser ignorado.
Aqui está a minha solução atual, por favor, ajuda validar se este se destaca como uma solução para o problema indicado:
using System;
using System.IO;
using System.Threading;
// Multi-threaded Logger
public class Logger {
// textwriter to use as logging output
protected readonly TextWriter _output;
// logger thread
protected Thread _loggerThread;
// logger thread wait timeout
protected int _timeOut = 500; //500ms
// amount of log requests attended
protected volatile int reqNr = 0;
// logging queue
protected readonly object[] _queue;
protected struct LogObj {
public DateTime _start;
public string _msg;
public LogObj(string msg) {
_start = DateTime.Now;
_msg = msg;
}
public LogObj(DateTime start, string msg) {
_start = start;
_msg = msg;
}
public override string ToString() {
return String.Format("{0}: {1}", _start, _msg);
}
}
public Logger(int dimension,TextWriter output) {
/// initialize queue with parameterized dimension
this._queue = new object[dimension];
// initialize logging output
this._output = output;
// initialize logger thread
Start();
}
public Logger() {
// initialize queue with 10 positions
this._queue = new object[10];
// initialize logging output to use console output
this._output = Console.Out;
// initialize logger thread
Start();
}
public void Log(string msg) {
lock (this) {
for (int i = 0; i < _queue.Length; i++) {
// seek for the first available position on queue
if (_queue[i] == null) {
// insert pending log into queue position
_queue[i] = new LogObj(DateTime.Now, msg);
// notify logger thread for a pending log on the queue
Monitor.Pulse(this);
break;
}
// if there aren't any available positions on logging queue, this
// log is not considered and the thread returns
}
}
}
public void GetLog() {
lock (this) {
while(true) {
for (int i = 0; i < _queue.Length; i++) {
// seek all occupied positions on queue (those who have logs)
if (_queue[i] != null) {
// log
LogObj obj = (LogObj)_queue[i];
// makes this position available
_queue[i] = null;
// print log into output stream
_output.WriteLine(String.Format("[Thread #{0} | {1}ms] {2}",
Thread.CurrentThread.ManagedThreadId,
DateTime.Now.Subtract(obj._start).TotalMilliseconds,
obj.ToString()));
}
}
// after printing all pending log's (or if there aren't any pending log's),
// the thread waits until another log arrives
//Monitor.Wait(this, _timeOut);
Monitor.Wait(this);
}
}
}
// Starts logger thread activity
public void Start() {
// Create the thread object, passing in the Logger.Start method
// via a ThreadStart delegate. This does not start the thread.
_loggerThread = new Thread(this.GetLog);
_loggerThread.Priority = ThreadPriority.Lowest;
_loggerThread.Start();
}
// Stops logger thread activity
public void Stop() {
_loggerThread.Abort();
_loggerThread = null;
}
// Increments number of attended log requests
public void IncReq() { reqNr++; }
}
Basicamente, aqui estão os pontos principais deste código:
- Inicie uma baixa prioridade fio que circula o logging fila e impressões pendentes registros para a saída. Depois disso, o fio é suspenso até nova log chega;
- Quando um log chega, o segmento madeireiro é despertar e faz isso de trabalho.
É esta solução thread-safe ? Eu tenho lido Produtores-Consumidores problema ea solução algoritmo, mas neste problema, embora eu tenho vários produtores, eu só tenho um leitor.
Agradecemos antecipadamente por toda a sua atenção.
Solução
Parece que deveria estar trabalhando. Produtores-consumidores não devem mudar muito em caso de único consumidor. Pequenos nitpicks:
-
aquisição de bloqueio pode ser uma operação cara (como diz @Vitaliy Lipchinsky). Eu recomendo para aferir o seu logger contra ingênuo 'write-through' operações interligadas logger e logger usando. Outra alternativa seria troca de fila existente com um vazio no
GetLog
e deixando seção crítica imediatamente. Desta forma, nenhum dos produtores não será bloqueado por operações longos nos consumidores. -
fazer tipo de referência LogObj (classe). Não há nenhum ponto em fazê-lo struct desde que você está boxe de qualquer maneira. ou campo
_queue
fazer mais para ser do tipoLogObj[]
(que é melhor de qualquer maneira). -
fazer o seu fundo fio para que ele não vai impedir de fechar seu programa se
Stop
não será chamado. -
Lave seu
TextWriter
. Ou então você está arriscando a perder até mesmo os registros que conseguiram fila de ajuste (10 ítens é um pequeno IMHO bit) -
Implementar IDisposable e / ou finalizador. Seu logger possui fio e texto escritor e aqueles que deveriam ser libertados (e corada - veja acima).
Outras dicas
Ei lá. Tinha um olhar rápido, e enquanto parece ser thread-safe, eu não acredito que isso é particularmente ideal. Gostaria de sugerir uma solução ao longo destas linhas
NOTA: apenas ler as outras respostas. O que se segue é uma solução de bloqueio bastante ideal, otimista baseado em seu próprio país. As principais diferenças é o encaixe sobre uma classe interna, minimizando 'secções críticas', e fornecendo terminação rosca elegante. Se você quiser evitar o bloqueio por completo, então você pode experimentar alguns dos que volátil "sem bloqueio" coisas lista encadeada como @Vitaliy Lipchinsky sugere.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
...
public class Logger
{
// BEST PRACTICE: private synchronization object.
// lock on _syncRoot - you should have one for each critical
// section - to avoid locking on public 'this' instance
private readonly object _syncRoot = new object ();
// synchronization device for stopping our log thread.
// initialized to unsignaled state - when set to signaled
// we stop!
private readonly AutoResetEvent _isStopping =
new AutoResetEvent (false);
// use a Queue<>, cleaner and less error prone than
// manipulating an array. btw, check your indexing
// on your array queue, while starvation will not
// occur in your full pass, ordering is not preserved
private readonly Queue<LogObj> _queue = new Queue<LogObj>();
...
public void Log (string message)
{
// you want to lock ONLY when absolutely necessary
// which in this case is accessing the ONE resource
// of _queue.
lock (_syncRoot)
{
_queue.Enqueue (new LogObj (DateTime.Now, message));
}
}
public void GetLog ()
{
// while not stopping
//
// NOTE: _loggerThread is polling. to increase poll
// interval, increase wait period. for a more event
// driven approach, consider using another
// AutoResetEvent at end of loop, and signal it
// from Log() method above
for (; !_isStopping.WaitOne(1); )
{
List<LogObj> logs = null;
// again lock ONLY when you need to. because our log
// operations may be time-intensive, we do not want
// to block pessimistically. what we really want is
// to dequeue all available messages and release the
// shared resource.
lock (_syncRoot)
{
// copy messages for local scope processing!
//
// NOTE: .Net3.5 extension method. if not available
// logs = new List<LogObj> (_queue);
logs = _queue.ToList ();
// clear the queue for new messages
_queue.Clear ();
// release!
}
foreach (LogObj log in logs)
{
// do your thang
...
}
}
}
}
...
public void Stop ()
{
// graceful thread termination. give threads a chance!
_isStopping.Set ();
_loggerThread.Join (100);
if (_loggerThread.IsAlive)
{
_loggerThread.Abort ();
}
_loggerThread = null;
}
Na verdade, você está introduzindo bloqueio aqui. Você bloqueio enquanto empurra uma entrada de registo para a fila (método Log): se 10 tópicos empurrada simultaneamente 10 itens em fila e acordou o fio Logger, em seguida, 11 segmento irá aguardar até que o segmento madeireiro registrar todos os itens ...
Se você quiser algo realmente escalável - implementar fila de livre-lock (exemplo é abaixo). Com mecanismo de sincronização fila de livre-lock será realmente imediatamente (você ainda pode usar identificador de espera único para notificações).
Se você não conseguir encontrar implementação da fila livre-lock na web, aqui está uma ideia de como fazer isso: Use lista ligada para uma implementação. Cada nó na lista vinculada contém um valor e uma referência volátil para o próximo nó. portanto, para as operações de enfileirar e retirar da fila que você pode usar o método Interlocked.CompareExchange. Espero que, a idéia é clara. Se não -. Deixe-me saber e eu vou fornecer mais detalhes
Eu estou apenas fazendo um experimento de pensamento aqui, já que eu não tenho tempo para realmente tentar código agora, mas acho que você pode fazer isso sem bloqueios em todos, se você for criativo.
Tenha sua classe de log contêm um método que aloca uma fila e um semáforo cada vez que ele é chamado (e outro que desaloca da fila e do semáforo quando o fio é feito). Os tópicos que querem fazer o registo vai chamar esse método quando eles começam. Quando eles querem fazer logon, eles empurram a mensagem para sua própria fila e definir o semáforo. O fio tem um registador de grande loop que atravessa as filas e as verificações semáforos associados. Se o semáforo associado à fila é maior do que zero, então a fila fica bateu fora e o semáforo diminuído.
Porque você não está tentando pop coisas fora da fila até que o semáforo está definido, e você não está definindo o semáforo até depois que você empurrou as coisas para a fila, eu pensar este será segura. De acordo com a documentação do MSDN para a classe fila, se você está enumerando a fila e outro segmento modifica a coleção, uma exceção é lançada. Capturar essa exceção e você deve ser bom.