简体   繁体   English

.NET的线程安全缓冲区

[英]Thread-safe buffer for .NET

(Note: Though I would like ideas for the future for .Net 4.0, I'm limited to .Net 3.5 for this project.) (注意:虽然我想了解.Net 4.0的未来想法,但我只限于此项目的.Net 3.5。)

I have a thread, which is reading data asynchronously from an external device (simulated in the code example by the ever-so-creative strSomeData :-) and storing it in a StringBuilder 'buffer' (strBuilderBuffer :-) 我有一个线程,它从外部设备异步读取数据(在代码示例中通过创造性的strSomeData :-)模拟并将其存储在StringBuilder'缓冲区'中(strBuilderBuffer :-)

In the 'main code' I want to 'nibble' at this 'buffer'. 在'主要代码'中,我想在这个'缓冲区'''唠叨'。 However, I am unsure as to how to do this in a thread safe manner, from a 'operational' perspective. 但是,从“操作”的角度来看,我不确定如何以线程安全的方式执行此操作。 I understand it is safe from a 'data' perspective, because according to msdn, "Any public static members of this ( StringBuilder ) type are thread safe. Any instance members are not guaranteed to be thread safe." 我理解从“数据”的角度看它是安全的,因为根据msdn,“这个( StringBuilder )类型的任何公共静态成员都是线程安全的。任何实例成员都不能保证是线程安全的。” However, my code below illustrates that it is possibly not thread-safe from an 'operational' perspective. 但是,我的下面的代码说明从“运营”角度来看它可能不是线程安全的。

The key is that I'm worried about two lines of the code: 关键是我担心两行代码:

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

if the computer OS sleeps my thread between the 'reading' of the buffer & the 'clearing' of the buffer, I can lose data (which is bad :-( 如果计算机操作系统在缓冲区的“读取”和缓冲区的“清除”之间休眠,我可能会丢失数据(这很糟糕:-(

Is there any way to guarantee the 'atomocy?' 有没有办法保证'atomocy?' of those two lines & force the computer to not interrupt them? 这两行并迫使计算机不打断它们?

With respect to Vlad's suggestion below regarding the use of lock , I tried it but it didn't work (at all really): 关于弗拉德下面关于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);
    }

Is there a better way of implementing a thread safe buffer? 有没有更好的方法来实现线程安全缓冲区?

Here's the full code: 这是完整的代码:

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

Download the Reactive Extensions backport for 3.5 here . 此处下载3.5版本的Reactive Extensions backport。 There is also a NuGet package for it. 还有一个NuGet包。 After you have it downloaded then just reference System.Threading.dll in your project. 下载后,只需在项目中引用System.Threading.dll即可。

Now you can use all of the new concurrent collections standard in .NET 4.0 within .NET 3.5 as well. 现在,您可以在.NET 3.5中使用.NET 4.0中的所有新并发集合标准。 The best one for your situation is the BlockingCollection . 最适合您情况的是BlockingCollection It is basically a buffer that allows threads to enqueue items and dequeue them like a normal queue. 它基本上是一个缓冲区,允许线程将项排入队列并将它们像普通队列一样出列。 Except that the dequeue operation blocks until an item is available. 除了出列操作阻止直到项目可用。

There is no need to use the StringBuilder class at all now. 现在根本不需要使用StringBuilder类。 Here is how I would refactor your code. 这是我如何重构你的代码。 I tried to keep my example short so that it is easier to understand. 我试图让我的例子简短,以便更容易理解。

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

For future reference the BufferBlock<T> class from the TPL Data Flow library will do basically the same thing as BlockingCollection . 为了将来参考, TPL数据流库中的BufferBlock<T>类与BlockingCollection基本相同。 It will be available in .NET 4.5. 它将在.NET 4.5中提供。

Using StringBuffer is not thread safe, but you can switch to ConcurrentQueue<char> . 使用StringBuffer不是线程安全的,但您可以切换到ConcurrentQueue<char>

In case you need other data structure, there are other thread-safe collections in .NET 4, see http://msdn.microsoft.com/en-us/library/dd997305.aspx . 如果您需要其他数据结构,.NET 4中还有其他线程安全的集合,请参阅http://msdn.microsoft.com/en-us/library/dd997305.aspx


Edit: in .NET 3.5 there are less synchronization primitives. 编辑:在.NET 3.5中,同步原语较少。 You can make a simple solution by adding a lock around Queue<char> , though it will be less efficient than the .NET 4's ConcurrentQueue . 您可以通过在Queue<char>周围添加一个锁来创建一个简单的解决方案,尽管它的效率低于.NET 4的ConcurrentQueue Or use the same StrignBuffer , again with lock ing reading/writing operations: 或者使用相同的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...

Edit: 编辑:

You cannot guarantee that the OS won't suspend your working thread which is holding the lock. 您无法保证操作系统不会挂起持有锁的工作线程。 However the lock guarantees that the other threads will be unable to interfere and change the buffer as long as one thread is processing it. 但是,只要一个线程正在处理它,锁就会保证其他线程无法干涉并更改缓冲区。

That's why your time of holding the lock should be as short as possible: 这就是为什么你拿锁的时间应该尽可能短:

  • taken the lock, added data, released the lock, -or- 采取锁定,添加数据,释放锁定, - 或 -
  • taken the lock, copied data, emptied the buffer, released the lock, started processing the copied data. 采取锁定,复制数据,清空缓冲区,释放锁定,开始处理复制的数据。

If you are doing a lot of reads out of the buffer, perhaps this will help: 如果你从缓冲区中读取大量内容,这可能会有所帮助:

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

Multiple readers are possible, but only one writer. 可以有多个读者,但只有一个作者。

It is available in .NET 1.X and up... 它在.NET 1.X及以上版本中可用...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM