簡體   English   中英

.NET的線程安全緩沖區

[英]Thread-safe buffer for .NET

(注意:雖然我想了解.Net 4.0的未來想法,但我只限於此項目的.Net 3.5。)

我有一個線程,它從外部設備異步讀取數據(在代碼示例中通過創造性的strSomeData :-)模擬並將其存儲在StringBuilder'緩沖區'中(strBuilderBuffer :-)

在'主要代碼'中,我想在這個'緩沖區'''嘮叨'。 但是,從“操作”的角度來看,我不確定如何以線程安全的方式執行此操作。 我理解從“數據”的角度看它是安全的,因為根據msdn,“這個( StringBuilder )類型的任何公共靜態成員都是線程安全的。任何實例成員都不能保證是線程安全的。” 但是,我的下面的代碼說明從“運營”角度來看它可能不是線程安全的。

關鍵是我擔心兩行代碼:

string strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
// Thread 'randomly' slept due to 'inconvenient' comp resource scheduling...
ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;

如果計算機操作系統在緩沖區的“讀取”和緩沖區的“清除”之間休眠,我可能會丟失數據(這很糟糕:-(

有沒有辦法保證'atomocy?' 這兩行並迫使計算機不打斷它們?

關於弗拉德下面關於lock的使用的建議,我嘗試了但它沒有用(完全沒有):

    public void BufferAnalyze()
    {
        String strCurrentBuffer;
        lock (ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer)
        {
            strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
            Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept due to comp resource scheduling");
            Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...
            ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;
        }
        Console.WriteLine("[BufferAnalyze()]\r\nstrCurrentBuffer[{0}] == {1}", strCurrentBuffer.Length.ToString(), strCurrentBuffer);
    }

有沒有更好的方法來實現線程安全緩沖區?

這是完整的代碼:

namespace ExploringThreads
{
    /// <summary>
    /// Description of BasicThreads_TestThreadSafety_v1a
    /// </summary>
    class ThreadWorker_TestThreadSafety_v1a
    {
        private Thread thread;
        public static StringBuilder strBuilderBuffer = new StringBuilder("", 7500);
        public static StringBuilder strBuilderLog = new StringBuilder("", 7500);

        public bool IsAlive
        {
            get { return thread.IsAlive; }
        }

        public ThreadWorker_TestThreadSafety_v1a(string strThreadName)
        {
            // It is possible to have a thread begin execution as soon as it is created.
            // In the case of MyThread this is done by instantiating a Thread object inside MyThread's constructor.
            thread = new Thread(new ThreadStart(this.threadRunMethod));
            thread.Name = strThreadName;
            thread.Start();
        }

        public ThreadWorker_TestThreadSafety_v1a() : this("")
        {
            //   NOTE:  constructor overloading ^|^
        }

        //Entry point of thread.
        public void threadRunMethod()
        {
            Console.WriteLine("[ThreadWorker_TestThreadSafety_v1a threadRunMethod()]");
            Console.WriteLine(thread.Name + " starting.");
            int intSomeCounter = 0;
            string strSomeData = "";
            do
            {
                Console.WriteLine("[ThreadWorker_TestThreadSafety_v1a threadRunMethod()] running.");
                intSomeCounter++;
                strSomeData = "abcdef" + intSomeCounter.ToString() + "|||";
                strBuilderBuffer.Append(strSomeData);
                strBuilderLog.Append(strSomeData);
                Thread.Sleep(200);
            } while(intSomeCounter < 15);

            Console.WriteLine(thread.Name + " terminating.");
        }
    }
    /// <summary>
    /// Description of BasicThreads_TestThreadSafety_v1a.
    /// </summary>
    public class BasicThreads_TestThreadSafety_v1a
    {
        public BasicThreads_TestThreadSafety_v1a()
        {
        }

        public void BufferAnalyze()
        {
            string strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
            Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept due to comp resource scheduling");
            Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...
            ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;
            Console.WriteLine("[BufferAnalyze()]\r\nstrCurrentBuffer[{0}] == {1}", strCurrentBuffer.Length.ToString(), strCurrentBuffer);
        }

        public void TestBasicThreads_TestThreadSafety_v1a()
        {
            Console.Write("Starting TestBasicThreads_TestThreadSafety_v1a  >>>  Press any key to continue . . . ");
            Console.Read();

            // First, construct a MyThread object.
            ThreadWorker_TestThreadSafety_v1a threadWorker_TestThreadSafety_v1a = new ThreadWorker_TestThreadSafety_v1a("threadWorker_TestThreadSafety_v1a Child");

            do
            {
                Console.WriteLine("[TestBasicThreads_TestThreadSafety_v1a()]");
                Thread.Sleep(750);
                BufferAnalyze();
                //} while (ThreadWorker_TestThreadSafety_v1a.thread.IsAlive);
            } while (threadWorker_TestThreadSafety_v1a.IsAlive);
            BufferAnalyze();
            Thread.Sleep(1250);
            Console.WriteLine("[TestBasicThreads_TestThreadSafety_v1a()]");
            Console.WriteLine("ThreadWorker_TestThreadSafety_v1a.strBuilderLog[{0}] == {1}", ThreadWorker_TestThreadSafety_v1a.strBuilderLog.Length.ToString(), ThreadWorker_TestThreadSafety_v1a.strBuilderLog);

            Console.Write("Completed TestBasicThreads_TestThreadSafety_v1a  >>>  Press any key to continue . . . ");
            Console.Read();
        }
    }
}

此處下載3.5版本的Reactive Extensions backport。 還有一個NuGet包。 下載后,只需在項目中引用System.Threading.dll即可。

現在,您可以在.NET 3.5中使用.NET 4.0中的所有新並發集合標准。 最適合您情況的是BlockingCollection 它基本上是一個緩沖區,允許線程將項排入隊列並將它們像普通隊列一樣出列。 除了出列操作阻止直到項目可用。

現在根本不需要使用StringBuilder類。 這是我如何重構你的代碼。 我試圖讓我的例子簡短,以便更容易理解。

public class Example
{
  private BlockingCollection<string> buffer = new BlockingCollection<string>();

  public Example()
  {
    new Thread(ReadFromExternalDevice).Start();
    new Thread(BufferAnalyze).Start();
  }

  private void ReadFromExteneralDevice()
  {
    while (true)
    {
      string data = GetFromExternalDevice();
      buffer.Add(data);
      Thread.Sleep(200);
    }
  }

  private void BufferAnalyze()
  {
    while (true)
    {
      string data = buffer.Take(); // This blocks if nothing is in the queue.
      Console.WriteLine(data);
    }
  } 
}

為了將來參考, TPL數據流庫中的BufferBlock<T>類與BlockingCollection基本相同。 它將在.NET 4.5中提供。

使用StringBuffer不是線程安全的,但您可以切換到ConcurrentQueue<char>

如果您需要其他數據結構,.NET 4中還有其他線程安全的集合,請參閱http://msdn.microsoft.com/en-us/library/dd997305.aspx


編輯:在.NET 3.5中,同步原語較少。 您可以通過在Queue<char>周圍添加一個鎖來創建一個簡單的解決方案,盡管它的效率低於.NET 4的ConcurrentQueue 或者使用相同的StrignBuffer ,再次使用lock讀/寫操作:

public static StringBuilder strBuilderBuffer = new StringBuilder("", 7500);
private object BufferLock = new object();

...

lock (BufferLock)
    strBuilderBuffer.Append(strSomeData);

...

string strCurrentBuffer;
lock (BufferLock)
{
    strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
    ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Clear();
}
Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept ...");
Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...

編輯:

您無法保證操作系統不會掛起持有鎖的工作線程。 但是,只要一個線程正在處理它,鎖就會保證其他線程無法干涉並更改緩沖區。

這就是為什么你拿鎖的時間應該盡可能短:

  • 采取鎖定,添加數據,釋放鎖定, - 或 -
  • 采取鎖定,復制數據,清空緩沖區,釋放鎖定,開始處理復制的數據。

如果你從緩沖區中讀取大量內容,這可能會有所幫助:

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

可以有多個讀者,但只有一個作者。

它在.NET 1.X及以上版本中可用...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM