Frage

Hier bin ich wieder mit Fragen über Multi-Threading und eine Übung meines Concurrent Programming Klasse.

Ich habe einen Multi-Threaded-Server - realisiert mit .NET Asynchrone Programmierung Modell - mit GET ( Download ) und PUT ( Hochladen ) Dateidienste. Dieser Teil wird durchgeführt und getestet.

Es kommt vor, dass die Aussage des Problems sagt dieser Server haben muss Anmeldung Aktivität mit der minimalen Auswirkungen auf die Antwortzeit des Servers, und es sollte durch eine niedrige Priorität Thread unterstützt - Logger Thread - für diesen Effekt erstellt. Alle Protokollierung Nachrichten werden von den Themen , die sie zu dieser Logger Thread produzieren geführt werden, einen Kommunikationsmechanismus, dass kann nicht sperren sie die Thread die sie aufruft (neben der notwendigen Sperr gegenseitigen Ausschluss zu gewährleisten) und unter der Annahme, dass einige Protokollmeldungen ignoriert werden kann.

Hier ist meine aktuelle Lösung, bitte Validierung helfen, wenn dies als eine Lösung für das genannte Problem steht:

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++; }

}

Im Grunde genommen, sind hier die wichtigsten Punkte dieses Codes:

  1. Starten Sie eine niedrige Priorität Thread , die die Logging-Warteschlange Schleifen und Drucke anhängige Protokolle zum Ausgang. Danach wird die Thread angehalten, bis neue log ankommt;
  2. Wenn ein Protokoll eintrifft, wird der Logger Thread wecken und tut es ist Arbeit.

Ist diese Lösung thread-safe ? Ich habe gelesen Produzenten-Konsumenten Problem und Lösungsalgorithmus, aber in diesem Problem, obwohl ich mehrere Produzenten habe, ich habe nur einen Leser.

Vielen Dank im Voraus für Ihre Aufmerksamkeit.

War es hilfreich?

Lösung

Es scheint, sollte es funktionieren. Die Erzeuger-Verbraucher sollten nicht stark bei einzelnen Verbrauchern ändern. Kleine Nitpicks:

  • Erwerb Sperre kann eine teure Operation sein (wie @Vitaliy Lipchinsky sagt). Ich würde Ihre Logger Benchmark gegen naiven ‚Write-Through‘ Logger und Logger mit verzahnten Operationen empfehlen. Eine andere Alternative würde den Austausch von bestehenden Warteschlange mit leeren in GetLog und verläßt kritischen Abschnitt sofort. Auf diese Weise keiner der Hersteller nicht durch lange Operationen in Verbraucher blockiert werden.

  • LogObj Referenztyp (Klasse) machen. Es gibt keinen Grund, es über struct da Sie es trotzdem sind Boxen. oder sonst _queue Feld seiner vom Typ LogObj[] (das ist sowieso besser).

  • machen
  • Ihren Thread Hintergrund machen, so dass es Ihr Programm nicht verhindern wird, zu schließen, wenn Stop nicht in Anspruch genommen werden.

  • Flush Ihre TextWriter. Sonst riskieren Sie auch die Datensätze zu verlieren, die verwalteten Warteschlange passen (10 Stück ist ein bisschen klein IMHO)

  • Implementieren Sie IDisposable und / oder Finalizerthread. Ihr Logger besitzt Thread und Textdichter und diese sollen (und gespült - siehe oben) befreit werden.

Andere Tipps

Hey. Hatte einen kurzen Blick, und während es Thread-sicher zu sein scheint, ich glaube es nicht besonders optimal ist. Ich würde eine Lösung in dieser Richtung vorschlagen

Hinweis: nur die anderen Antworten lesen. Was folgt, ist eine ziemlich optimal, optimistische Sperren Lösung basierend auf Ihren eigenen. Wesentliche Unterschiede auf eine interne Klasse sperrt, "kritische Abschnitte, und die Bereitstellung graceful Faden Beendigung minimieren. Wenn Sie insgesamt Sperren vermeiden wollen, dann können Sie einige dieser flüchtigen „Nicht-locking“ verkettete Liste Sachen versuchen, so @Vitaliy Lipchinsky vermuten lässt.

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;
}

Eigentlich Sperren einführen Sie hier. Sie haben beim Verriegeln einen Protokolleintrag an die Warteschlange (Log-Methode) drücken: Bei 10 Threads gleichzeitig 10 Elemente in die Warteschlange eingeschoben und die Logger Gewinde aufgewacht, dann 11. Thread, bis der Logger Thread warten log werden alle Einzelteile ...

Wenn Sie etwas wirklich skalierbare wollen - implementieren Lock-Free-Warteschlange (Beispiel unten). Mit Lock-frei wird Warteschlange Synchronisationsmechanismus wirklich auf Anhieb werden (Sie sogar einzelne Waithandle für Benachrichtigungen verwenden können).

Wenn Sie nicht Lock-freie Warteschlange Implementierung im Web finden verwalten, hier ist eine Idee, wie dies zu tun: Verwenden Sie verknüpfte Liste für eine Implementierung. Jeder Knoten in verketteten Liste enthält einen Wert und einen flüchtigen Bezug auf den nächsten Knoten. daher für den Betrieb einreihen und dequeue können Sie Interlocked.CompareExchange Methode verwenden. Ich hoffe, die Idee ist klar. Wenn nicht -. Lassen Sie uns wissen und ich werde mehr Details liefern

Ich mache nur ein Gedankenexperiment hier, da ich jetzt keine Zeit haben, um tatsächlich Code versuchen, aber ich denke, man kann dies überhaupt ohne Schleusen tun, wenn Sie kreativ sind.

Haben Sie Ihre Logging-Klasse eine Methode enthalten, die eine Warteschlange und eine Semaphore ordnet jedes Mal aufgerufen wird (und ein anderer, der die Warteschlange und Semaphore freigibt, wenn der Thread ausgeführt wird). Die Fäden, die tun Protokollierung wollen diese Methode aufrufen, wenn sie beginnen. Wenn sie protokollieren wollen, drücken sie die Nachricht auf ihre eigene Warteschlange und die Semaphore gesetzt. Der Logger Gewinde hat eine große Schleife, die die Warteschlangen durchläuft und prüft die zugehörigen Semaphoren. Wenn die Semaphore mit der Warteschlange verbunden ist, ist größer als Null ist, dann wird die Warteschlange tauchte ab und die Semaphore verringert.

Weil Sie versuchen, die Dinge nicht aus der Warteschlange Pop bis nach der Semaphore gesetzt ist, und Sie Einstellung nicht die Semaphore erst, nachdem Sie die Dinge in der Warteschlange geschoben habe, I denkt dies wird sicher. Gemäß der MSDN-Dokumentation für die Warteschlange Klasse, wenn Sie die Warteschlange Aufzählen und ein anderer Thread modifiziert die Sammlung, wird eine Ausnahme ausgelöst. Fangen Sie diese Ausnahme und Sie sollten gut sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top