代码之家  ›  专栏  ›  技术社区  ›  Dmitry Volkov

防止在计算过程中更改资源

  •  0
  • Dmitry Volkov  · 技术社区  · 6 年前

    我对并发系统很陌生,所以我有点困在这里。

    正在处理的某些资源

    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();
    }
    

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

    • 它不会放弃使代码并发的所有努力。
    • 第二,在实际应用中这个问题是非常罕见的,所以我可以 牺牲一些表演。

    我猜这个问题可以用适当的方法解决 lock s、 但我没把它们放对。

    s、 它防止从不同的线程同时调用锁定的代码。放置 Consumer::ConsumeResource 不会有帮助,就像放在里面一样 Resource::Capacity 塞特。我需要在使用者处理资源时以某种方式锁定对资源的修改。

    我希望我能有效地解释我的问题。这对我来说是全新的,所以如果需要的话,我会尽量让事情更具体。


    经过深思熟虑,我想出了一个有点草率的解决办法。

    我决定使用comsumer的id锁定消费者的资源属性,并手动等待下一个消费者的轮到:

    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);
        }
    }
    

    s。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Dmitry Volkov    6 年前

    经过一些关于 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();
            }
        }
    }
    

    说完,我重写了 Resource Consumer 为了保证线程安全:

    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);
            });
        }
    }
    

    在所提供的示例中,这些操作有效地消除了并发带来的所有可能的利润,但这是因为只有一个利润 资源 实例。在实际的应用程序中,当有成千上万的资源并且只有几个相互冲突的情况时,这将很好地工作。