C# 无锁计算:如何对其他线程正在更改的N个双倍数求和?

C# 无锁计算:如何对其他线程正在更改的N个双倍数求和?,c#,performance,locking,C#,Performance,Locking,upd:让我简单地重新表述一下我的问题。 有N个双数字。有N个专用线程,每个线程更新自己的双倍编号(\u cachedProduct,在下面的示例中) 不知何故,我需要对这些数字进行求和,并且我需要在更改任何双倍数字后尽快引发索引更新事件(如果可以在10µs或更短的时间内引发此类事件,那就太好了) 下面是我如何尝试执行此任务的 =============================================== 为了计算股票交易所指数,我创建了private double[]\u ca

upd:让我简单地重新表述一下我的问题。 有N个双数字。有N个专用线程,每个线程更新自己的双倍编号(
\u cachedProduct
,在下面的示例中)

不知何故,我需要对这些数字进行
求和
,并且我需要在更改任何双倍数字后尽快引发
索引更新
事件(如果可以在10µs或更短的时间内引发此类事件,那就太好了)

下面是我如何尝试执行此任务的

===============================================

为了计算股票交易所指数,我创建了
private double[]\u cachedProduct字段。这些字段已写入
千丝万缕

    // called from another threads
    public override void InstrumentUpdated(Instrument instrument)
    {
        if (!_initialized)
        {
            if (!Initialize())
            {
                return;
            }
        }
        int instrumentId = instrument.Id;
        OrderBook ob = Program.market.OrderBook(instrument);
        if (ob.MedianOrAskOrBid == null)
        {
            _cachedProduct[instrumentId] = 0;
        }
        else
        {
            _cachedProduct[instrumentId] = ((double) ob.MedianOrAskOrBid)*_ammounts[instrumentId];
        }
    }
\u amounts
是预先初始化的数组,请忽略
初始化
方法和变量-它们只起作用

在循环中,我只对所有缓存产品求和,当值更改时,我会通知其他人

        Task.Factory.StartNew(() =>
                {
                    while(true)
                    {
                        if (_initialized)
                        {
                            break;
                        }
                    }
                    while (true)
                    {
                        CalculateAndNotify();
                        //Thread.Sleep(5);
                    }
                }
            , TaskCreationOptions.LongRunning);


    protected void CalculateAndNotify()
    {
        var oldValue = Value;
        Calculate();
        if (oldValue != Value)
        {
            NotifyIndexChanged();
        } 
    }

    protected override void Calculate()
    {
        double result = 0;
        for (int i = 0; i < _instrumentIds.Count(); i++)
        {
            int instrumentId = _instrumentIds[i];
            if (_cachedProduct[instrumentId] == 0)
            {
                Value = null;
                return;
            }
            result += _cachedProduct[instrumentId];;
        }
        Value = result;
    }
Task.Factory.StartNew(()=>
{
while(true)
{
如果(_已初始化)
{
打破
}
}
while(true)
{
CalculateAndNotify();
//睡眠(5);
}
}
,TaskCreationOptions.LongRunning);
受保护的void CalculateAndNotify()
{
var oldValue=价值;
计算();
如果(旧值!=值)
{
NotifyIndexChanged();
} 
}
受保护的覆盖无效计算()
{
双结果=0;
对于(int i=0;i<_instrumentIds.Count();i++)
{
int-instrumentId=_-instrumentId[i];
if(_cachedProduct[instrumentId]==0)
{
值=空;
返回;
}
结果+=_cachedProduct[instrumentId];;
}
值=结果;
}
我必须使用
Interlocked
来更新我的double
\u cachedProduct
值,但现在请忽略这一事实,您看到此代码还有哪些其他问题

我是否应该在
中调用
Calculate
方法,而(true)
因此我总是
使用一个内核而不延迟。我的机器有24个核心,所以我认为这是可以的

然而,如果没有
Thread.Sleep(5)
(注释),我确实看到整个程序的速度明显减慢,我不明白为什么。程序在许多地方的执行速度要慢几十倍


问题是我使用
while(true)
而不使用任何锁定的想法是否可行。或者我应该引入一些锁定方法,以便在更新
\u cachedProduct
中的一个时,我只计算
索引?

我认为如果不使用额外的线程和循环进行求和,您可能会获得更好的性能和更清晰的代码。对仪器进行每次更改时,您都会计算差异,并立即更新索引并执行通知

因此,如果线程为单个工具调用InstrumentUpdated

  change = newvalue - currentvalue;
  // used interlocked here to change the index threadsafe
  StockExchangeSum = Interlocked.Add(ref StockExchangeSum,change);
  NotifyIndexChanged();

我认为,如果不使用额外的线程和循环进行求和,您可能会获得更好的性能和更清晰的代码。对仪器进行每次更改时,您都会计算差异,并立即更新索引并执行通知

因此,如果线程为单个工具调用InstrumentUpdated

  change = newvalue - currentvalue;
  // used interlocked here to change the index threadsafe
  StockExchangeSum = Interlocked.Add(ref StockExchangeSum,change);
  NotifyIndexChanged();

double[]可以是更复杂的类型吗? WaitHandle.WaitAny在性能方面如何比较

大致如下

private Index[] indicies;

public class Index
{
    public WaitHandle Updated =
        new EventWaitHandle(false, EventResetMode.AutoReset);
    public double _value;
    public double Value
    {
        get {return _value;}
        set
        {
            if(_value != value)
            {
                _value = value;
                Updated.Set();
            }
        }
    }
}

TaskFactory.StartNew(() =>
{
    while(true)
    {
        WaitHandle.Any(indicies.Select(i => i.Updated));
        CalculateAndNotify();
    }
});

double[]可以是更复杂的类型吗? WaitHandle.WaitAny在性能方面如何比较

大致如下

private Index[] indicies;

public class Index
{
    public WaitHandle Updated =
        new EventWaitHandle(false, EventResetMode.AutoReset);
    public double _value;
    public double Value
    {
        get {return _value;}
        set
        {
            if(_value != value)
            {
                _value = value;
                Updated.Set();
            }
        }
    }
}

TaskFactory.StartNew(() =>
{
    while(true)
    {
        WaitHandle.Any(indicies.Select(i => i.Updated));
        CalculateAndNotify();
    }
});

你需要考虑的几点

  • 您是否尝试过独立于代码的其余部分分析计算块?我在计算函数中注意到:

    对于(int i=0;i<_instrumentIds.Count();i++)

    _Count()调用整个集合上的迭代,并且有可能在循环的每次行程中都调用该迭代。i、 e.您正在进行N^2/2次的_InstrumentID迭代

  • 在此计算操作期间是否修改了_instrumentIdsIEnumerable?如果是这样的话,你可能会得到各种各样的比赛条件,导致错误的答案

  • 包含CalculateAndNotify的任务是一次调用还是多次调用(嵌套)?例如,CalculateAndNotify中是否存在可能导致递归触发的操作

    如果是这样,您可能会发现同时执行了多个计算(使用多个线程,直到池耗尽)。您是否可以在操作的开始/结束时记录一些日志,或者计算同时计算的次数以检查这一点

    如果这是一个问题,您可以包括一些逻辑,其中CalculateAndNotify操作排队,新的计算操作在前一个操作完成之前无法执行


您需要考虑的一些要点

  • 您是否尝试过独立于代码的其余部分分析计算块?我在计算函数中注意到:

    对于(int i=0;i<_instrumentIds.Count();i++)

    _Count()调用整个集合上的迭代,并且有可能在循环的每次行程中都调用该迭代。i、 e.您正在进行N^2/2次的_InstrumentID迭代

  • 在此计算操作期间是否修改了_instrumentIdsIEnumerable?如果是这样的话,你可能会得到各种各样的比赛条件,导致错误的答案

  • 包含CalculateAndNotify的任务是一次调用还是多次调用(嵌套)?例如,CalculateAndNotify中是否存在可能导致递归触发的操作

    如果是这样,你可能会发现你有几个计算