Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#多字段的不可变计数器_C#_Multithreading_Thread Safety_Immutable Collections - Fatal编程技术网

C#多字段的不可变计数器

C#多字段的不可变计数器,c#,multithreading,thread-safety,immutable-collections,C#,Multithreading,Thread Safety,Immutable Collections,我在消息计数器上有相当高的吞吐量(每秒数万个),并且正在寻找一种有效的方法来获取计数,而无需在每10秒更新一次时在任何地方都加锁,或者理想情况下不锁定每个消息计数 使用不可变计数器对象 我使用的是不可变计数器类: public class Counter { public Counter(int quotes, int trades) { Quotes = quotes; Trades = trades; } readonly p

我在消息计数器上有相当高的吞吐量(每秒数万个),并且正在寻找一种有效的方法来获取计数,而无需在每10秒更新一次时在任何地方都加锁,或者理想情况下不锁定每个消息计数

使用不可变计数器对象

我使用的是不可变计数器类:

public class Counter
{
    public Counter(int quotes, int trades)
    {
        Quotes = quotes;
        Trades = trades;
    }

    readonly public int Quotes;
    readonly public int Trades;
    // and some other counter fields snipped
}
并将在每个消息处理循环中更新此信息:

class MyProcessor
{
    System.Timers.Timer timer;
    Counter counter = new Counter(0,0);

    public MyProcessor()
    {
       // update ever 10 seconds
       this.timer = new System.Timers.Timer(10000);

       timer.Elapsed += (sender, e) => {
          var quotesPerSecond = this.counter.Quotes / 10.0;
          var tradesPerSecond = this.counter.Trades / 10.0;
          this.Counter = new Counter(0,0);
       });
    }

    public void ProcessMessages(Messages messages)
    {
       foreach(var message in messages) { /* */ }

       var oldCounter = counter;
       this.counter = new Counter(oldCounter.Quotes, oldCounter.Trades);   
    }
}
我有很多计数器(并没有全部显示),所以这意味着在各个计数器字段上有很多单独的
互锁。Increment
调用

我唯一能想到的另一种方法是锁定每次运行的
ProcessMessages
(这将是广泛的),并对一些实用程序(而不是程序崩溃的关键程序)进行重载

当我们只需要每10秒更新一次时,是否可以在没有硬联锁/线程机制的情况下以这种方式使用不可变计数器对象

标记检查以避免锁定

计时器线程是否可以为要检查的
ProcessMessages
设置一个标志,如果它看到该标志已设置,则再次从零开始计数,即

/* snipped the MyProcessor class, same as before */

System.Timers.Timer timer;
Counter counter = new Counter(0,0);
ManualResetEvent reset = new ManualResetEvent(false);

public MyProcessor()
{
   // update ever 10 seconds
   this.timer = new System.Timers.Timer(10000);

   timer.Elapsed += (sender, e) => {
      var quotesPerSecond = this.counter.Quotes / 10.0;
      var tradesPerSecond = this.counter.Trades / 10.0;
      // log
      this.reset.Set();
   });
}

// this should be called every second with a heartbeat message posted to queue
public void ProcessMessages(Messages messages)
{
   if (reset.WaitOne(0) == true)
   {
      this.counter = new Counter(this.counter.Quotes, this.counter.Trades, this.counter.Aggregates);
      reset.Reset();
   }
   else
   {
      this.counter = new Counter(
                        this.counter.Quotes + message.Quotes.Count,
                        this.counter.Trades + message.Trades.Count);
   }
}

/* end of MyProcessor class */
这是可行的,但是当进程消息停止时更新会“暂停”(虽然吞吐量非常高,但它确实会在夜间暂停数小时,理想情况下应该显示实际值而不是最后一个值)


解决此问题的一种方法是每秒向
MyProcessor.ProcessMessages()
发送一条心跳消息,以强制对消息计数器进行内部更新,并在设置
reset
ManualResetEvent时进行后续重设。

以下是针对
计数器类的三种新方法。一个用于从特定位置读取最新值,一个用于安全更新特定位置,另一个用于基于现有计数器轻松创建新的
计数器

public static Counter Read(ref Counter counter)
{
    return Interlocked.CompareExchange(ref counter, null, null);
}

