我有以下课程:
public class AtomicLong
{
private long initial;
private long value;
public AtomicLong(long value = 0)
{
this.initial = value;
this.value = value;
}
public class Handle : IDisposable
{
private AtomicLong source;
private long amount;
public Handle(AtomicLong source, long amount)
{
this.source = source;
this.amount = amount;
}
public void Dispose()
{
if (source == null)
return;
Interlocked.Add(ref source.value, amount);
source = null;
}
}
public Handle Claim(long amount)
{
if (amount > initial)
throw new ArgumentOutOfRangeException("amount", amount, "Must be no more than the initial amount.");
if (amount < 0)
throw new ArgumentOutOfRangeException("amount", amount, "Must be nonnegative.");
while (true)
{
var oldValue = Interlocked.Read(ref value);
var newValue = oldValue - amount;
if (newValue >= 0 &&
oldValue == Interlocked.CompareExchange(ref value, newValue, oldValue))
{
return new Handle(this, amount);
}
}
}
}
一个示例用法是,我可以有一个AtomicLong unusedMemory
代表一组工作人员当前可用的内存字节数的单个。(这并不意味着接近精确 - 这只是一个粗略的衡量标准。)然后我在一堆不同的工作线程上执行此操作:
while (true)
{
var unitOfWork = WaitForUnitOfWork();
long requiredMemory = unitOfWork.RequiredMemory;
using (var handle = unusedMemory.Claim(requiredMemory))
{
//wait until requireMemory can be claimed from unusedMemory
//do work with reserved memory, represented by handle
//when handle disposes, memory is released back to the unusedMemory
}
}
我AtomicLong
班的问题是呼叫Claim
将忙,直到他们返回。我想通过使用某种操作系统级别的等待句柄抽象来解决这个问题。
你能建议我怎么做吗?
动机
考虑以下场景:
- usedMemory 的初始值为 10GB (
10 << 30
) - 100 个工作线程
- 10 个工作单元,每个需要 10GB 和 1 分钟来执行
- 第一个工人打电话
Claim(10 << 30)
,它几乎立即返回- 它开始做将在 1 分钟后完成的工作
- 大约 9 名其他工作人员拨打了相同的电话
Claim(10 << 30)
并进行了“糟糕”的忙等待 1 分钟- 9 个线程在方法中执行类似
while(true){/*do nothing*/}
循环的操作Claim
! - 大量不必要的 CPU 使用率
- 9 个线程在方法中执行类似
WaitForUnitOfWork()
其余的工人(90)在方法中做一个“好的”操作系统级等待
重要的一点: 只有在请求的内存实际上可以Claim
申请时才“便宜” 。amount
如果不是,则在可用之前一直等待。
为了完全清楚,在该Claim
方法中,我指出了导致所有差异的确切表达式(newValue >= 0
):
while (true)
{
var oldValue = Interlocked.Read(ref value);
var newValue = oldValue - amount;
if (newValue >= 0 && // <--------------------------- THIS IS THE PROBLEM
oldValue == Interlocked.CompareExchange(ref value, newValue, oldValue))
{
return new Handle(this, amount);
}
}
问题不在于是否Interlocked.CompareExchange
会很贵——我知道它很便宜。问题是当amount
调用者想要的Claim
当前大于amount
.AtomicLong
如果你有完全不同的方法来解决这类问题,或者看到我已经拥有的一些缺陷,我也想听听!