문제

여기에 나는 멀티 스레딩과 나의 운동에 대한 질문이 다시 동시 프로그래밍 수업.

.NET을 사용하여 구현 된 다중 스레드 서버가 있습니다 비동기 프로그래밍 모델 - 와 함께 GET (다운로드) 그리고 PUT (업로드) 파일 서비스. 이 부분은 완료되고 테스트됩니다.

문제의 진술은이 서버가 벌채 반출 서버 응답 시간에 최소 영향을 미치는 활동은 우선 순위가 낮을 때 지원해야합니다. - 로거 스레드 -이 효과를 위해 만들어졌습니다. 모두 벌채 반출 메시지가 전달됩니다 스레드 그것들을 이것으로 만들어냅니다 로거 스레드, 의사 소통 메커니즘을 사용합니다 그렇지 않을 수도 있습니다 잠금 이를 통해 (상호 배제를 보장하기 위해 필요한 잠금 장치 외에) 로깅 메시지 무시 될 수 있습니다.

다음은 내 현재 솔루션입니다. 이것이 명시된 문제에 대한 해결책으로 여겨지는지 확인하십시오.

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

}

기본적 으로이 코드의 주요 요점은 다음과 같습니다.

  1. 우선 순위가 낮습니다 루프 로깅 대기열 그리고 인쇄중인 인쇄물 로그 출력에. 그 후, 새로운까지 정지됩니다 통나무 도착;
  2. 로그가 도착하면 로거 스레드가 깨어나고 작동합니다.

이 솔루션입니다 스레드 안전? 나는 읽고있다 생산자 소비자 문제 및 솔루션 알고리즘이지만이 문제에서는 여러 생산자가 있지만 독자가 한 명뿐입니다.

모든 관심에 미리 감사드립니다.

도움이 되었습니까?

해결책

작동 해야하는 것 같습니다. 단일 소비자의 경우 생산자 소비자가 크게 변경해서는 안됩니다. 작은 nitpicks :

  • 잠금을 취득하는 것은 비싼 작업 일 수 있습니다 (@vitaliy lipchinsky가 말한 것처럼). 인터 로크 작업을 사용하여 순진한 '쓰기 스루'로거 및 로거에 대해 로거를 벤치마킹하는 것이 좋습니다. 또 다른 대안은 기존 큐를 비어있는 큐를 교환하는 것입니다. GetLog 그리고 즉시 중요한 섹션을 떠납니다. 이런 식으로 소비자의 긴 운영으로 생산자 중 어느 것도 차단되지 않습니다.

  • logobj 참조 유형 (클래스)을 만듭니다. 어쨌든 권투를하기 때문에 구조물로 만드는 것은 아무런 의미가 없습니다. 또는 그렇지 않습니다 _queue 유형의 필드 LogObj[] (어쨌든 더 낫다).

  • 프로그램 폐쇄를 방해하지 않도록 스레드 배경을 만들어 Stop 호출되지 않습니다.

  • 플러시 TextWriter. 또는 대기열에 맞는 기록조차도 잃을 위험이 있습니다 (10 개 항목은 약간 작은 IMHO).

  • idisposable 및/또는 finalizer를 구현합니다. Logger는 스레드와 텍스트 작성자를 소유하고 있으며 해방되어야합니다 (위의 플러시 - 위 참조).

다른 팁

이봐. 간단한 모습을 보았고 스레드 안전한 것으로 보이지만 특히 최적이라고 생각하지 않습니다. 이 라인을 따라 솔루션을 제안합니다

노트: 다른 응답 만 읽으십시오. 다음은 스스로 기준으로 상당히 최적의 낙관적 잠금 솔루션입니다. 주요 차이점은 내부 클래스에 잠겨 '중요한 섹션'을 최소화하고 우아한 스레드 종료를 제공하는 것입니다. @vitaliy lipchinsky가 제안한 것처럼 휘발성 "비 잠금"링크 목록 항목 중 일부를 시도 할 수 있습니다.

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

실제로, 당신은 여기에 잠금을 소개하고 있습니다. 로그 항목을 큐에 밀어 넣는 동안 잠금이 있습니다 (로그 메서드) : 10 개의 스레드가 동시에 10 개의 항목을 큐에 푸시하고 로거 스레드를 깨우면 11 번째 스레드가 Logger 스레드가 모든 항목을 로그까지 기다릴 것입니다 ...

실제로 확장 가능한 것을 원한다면 - 잠금이없는 큐를 구현하십시오 (예제는 아래입니다). 잠금 큐를 사용하면 동기화 메커니즘이 실제로 직선이 될 것입니다 (알림에 단일 대기 핸들을 사용할 수도 있음).

웹에서 잠금 큐 구현을 찾을 수없는 경우 다음은 다음을 수행하는 방법입니다. 구현을 위해 링크 된 목록을 사용하십시오. 링크 된 목록의 각 노드에는 a가 포함됩니다 및 다음 노드에 대한 휘발성 참조. 따라서 작동 Enqueue 및 Dequeue의 경우 interlocked.compareexchange 메소드를 사용할 수 있습니다. 아이디어가 분명하기를 바랍니다. 그렇지 않다면 - 알려 주시면 자세한 내용을 제공하겠습니다.

나는 지금 당장 코드를 시도 할 시간이 없기 때문에 여기서 생각 실험을하고 있습니다.

로깅 클래스에 호출 될 때마다 대기열과 세마포어를 할당하는 메소드 (및 스레드가 완료 될 때 큐와 세마포어를 처리하는 다른 방법)가 포함되어 있습니다. 로깅을 수행하려는 스레드는 시작할 때이 방법을 호출합니다. 그들이 로그를 원할 때, 그들은 메시지를 자신의 줄에 밀고 세마포어를 설정합니다. 로거 스레드에는 대기열을 통해 실행되는 큰 루프가 있으며 관련 세마포어를 확인합니다. 대기열과 관련된 세마포어가 0보다 크면 대기열이 튀어 나와 세마포어가 줄어 듭니다.

세마포어가 설정된 후까지 대기열에서 물건을 튀어 나오려고하지 않기 때문에 큐에 물건을 밀어 넣을 때까지 세마포어를 설정하지 않기 때문입니다. 생각한다 이것은 안전 할 것입니다. 큐 클래스의 MSDN 문서에 따르면, 큐를 열거하고 다른 스레드가 컬렉션을 수정하면 예외가 발생합니다. 그 예외를 잡으면 좋을 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top