public static void Update(ref Counter counter, Func<Counter, Counter> updateFactory)
{
    var counter1 = counter;
    while (true)
    {
        var newCounter = updateFactory(counter1);
        var counter2 = Interlocked.CompareExchange(ref counter, newCounter, counter1);
        if (counter2 == counter1) break;
        counter1 = counter2;
    }
}

public Counter Add(int quotesDelta, int tradesDelta)
{
    return new Counter(Quotes + quotesDelta, Trades + tradesDelta);
}

由多个线程同时直接访问
MyProcessor.counter
字段不是线程安全的,因为它既不受
锁的保护,也不受
锁的保护。上述方法使用起来很安全,因为它们是通过操作访问字段的。

我想用我提出的方法更新每个人,计数器更新是在线程本身内部推送的

一切都由
DequeueThread
循环驱动,特别是
this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds))
函数

这将从队列中返回一个项目,对其进行处理并更新计数器,或者超时,然后更新计数器-不涉及其他线程—所有操作(包括更新消息速率)都是在线程内完成的

总而言之,没有任何东西是并行运行的(就数据包的去奎因而言),它一次获取一个项目,然后处理它和之后的计数器。然后最后循环返回以处理队列中的下一项

这样就不需要同步:

internal class Counter
{
    public Counter(Action<int,int,int,int> updateCallback, double updateEvery)
    {
        this.updateCallback = updateCallback;
        this.UpdateEvery = updateEvery;
    }

    public void Poll()
    {
        if (nextUpdate < DateTimeOffset.UtcNow)
        {
            // post the stats, and reset
            this.updateCallback(this.quotes, this.trades, this.aggregates, this.statuses);
            this.quotes = 0;
            this.trades = 0;
            this.aggregates = 0;
            this.statuses = 0;
            nextUpdate = DateTimeOffset.UtcNow.AddSeconds(this.UpdateEvery);
        }
    }

    public void AddQuotes(int count) => this.quotes += count;
    public void AddTrades(int count) => this.trades += count;
    public void AddAggregates(int count) => this.aggregates += count;
    public void AddStatuses(int count) => this.statuses += count;

    private int quotes;
    private int trades;
    private int aggregates;
    private int statuses;

    private readonly Action<int,int,int,int> updateCallback;
    public double UpdateEvery { get; private set; }
    private DateTimeOffset nextUpdate;
}

public class DeserializeWorker
{
    private readonly BufferBlock<byte[]> queue = new BufferBlock<byte[]>();
    private readonly IPolygonDeserializer polygonDeserializer;
    private readonly ILogger<DeserializeWorker> logger;

    private readonly Counter counter; 
    const double UpdateFrequencySeconds = 5.0;        
    long maxBacklog = 0;

    public DeserializeWorker(IPolygonDeserializer polygonDeserializer, ILogger<DeserializeWorker> logger)
    {
        this.polygonDeserializer = polygonDeserializer ?? throw new ArgumentNullException(nameof(polygonDeserializer));
        this.logger = logger;
        this.counter = new Counter(ProcesCounterUpdateCallback, UpdateFrequencySeconds);
    }

    public void Add(byte[] data)
    {
        this.queue.Post(data);
    }

    public Task Run(CancellationToken stoppingToken)
    {
        return Task
                .Factory
                .StartNew(
                    async () => await DequeueThread(stoppingToken),
                    stoppingToken,
                    TaskCreationOptions.LongRunning,
                    TaskScheduler.Default)
                .Unwrap();
    }

    private async Task DequeueThread(CancellationToken stoppingToken)
    {
        while (stoppingToken.IsCancellationRequested == false)
        {
            try
            {
                var item = await this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds), stoppingToken);
                await ProcessAsync(item);
            }
            catch (TimeoutException)
            {
                // this is ok, timeout expired 
            }
            catch(TaskCanceledException)
            {
                break; // task cancelled, break from loop
            }
            catch (Exception e)
            {
                this.logger.LogError(e.ToString());
            }

