高性能锁定模式

时间:2012-04-09 10:10:23

标签: c# .net multithreading locking task-parallel-library

我正在研究下面的代码,并试图尽可能快地制作它。

每次在系统中触发事件时,都会调用execute方法。我正在测试的是查看自上次执行减少以来是否已经过了x分钟。如果x分钟已经过去,那么我们应该执行任务。

由于事件可以从任何线程触发并且很快发生,我认为触发锁定任务(即使它是一个任务)会比锁定它更好。

有没有人对如何改进这方面有任何反馈?

public class TriggerReduce
{ 
    private readonly object _lock = new object();
    private readonly int _autoReduceInterval = 5;
    private DateTime _lastTriggered;

    public void Execute(object sender, EventArgs e)
    {
        var currentTime = DateTime.Now;
        if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes > _autoReduceInterval)
        {
            var shouldRun = false;
            lock (_lock)
            {
                if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes > _autoReduceInterval)
                {
                    _lastTriggered = currentTime;
                    shouldRun = true;
                }
            }

            if (shouldRun)
            {
                Task.Factory.StartNew(() =>
                {
                    //Trigger reduce which is a long running task
                }, TaskCreationOptions.LongRunning);
            }
        }
    }
}

5 个答案:

答案 0 :(得分:1)

哦,我不会那样做!将'if(currentTime'和'shouldRun'的东西放回锁内。

不要在锁外更改/检查状态 - 它肯定会搞砸。

在这种情况下,刚刚将'shouldRun'设置为true的线程可能会被另一个进入的线程反转,并在锁定之前再次将'shouldRun'设置为false。然后第一个线程没有进入'StartNew',后面的线程也不会,因为第一个线程将_lastTriggered设置为当前时间。

OTOH :)因为'shouldRun'是一个自动变量而不是一个字段,它不是状态。只有一个线程可以进入锁内,仔细检查间隔并更新_lastTriggered时间。

我不喜欢这种复核,但目前看不出为什么它不起作用。

答案 1 :(得分:0)

使用Monitor.TryEnter。

if (Monitor.TryEnter(_lock))
{
    try
    {
        if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes >
            _autoReduceInterval)
        {
            _lastTriggered = currentTime;
            shouldRun = true;
        } 
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}

答案 2 :(得分:0)

避免锁定并使用Interlocked.Exchange会不会有帮助?

E.g。

private long _lastTriggeredTicks;

private DateTime lastTriggered
{
    get 
    { 
        var l = Interlocked.Read( ref _lastTriggeredTicks );
        return new DateTime( l );
    }
    set
    {
        Interlocked.Exchange( ref _lastTriggeredTicks, value );
    }
}

根据我的理解Interlocked is faster than a lock陈述。

答案 3 :(得分:0)

public class TriggerReduce //StartNew is fast and returns fast
{

    private readonly object _lock = new object();
    private readonly int _triggerIntervalMins = 5;

    private DateTime _nextTriggerAt = DateTime.MinValue;
    private bool inTrigger = false;

    public void Execute(object sender, EventArgs e)
    {
        lock (_lock)
        {
            var currentTime = DateTime.Now;
            if (_nextTriggerAt > currentTime)
                return;

            _nextTriggerAt = currentTime.AddMinutes(_triggerIntervalMins);//runs X mins after last task started running (or longer if task took longer than X mins)
        }

        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);
    }
}


public class TriggerReduce//startNew is a long running function that you want to wait before you recalculate next execution time
{

    private readonly object _lock = new object();
    private readonly int _triggerIntervalMins = 5;

    private DateTime _nextTriggerAt = DateTime.MinValue;
    private bool inTrigger = false;

    public void Execute(object sender, EventArgs e)
    {
        var currentTime;
        lock (_lock)
        {
            currentTime = DateTime.Now;
            if (inTrigger || (_nextTriggerAt > currentTime))
                return;

            inTrigger = true;
        }
        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);

        lock (_lock)
        {
            inTrigger = false;
            _nextTriggerAt = DateTime.Now.AddMinutes(_triggerIntervalMins);//runs X mins after task finishes
            //_nextTriggerAt = currentTime.AddMinutes(_triggerIntervalMins);//runs X mins after last task started running (or longer if task took longer than X mins)
        }
    }
}

答案 4 :(得分:0)

我认为你已经有了一个相当合理的方法。最大的问题是您正在锁定之外访问_lastTriggered。双重检查的锁定习语在这里不起作用。只需您的代码就可以看起来像这样。

public void Execute(object sender, EventArgs e)
{
    var currentTime = DateTime.Now;
    var shouldRun = false;
    lock (_lock)
    {
        TimeSpan span = currentTime - _lastTriggeed;
        if (span.TotalMinutes > _autoReduceInterval)
        {
            _lastTriggered = currentTime;
            shouldRun = true;
        }
    }

    if (shouldRun)
    {
        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);
    }
}