防止在计算过程中更改资源
Prevent changes in resources while calculations are in progress
我有一个运行速度很慢的应用程序,我正在尝试加快它的速度。
我对并发系统很陌生,所以我有点卡在这里。
很快,我可以将系统呈现如下classes:
正在处理的一些资源
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);
});
}
}
如您所见,Consumer 依赖于 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();
}
你会看到,当资源容量发生变化时,一切都会崩溃。
我想不出任何其他示例,它缺少一些细节。
- 首先,资源class的实例很多,所以锁定访问
它不会放弃使代码并发的所有努力。
- 其次,在实际应用中这个问题很少见,所以我可以
在那里牺牲一点性能。
我猜这个问题可以通过正确放置 lock
来解决,但我没能正确放置它们。
据我了解 lock
s 的概念,它可以防止同时从不同线程调用锁定的代码。将 lock
放在 Consumer::ConsumeResource
中无济于事,就像将它放在 Resource::Capacity
setter 中一样。我需要以某种方式锁定资源的修改,而消费者正在使用资源完成它的工作。
我希望我能有效地解释我的问题。这对我来说都是全新的,所以如果需要,我会尽量让事情变得更具体。
经过深思熟虑,我想到了一个有点草率的解决方案。
我决定使用 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);
}
}
这种方式按预期工作,但我觉得它也可以用 lock
s 实现。
在研究了 lock
之类的东西之后,我写了那个小帮手 class:
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);
});
}
}
在提供的示例中,这些操作有效地扼杀了并发带来的所有可能利润,但这是因为只有一个 Resource
实例。在现实世界的应用程序中,当有成千上万的资源并且只有几个冲突的情况时,这就可以正常工作。
我有一个运行速度很慢的应用程序,我正在尝试加快它的速度。 我对并发系统很陌生,所以我有点卡在这里。
很快,我可以将系统呈现如下classes:
正在处理的一些资源
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);
});
}
}
如您所见,Consumer 依赖于 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();
}
你会看到,当资源容量发生变化时,一切都会崩溃。
我想不出任何其他示例,它缺少一些细节。
- 首先,资源class的实例很多,所以锁定访问 它不会放弃使代码并发的所有努力。
- 其次,在实际应用中这个问题很少见,所以我可以 在那里牺牲一点性能。
我猜这个问题可以通过正确放置 lock
来解决,但我没能正确放置它们。
据我了解 lock
s 的概念,它可以防止同时从不同线程调用锁定的代码。将 lock
放在 Consumer::ConsumeResource
中无济于事,就像将它放在 Resource::Capacity
setter 中一样。我需要以某种方式锁定资源的修改,而消费者正在使用资源完成它的工作。
我希望我能有效地解释我的问题。这对我来说都是全新的,所以如果需要,我会尽量让事情变得更具体。
经过深思熟虑,我想到了一个有点草率的解决方案。
我决定使用 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);
}
}
这种方式按预期工作,但我觉得它也可以用 lock
s 实现。
在研究了 lock
之类的东西之后,我写了那个小帮手 class:
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);
});
}
}
在提供的示例中,这些操作有效地扼杀了并发带来的所有可能利润,但这是因为只有一个 Resource
实例。在现实世界的应用程序中,当有成千上万的资源并且只有几个冲突的情况时,这就可以正常工作。