            UpdateCounters();
        }

        await StopAsync();
    }


    protected async Task StopAsync()
    {
        this.queue.Complete();
        await this.queue.Completion;
    }

    protected void ProcessStatuses(IEnumerable<Status> statuses)
    {
        Parallel.ForEach(statuses, (current) =>
        {
            if (current.Result != "success")
                this.logger.LogInformation($"{current.Result}: {current.Message}");
        });
    }

    protected void ProcessMessages<T>(IEnumerable<T> messages)
    {
        Parallel.ForEach(messages, (current) =>
        {
            // serialize by type T
            // dispatch
        });
    }

    async Task ProcessAsync(byte[] item)
    {
        try
        {
            var memoryStream = new MemoryStream(item);
            var message = await this.polygonDeserializer.DeserializeAsync(memoryStream);

            var messagesTask = Task.Run(() => ProcessStatuses(message.Statuses));
            var quotesTask = Task.Run(() => ProcessMessages(message.Quotes));
            var tradesTask = Task.Run(() => ProcessMessages(message.Trades));
            var aggregatesTask = Task.Run(() => ProcessMessages(message.Aggregates));

            this.counter.AddStatuses(message.Statuses.Count);
            this.counter.AddQuotes(message.Quotes.Count);
            this.counter.AddTrades(message.Trades.Count);
            this.counter.AddAggregates(message.Aggregates.Count);

            Task.WaitAll(messagesTask, quotesTask, aggregatesTask, tradesTask);                                
        }
        catch (Exception e)
        {
            this.logger.LogError(e.ToString());
        }
    }

    void UpdateCounters()
    {
        var currentCount = this.queue.Count;
        if (currentCount > this.maxBacklog)
            this.maxBacklog = currentCount;

        this.counter.Poll();
    }

    void ProcesCounterUpdateCallback(int quotes, int trades, int aggregates, int statuses)
    {
        var updateFrequency = this.counter.UpdateEvery;
        logger.LogInformation(
            $"Queue current {this.queue.Count} (max {this.maxBacklog }), {quotes / updateFrequency} quotes/sec, {trades / updateFrequency} trades/sec, {aggregates / updateFrequency} aggregates/sec, {statuses / updateFrequency} status/sec");
    }
}
内部类计数器
{
公共计数器(Action updateCallback,double updateEvery)
{
this.updateCallback=updateCallback;
this.UpdateEvery=UpdateEvery;
}
公众投票
{
if(nextUpdatethis.quotes+=count;
public void AddTrades(int count)=>this.trades+=count;
public void AddAggregates(int count)=>this.aggregates+=count;
public void AddStatuses(int count)=>this.statuses+=count;
私人整数报价;
私人贸易;
私人机构;
私有int状态;
私有只读操作updateCallback;
public-double-UpdateEvery{get;private-set;}
私有日期时间偏移量nextUpdate;
}
公共类反序列化工作者
{
私有只读缓冲块队列=新缓冲块();
专用只读iPolyGon反序列化程序PolyGondSerializer;
专用只读ILogger记录器;
专用只读计数器;
const double updatefrequencycyses=5.0;
长maxBacklog=0;
公共反序列化工作程序(IPolyGon反序列化程序PolyGondSerializer,ILogger logger)
{
this.polygonderSerializer=polygonderSerializer??抛出新的ArgumentNullException(nameof(polygonderSerializer));
this.logger=记录器;
this.counter=新计数器(proceCounterUpdateCallback,UpdateFrequencySeconds);
}
公共无效添加(字节[]数据)
{
this.queue.Post(数据);
}
公共任务运行(CancellationToken stoppingToken)
{
返回任务
.工厂
斯塔特纽先生(
async()=>等待出列线程(stoppingToken),
停止通话,
TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Unwrap();
}
专用异步任务出列线程(CancellationToken stoppingToken)
{
while(stoppingToken.IsCancellationRequested==false)
{
尝试
{
var item=wait this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds),stoppingToken);
等待进程异步(项目);
}
捕获(超时异常)
{
//这是正常的,超时已过期
}
捕获(TaskCanceledException)
{
中断;//任务已取消,从循环中断
}
捕获(例外e)
{
this.logger.LogError(例如ToString());
}
UpdateCounters();
internal class Counter
{
    public Counter(Action<int,int,int,int> updateCallback, double updateEvery)
    {
        this.updateCallback = updateCallback;
        this.UpdateEvery = updateEvery;
    }

    public void Poll()
    {
        if (nextUpdate < DateTimeOffset.UtcNow)
        {
            // post the stats, and reset
            this.updateCallback(this.quotes, this.trades, this.aggregates, this.statuses);
            this.quotes = 0;
            this.trades = 0;
            this.aggregates = 0;
            this.statuses = 0;
            nextUpdate = DateTimeOffset.UtcNow.AddSeconds(this.UpdateEvery);
        }
    }

    public void AddQuotes(int count) => this.quotes += count;
    public void AddTrades(int count) => this.trades += count;
    public void AddAggregates(int count) => this.aggregates += count;
    public void AddStatuses(int count) => this.statuses += count;

    private int quotes;
    private int trades;
    private int aggregates;
    private int statuses;

    private readonly Action<int,int,int,int> updateCallback;
    public double UpdateEvery { get; private set; }
    private DateTimeOffset nextUpdate;
}

public class DeserializeWorker
{
    private readonly BufferBlock<byte[]> queue = new BufferBlock<byte[]>();
    private readonly IPolygonDeserializer polygonDeserializer;
    private readonly ILogger<DeserializeWorker> logger;

    private readonly Counter counter; 
    const double UpdateFrequencySeconds = 5.0;        
    long maxBacklog = 0;

    public DeserializeWorker(IPolygonDeserializer polygonDeserializer, ILogger<DeserializeWorker> logger)
    {
        this.polygonDeserializer = polygonDeserializer ?? throw new ArgumentNullException(nameof(polygonDeserializer));
        this.logger = logger;
        this.counter = new Counter(ProcesCounterUpdateCallback, UpdateFrequencySeconds);
    }

    public void Add(byte[] data)
    {
        this.queue.Post(data);
    }

    public Task Run(CancellationToken stoppingToken)
    {
        return Task
                .Factory
                .StartNew(
                    async () => await DequeueThread(stoppingToken),
                    stoppingToken,
                    TaskCreationOptions.LongRunning,
                    TaskScheduler.Default)
                .Unwrap();
    }

    private async Task DequeueThread(CancellationToken stoppingToken)
    {
        while (stoppingToken.IsCancellationRequested == false)
        {
            try
            {
                var item = await this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds), stoppingToken);
                await ProcessAsync(item);
            }
            catch (TimeoutException)
            {
                // this is ok, timeout expired 
            }
            catch(TaskCanceledException)
            {
                break; // task cancelled, break from loop
            }
            catch (Exception e)
            {
                this.logger.LogError(e.ToString());
            }

            UpdateCounters();
        }

        await StopAsync();
    }


    protected async Task StopAsync()
    {
        this.queue.Complete();
        await this.queue.Completion;
    }

    protected void ProcessStatuses(IEnumerable<Status> statuses)
    {
        Parallel.ForEach(statuses, (current) =>
        {
            if (current.Result != "success")
                this.logger.LogInformation($"{current.Result}: {current.Message}");
        });
    }

    protected void ProcessMessages<T>(IEnumerable<T> messages)
    {
        Parallel.ForEach(messages, (current) =>
        {
            // serialize by type T
            // dispatch
        });
    }

    async Task ProcessAsync(byte[] item)
    {
        try
        {
            var memoryStream = new MemoryStream(item);
            var message = await this.polygonDeserializer.DeserializeAsync(memoryStream);

            var messagesTask = Task.Run(() => ProcessStatuses(message.Statuses));
            var quotesTask = Task.Run(() => ProcessMessages(message.Quotes));
            var tradesTask = Task.Run(() => ProcessMessages(message.Trades));
            var aggregatesTask = Task.Run(() => ProcessMessages(message.Aggregates));

            this.counter.AddStatuses(message.Statuses.Count);
            this.counter.AddQuotes(message.Quotes.Count);
            this.counter.AddTrades(message.Trades.Count);
            this.counter.AddAggregates(message.Aggregates.Count);

            Task.WaitAll(messagesTask, quotesTask, aggregatesTask, tradesTask);                                
        }
        catch (Exception e)
        {
            this.logger.LogError(e.ToString());
        }
    }

    void UpdateCounters()
    {
        var currentCount = this.queue.Count;
        if (currentCount > this.maxBacklog)
            this.maxBacklog = currentCount;

        this.counter.Poll();
    }

    void ProcesCounterUpdateCallback(int quotes, int trades, int aggregates, int statuses)
    {
        var updateFrequency = this.counter.UpdateEvery;
        logger.LogInformation(
            $"Queue current {this.queue.Count} (max {this.maxBacklog }), {quotes / updateFrequency} quotes/sec, {trades / updateFrequency} trades/sec, {aggregates / updateFrequency} aggregates/sec, {statuses / updateFrequency} status/sec");
    }
}