由于多线程上下文中的某些速率限制,我需要在 N 个不同连接之间循环一些调用。我决定使用一个列表和一个“计数器”来实现这个功能,它应该在每次调用的实例之间“跳一个”。
我将用一个最小的例子来说明这个概念(使用一个名为 A 的类来代表连接)
class A
{
public A()
{
var newIndex = Interlocked.Increment(ref index);
ID = newIndex.ToString();
}
private static int index;
public string ID;
}
static int crt = 0;
static List<A> Items = Enumerable.Range(1, 15).Select(i => new A()).ToList();
static int itemsCount = Items.Count;
static A GetInstance()
{
var newIndex = Interlocked.Increment(ref crt);
var instance = Items[newIndex % itemsCount];
//Console.WriteLine($"{DateTime.Now.Ticks}, {Guid.NewGuid()}, Got instance: {instance.ID}");
return instance;
}
static void Test()
{
var sw = Stopwatch.StartNew();
var tasks = Enumerable.Range(1, 1000000).Select(i => Task.Run(GetInstance)).ToArray();
Task.WaitAll(tasks);
}
这按预期工作,因为它确保调用在连接之间是循环的。我可能会在“真实”代码中坚持这个实现(计数器使用 long 而不是 int)
但是,即使在我的用例中不太可能达到 int.MaxValue ,我想知道是否有办法“安全地溢出”计数器。
我知道 C# 中的“%”是“余数”而不是“模数”,这意味着一些 ?: 体操需要始终返回正数,这是我想避免的。
所以我想提出的是:
static A GetInstance()
{
var newIndex = Interlocked.Increment(ref crt);
Interlocked.CompareExchange(ref crt, 0, itemsCount); //?? the return value is the original value, how to know if it succeeded
var instance = Items[newIndex];
//Console.WriteLine($"{DateTime.Now.Ticks}, {Guid.NewGuid()}, Got instance: {instance.ID}");
return instance;
}
我期望的是Interlocked.CompareExchange(ref crt, 0, itemsCount)
只有一个线程“赢得”,一旦达到可用连接数,将计数器设置回 0。但是,我不知道如何在这种情况下使用它。
可以在这里使用 CompareExchange 或 Interlocked 中的其他机制吗?