C#一位作家许多读者只读了一次

C#一位作家许多读者只读了一次,c#,.net,multithreading,synchronization,C#,.net,Multithreading,Synchronization,我有4条线。一个是从网络中读取一些信息,将其写入变量,并在每一块之后发出信号。其中3个正在读取此变量,并且应该只读取一次。当前解决方案是写入程序在写入事件后设置事件并等待读卡器事件。读取器等待事件,然后读取并设置它们的事件(表示它们已读取)。问题是,读者可以阅读不止一次,而我有重复的。如何实现读者只需阅读一次的规则?我建议ConcurrentQueue-它保证每个线程从队列中获得唯一的实例。这是一个很好的解释如何使用它 ConnurrentQueue.TryDequeue()是一种线程安全的方法

我有4条线。一个是从网络中读取一些信息,将其写入变量,并在每一块之后发出信号。其中3个正在读取此变量,并且应该只读取一次。当前解决方案是写入程序在写入事件后设置事件并等待读卡器事件。读取器等待事件,然后读取并设置它们的事件(表示它们已读取)。问题是,读者可以阅读不止一次,而我有重复的。如何实现读者只需阅读一次的规则?

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


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

实现这一点的一种方法是

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

每个读卡器线程都以对原始标记节点的引用和一个
AutoResetEvent
开始。每当写入程序中出现一条新数据时,它将向每个读卡器线程发出
AutoResetEvent
信号。然后,读取器线程将简单地遍历,直到找到一个没有下一个节点的标记

此方案确保所有读卡器只查看一次数据。最大的复杂性是构造列表,以便以无锁方式写入和读取列表。这是非常直截了当的,
是互锁的

链表类型

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();
    }
  }
}
类编写器{
私人名单;
私有节点m_lastMarker;
公共写入程序(列表,节点标记){
m_lastMarker=标记;
m_list=列表;
}
//假设这不能重叠。如果这会过载,那么你会
//在这个方法中,需要在编写
//m_lastmaker
无效OnDataRead(T[]项){
如果(items.Length==0){
返回;
}
//建立一个新数据的链接列表,后面是
//表示数据结束的标记。
var head=new Node(){Data=items[0]};
无功电流=水头;
对于(int i=1;i
样本读取器类型

class Reader<T> { 
  private AutoResetEvent m_event;
  private Node<T> m_marker;

  void Go() {
    while(true) { 
      m_event.WaitOne();
      var current = m_marker.Next;
      while (current != null) { 
        if (current.IsMarker) { 
          // Found a new marker.  Always record the marker because it may 
          // be the last marker in the chain 
          m_marker = current;
        } else { 
          // Actually process the data 
          ProcessData(current.Data);
        }
        current = current.Next;
      }
    }
  }
}
类读取器{
私人自动回复m_事件;
专用节点m_标记;
void Go(){
虽然(正确){
m_event.WaitOne();
var current=m_marker.Next;
while(当前!=null){
if(current.IsMarker){
//找到新标记。请始终记录标记,因为它可能
//成为链条中的最后一个标记
m_标记=当前;
}否则{
//实际处理数据
ProcessData(当前数据);
}
当前=当前。下一步;
}
}
}
}

我想我已经找到了前进的道路。我创建了2个AutoResetEvent数组,每个读卡器都有2个事件,wait for write event和set read event,writer set all write events和wait for all read events


JaredPar,你的回答很有用,对我很有帮助

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

它还可以知道是否遗漏了某个值

但是如果您真的需要它们处于锁定步骤并且只获取一次值,我建议您使用两个
ManualResetEvent
对象和一个。下面是如何使用它们

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
这与每个等待线程有两个事件的效果相同,但更简单

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

这非常适合

您可以使用两个
屏障
在两种状态之间切换

下面是一个例子:

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;
    }
}
使用系统;
使用系统线程;
使用System.Threading.Tasks;
名称空间演示
{
内部课程计划
{
私有静态void Main(字符串[]args)
{
int readerCount=4;
屏障屏障1=新屏障(读卡器计数+1);
屏障屏障2=新屏障(读卡器计数+1);
对于(int i=0;ireader(barrier1,barrier2));
}
while(true)
{
barrier1.SignalAndWait();//等待所有线程到达“新数据可用”点。
if((值%10000)==0)//每隔一段时间打印一次邮件。
控制台写入线(值);
巴里