سؤال

أنا هنا مرة أخرى مع أسئلة حول تعدد الخيوط وتمرين خاص بي البرمجة المتزامنة فصل.

لدي خادم متعدد الخيوط - تم تنفيذه باستخدام .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. عند وصول السجل، يتم تنشيط مؤشر ترابط المسجل ويعمل.

هل هذا هو الحل آمن للخيط؟لقد كنت أقرأ المنتجون والمستهلكون خوارزمية المشكلة والحل، ولكن في هذه المشكلة على الرغم من أن لدي منتجين متعددين، إلا أن لدي قارئًا واحدًا فقط.

شكرا مقدما على كل اهتمامكم.

هل كانت مفيدة؟

المحلول

يبدو أنه ينبغي أن تعمل.لا ينبغي أن يتغير المنتجون والمستهلكون بشكل كبير في حالة المستهلك الفردي.نيتبيك قليلا:

  • قد يكون الحصول على القفل عملية مكلفة (كما يقول @Vitaliy Lipchinsky).أوصي بقياس أداء المسجل الخاص بك مقابل مسجل ومسجل "الكتابة" الساذجين باستخدام العمليات المتشابكة.البديل الآخر هو استبدال قائمة الانتظار الحالية بقائمة انتظار فارغة GetLog وترك القسم الحرج على الفور.بهذه الطريقة لن يتم منع أي من المنتجين من عمليات طويلة لدى المستهلكين.

  • جعل نوع مرجع LogObj (الفئة).ليس هناك فائدة من جعلها مبنية لأنك ملاكمة على أي حال.أو جعل آخر _queue الحقل ليكون من النوع LogObj[] (وهذا أفضل على أي حال).

  • قم بعمل خلفية لموضوعك بحيث لا يمنع إغلاق البرنامج إذا Stop لن يتم استدعاؤه.

  • اغسل TextWriter.وإلا فإنك تخاطر بفقدان حتى تلك السجلات التي تمكنت من احتواء قائمة الانتظار (10 عناصر هي IMHO صغيرة بعض الشيء)

  • تنفيذ IDisposable و/أو Finalizer.يمتلك المسجل الخاص بك كاتب خيط ونص ويجب تحريرهما (ومسحهما - انظر أعلاه).

نصائح أخرى

مرحبًا يا من هناك.ألقيت نظرة سريعة، وعلى الرغم من أنه يبدو آمنًا للخيط، إلا أنني لا أعتقد أنه الأمثل بشكل خاص.أود أن أقترح حلاً على هذا المنوال

ملحوظة: فقط اقرأ الردود الأخرى.ما يلي هو حل قفل مثالي ومتفائل إلى حد ما يعتمد على حلك الخاص.تتمثل الاختلافات الرئيسية في تأمين فئة داخلية، وتقليل "الأقسام المهمة"، وتوفير إنهاء أنيق لمؤشر الترابط.إذا كنت تريد تجنب القفل تمامًا، فيمكنك تجربة بعض عناصر القائمة المرتبطة "غير المقفلة" المتقلبة كما يقترح @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 عناصر في وقت واحد إلى قائمة الانتظار وأيقظت مؤشر ترابط المسجل، فسينتظر الخيط الحادي عشر حتى يسجل مؤشر ترابط المسجل جميع العناصر ...

إذا كنت تريد شيئًا قابلاً للتطوير حقًا - فقم بتنفيذ قائمة انتظار خالية من القفل (المثال أدناه).مع آلية مزامنة قائمة الانتظار الخالية من القفل، ستكون هذه الآلية مباشرة (يمكنك حتى استخدام مقبض انتظار واحد للإشعارات).

إذا لم تتمكن من العثور على تنفيذ قائمة انتظار خالية من القفل في الويب، فإليك فكرة عن كيفية القيام بذلك:استخدم القائمة المرتبطة للتنفيذ.تحتوي كل عقدة في القائمة المرتبطة على قيمة وإشارة متقلبة إلى العقدة التالية.ولذلك، بالنسبة للعمليات التي يتم وضعها في قائمة الانتظار وفصلها، يمكنك استخدام طريقة Interlocked.CompareExchange.أتمنى أن تكون الفكرة واضحة.إذا لم يكن الأمر كذلك، فأخبرني وسأقدم لك المزيد من التفاصيل.

أنا أقوم فقط بتجربة فكرية هنا، حيث أنه ليس لدي الوقت لتجربة البرمجة فعليًا في الوقت الحالي، ولكن أعتقد أنه يمكنك القيام بذلك بدون أقفال على الإطلاق إذا كنت مبدعًا.

اطلب من فئة التسجيل الخاصة بك أن تحتوي على طريقة تقوم بتخصيص قائمة انتظار وإشارة في كل مرة يتم استدعاؤها (وطريقة أخرى تقوم بإلغاء تخصيص قائمة الانتظار والإشارة عند انتهاء مؤشر الترابط).سوف تستدعي سلاسل الرسائل التي تريد إجراء التسجيل هذه الطريقة عند البدء.عندما يريدون التسجيل، يقومون بدفع الرسالة إلى قائمة الانتظار الخاصة بهم وتعيين الإشارة.يحتوي مؤشر ترابط المسجل على حلقة كبيرة تمر عبر قوائم الانتظار وتتحقق من الإشارات المرتبطة بها.إذا كانت الإشارة المرتبطة بقائمة الانتظار أكبر من الصفر، فسيتم فصل قائمة الانتظار وتناقص الإشارة.

نظرًا لأنك لا تحاول إخراج الأشياء من قائمة الانتظار إلا بعد تعيين الإشارة، ولا تقوم بتعيين الإشارة إلا بعد دفع الأشياء إلى قائمة الانتظار، فأنا يفكر سيكون هذا آمنا.وفقًا لوثائق MSDN الخاصة بفئة قائمة الانتظار، إذا كنت تقوم بتعداد قائمة الانتظار وقام مؤشر ترابط آخر بتعديل المجموعة، فسيتم طرح استثناء.قبض على هذا الاستثناء ويجب أن تكون جيدًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top