在进行计算时防止资源变化

时间:2018-09-21 21:33:23

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

我有一个运行速度很慢的应用程序,我正在尝试加快速度。 我对并发系统还很陌生,所以这里有些卡住了。

很快,我可以将系统分为以下几类:

一些正在处理的资源

public class Resource
{
    public int Capacity { get; set; } = 1000;
}

消费者

public class Consumer
{
    private readonly int _sleep;

    public Consumer(int sleep)
    {
        _sleep = sleep;
    }

    public void ConsumeResource(Resource resource)
    {
        var capture = resource.Capacity;
        Thread.Sleep(_sleep);   // some calsulations and stuff
        if (resource.Capacity != capture)
            throw new SystemException("Something went wrong");
        resource.Capacity -= 1;
    }
}

和负责这项工作的资源管理器

public class ResourceManager
{
    private readonly List<Consumer> _consumers;
    private readonly Resource _resource;

    public ResourceManager(List<Consumer> consumers)
    {
        _consumers = consumers;
        _resource = new Resource();
    }

    public void Process()
    {
        Parallel.For(0, _consumers.Count, i =>
        {
            var consumer = _consumers[i];
            consumer.ConsumeResource(_resource);
        });
    }
}

因此,如您所见,消费者依赖于资源状态。如果您使用以下代码运行此仿真

static void Main(string[] args)
{
    var consumers = new List<Consumer>
    {
        new Consumer(1000),
        new Consumer(900),
        new Consumer(800),
        new Consumer(700),
        new Consumer(600),
    };

    var resourceManager = new ResourceManager(consumers);
    resourceManager.Process();
}

您将看到,当资源容量发生变化时,一切都会中断。

我想不出任何其他示例,它缺少一些细节。

  • 首先,有很多Resource类的实例,因此锁定访问 这样做不会放弃所有使代码并发的工作。
  • 第二,在实际应用中,此问题非常罕见,因此我可以 在那里牺牲一点表现。

我猜可以通过正确放置lock来解决此问题,但是我无法正确放置它们。

据我所知lock的概念,它防止了从不同线程同时调用锁定的代码。将lock放在Consumer::ConsumeResource中无济于事,就像将其放入Resource::Capacity设置器中一样。我需要某种方式来锁定使用者在使用资源进行工作时锁定资源的修改。

我希望我能有效地解释我的问题。这对我来说是很新的,所以如果需要,我会尝试使事情更具体。


经过长时间的努力,我想到了一个草率的解决方案。

我决定使用消费者的ID锁定消费者的Resource属性,并手动等待下一个消费者的回合:

public class Resource
{
    private int Capacity { get; set; } = 1000;

    private Guid? _currentConsumer;

    public int GetCapacity(Guid? id)
    {
        while (id.HasValue && _currentConsumer.HasValue && id != _currentConsumer)
        {
            Thread.Sleep(5);
        }

        _currentConsumer = id;
        return Capacity;
    }

    public void SetCapacity(int cap, Guid id)
    {
        if (_currentConsumer.HasValue && id != _currentConsumer)
            return;

        Capacity = cap;
        _currentConsumer = null;
    }
}

public class Consumer
{
    private readonly int _sleep;

    private Guid _id = Guid.NewGuid();

    public Consumer(int sleep)
    {
        _sleep = sleep;
    }

    public void ConsumeResource(Resource resource)
    {
        var capture = resource.GetCapacity(_id);
        Thread.Sleep(_sleep);   // some calsulations and stuff
        if (resource.GetCapacity(_id) != capture)
            throw new SystemException("Something went wrong");
        resource.SetCapacity(resource.GetCapacity(_id) - 1, _id);
    }
}

这种方式可以正常工作,但我感觉也可以用lock s来实现。

1 个答案:

答案 0 :(得分:0)

在对lock和事物进行了一些研究之后,我编写了这个小助手类:

public class ConcurrentAccessProvider<TObject>
{
    private readonly Func<TObject> _getter;
    private readonly Action<TObject> _setter;
    private readonly object _lock = new object();

    public ConcurrentAccessProvider(Func<TObject> getter, Action<TObject> setter)
    {
        _getter = getter;
        _setter = setter;
    }

    public TObject Get()
    {
        lock (_lock)
        {
            return _getter();
        }
    }

    public void Set(TObject value)
    {
        lock (_lock)
        {
            _setter(value);
        }
    }

    public void Access(Action accessAction)
    {
        lock (_lock)
        {
            accessAction();
        }
    }
}

以此,我重写了ResourceConsumer使其具有线程安全性:

public class Resource
{
    public ConcurrentAccessProvider<int> CapacityAccessProvider { get; }
    private int _capacity;

    public Resource()
    {
        CapacityAccessProvider = new ConcurrentAccessProvider<int>(() => _capacity, val => _capacity = val);
    }

    public int Capacity
    {
        get => CapacityAccessProvider.Get();
        set => CapacityAccessProvider.Set(value);
    }
}

public class Consumer
{
    private readonly int _sleep;

    public Consumer(int sleep)
    {
        _sleep = sleep;
    }

    public void ConsumeResource(Resource resource)
    {
        resource.CapacityAccessProvider.Access(() =>
        {
            var capture = resource.Capacity;
            Thread.Sleep(_sleep);   // some calsulations and stuff
            if (resource.Capacity != capture)
                throw new SystemException("Something went wrong");
            resource.Capacity -= 1;

            Console.WriteLine(resource.Capacity);
        });
    }
}

在提供的示例中,这些操作有效地杀死了并发中的所有可能利润,但这是因为只有一个Resource实例。在现实世界的应用程序中,如果有成千上万的资源并且只有几种冲突的情况,那将很好地工作。