【发布时间】:2020-04-24 21:16:02
【问题描述】:
我在消息计数器上具有相当高的吞吐量(每秒数万),并且正在寻找一种有效的方法来获取计数,而无需在任何地方放置锁,或者理想情况下在我每次更新时不锁定每个消息计数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);
}
}
我有很多计数器(未全部显示),因此意味着在各个计数器字段上会有很多单独的 Interlocked.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 时对消息计数器进行内部更新并随后重置。
【问题讨论】:
-
您的计数器远非一成不变。您直接公开公共字段,它们不是只读的。那怎么是不可变的? (如果这是高吞吐量,您可能需要重新考虑需要非常频繁地创建新对象的设计。)
-
抱歉,深夜,添加只读不小心错过了
标签: c# multithreading thread-safety immutable-collections