C#One Writer很多读者都读过一次

时间:2013-03-25 21:07:35

标签: c# .net multithreading synchronization

我有4个帖子。一个是从网络中读取一些信息,将其写入变量,并在每个部分后发出信号。其中3个正在阅读这个变量,应该只读一次。当前的解决方案是编写器在写入并等待读者事件之后设置事件。读者等待事件,然后阅读并设置他们的事件(意思是他们阅读)。问题是读者可以阅读不止一次,我有重复。如何才能让读者完全阅读一次?

5 个答案:

答案 0 :(得分:2)

实现此目的的一种方法是以下

数据在线程之间作为单链表共享。列表中的每个节点都可以是标记或具有数据。该列表以单个节点开始,该节点键入到标记。当读取数据时,形成新的列表,其具有一系列数据节点,后跟标记。此列表将附加到添加到列表中的最新标记。

每个读者线程都以对原始标记节点和AutoResetEvent的引用开始。每当一个新的数据进入编写器时,它将为每个读者线程发出AutoResetEvent信号。然后读者线程将简单地走,直到找到没有Next节点的标记。

此方案确保所有读者只能看到一次数据。最大的复杂因素是构建列表,以便可以以无锁方式写入和读取。尽管<{1}}

,但这非常简单

链接列表类型

Interlocked.CompareExchange

示例编写者类型

class Node<T> { 
  public bool IsMarker;
  public T Data;
  public Node<T> Next;
}

样本读者类型

class Writer<T> {
  private List<AutoResetEvent> m_list; 
  private Node<T> m_lastMarker;

  public Writer(List<AutoResetEvent> list, Node<T> marker) { 
    m_lastMarker = marker;
    m_list = list;
  }

  // Assuming this can't overlap.  If this can overload then you will
  // need synchronization in this method around the writing of 
  // m_lastMarker      
  void OnDataRead(T[] items) {
    if (items.Length == 0) {
      return;
    }

    // Build up a linked list of the new data followed by a 
    // Marker to signify the end of the data.  
    var head = new Node<T>() { Data = items[0] };
    var current = head;
    for (int i = 1; i < items.Length; i++) {
      current.Next = new Node<T>{ Data = items[i] };
      current = current.Next;
    }
    var marker = new Node<T> { IsMarker = true };
    current.Next = marker;

    // Append the list to the end of the last marker node the writer
    // created 
    m_lastMarker.Next = head;
    m_lastMarker = marker;

    // Tell each of the readers that there is new data 
    foreach (var e in m_list) { 
      e.Set();
    }
  }
}

答案 1 :(得分:1)

我同意评论说你应该编写消费者线程来接受多次获得相同值的可能性。也许最简单的方法是为每个更新添加一个顺序标识符。这样,线程可以将顺序id与它读取的最后一个id进行比较,并知道它是否正在重复。

它也会知道它是否错过了一个值。

但是如果你真的需要它们处于锁定状态并且只获得一次值,我建议你使用两个ManualResetEvent个对象和一个CountdownEvent。以下是如何使用它们。

ManualResetEvent DataReadyEvent = new ManualResetEvent();
ManualResetEvent WaitForResultEvent = new ManualResetEvent();
CountdownEvent Acknowledgement = new CountdownEvent(NumWaitingThreads);

读者线程等待DataReadyEvent

当另一个线程从网络读取值时,它会这样做:

Acknowledgement.Reset(NumWaitingThreads);
DataReadyEvent.Set();  // signal waiting threads to process
Acknowledgement.WaitOne();  // wait for all threads to signal they got it.
DataReadyEvent.Reset(); // block threads' reading
WaitForResultEvent.Set(); // tell threads they can continue

等待线程执行此操作:

DataReadyEvent.WaitOne(); // wait for value to be available
// read the value
Acknowledgement.Set();  // acknowledge receipt
WaitForResultEvent.WaitOne(); // wait for signal to proceed

这与每个等待线程有两个事件的效果相同,但更简单。

但是,它有一个缺点,即如果一个线程崩溃,这将挂起倒计时事件。但是,如果生产者线程等待所有线程消息,那么你的方法也会。

答案 2 :(得分:1)

这非常适合the Barrier class

你可以使用两个Barriers来触发这两种状态。

以下是一个例子:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            int readerCount = 4;

            Barrier barrier1 = new Barrier(readerCount + 1);
            Barrier barrier2 = new Barrier(readerCount + 1);

            for (int i = 0; i < readerCount; ++i)
            {
                Task.Factory.StartNew(() => reader(barrier1, barrier2));
            }

            while (true)
            {
                barrier1.SignalAndWait(); // Wait for all threads to reach the "new data available" point.

                if ((value % 10000) == 0)       // Print message every so often.
                    Console.WriteLine(value);

                barrier2.SignalAndWait(); // Wait for the reader threads to read the current value.
                ++value;                  // Produce the next value.
            }
        }

        private static void reader(Barrier barrier1, Barrier barrier2)
        {
            int expected = 0;

            while (true)
            {
                barrier1.SignalAndWait(); // Wait for "new data available".

                if (value != expected)
                {
                    Console.WriteLine("Expected " + expected + ", got " + value);
                }

                ++expected;
                barrier2.SignalAndWait();  // Signal that we've read the data, and wait for all other threads.
            }
        }

        private static volatile int value;
    }
}

答案 3 :(得分:0)

我建议使用ConcurrentQueue - 它保证每个线程从队列中获取一个唯一的实例。 Here很好地解释了如何使用它。

ConnurrentQueue<T>.TryDequeue()是一种线程安全的方法,用于检查队列是否为空以及是否未从队列中获取项目。由于它同时执行两个操作,程序员不必担心竞争条件。

答案 4 :(得分:0)

我想我已经找到了要走的路。我创建了2个AutoResetEvent数组,每个读者有2个事件,等待写事件和设置读事件,写入器设置所有写事件并等待所有读事件。

JaredPar,你的回答非常有用并帮助我