Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在这个多线程循环实现中是否可以正确使用联锁的CompareExchange?_C#_Multithreading_Round Robin_Interlocked_Interlocked Increment - Fatal编程技术网

C# 在这个多线程循环实现中是否可以正确使用联锁的CompareExchange?

C# 在这个多线程循环实现中是否可以正确使用联锁的CompareExchange?,c#,multithreading,round-robin,interlocked,interlocked-increment,C#,Multithreading,Round Robin,Interlocked,Interlocked Increment,由于多线程上下文中的一些速率限制,我需要在N个不同的连接之间循环一些调用。我决定使用一个列表和一个“计数器”来实现这个功能,它应该在每次调用的实例之间“跳转一次” 我将用一个简单的例子来说明这个概念(使用一个名为a的类来代替连接) 我所期望的是是互锁的。CompareExchange(ref-crt,0,itemsont)将仅由一个线程“赢得”,一旦达到可用连接数,就将计数器设置回0。然而,我不知道如何在这种情况下使用它 这里可以使用CompareExchange或Interlocked中的其他

由于多线程上下文中的一些速率限制,我需要在N个不同的连接之间循环一些调用。我决定使用一个列表和一个“计数器”来实现这个功能,它应该在每次调用的实例之间“跳转一次”

我将用一个简单的例子来说明这个概念(使用一个名为a的类来代替连接)

我所期望的是
是互锁的。CompareExchange(ref-crt,0,itemsont)
将仅由一个线程“赢得”,一旦达到可用连接数,就将计数器设置回0。然而,我不知道如何在这种情况下使用它

这里可以使用CompareExchange或Interlocked中的其他机制吗?

否,该类不提供任何机制,允许您在
Int32
值溢出时将其恢复为零。原因是两个线程可以同时调用
var newIndex=Interlocked.Increment(ref-crt)语句,在这种情况下,两者都会使计数器溢出,然后都不会成功地将值更新回零。此功能超出了
联锁
类的功能。要使这样复杂的操作原子化,您需要使用其他一些同步机制,如


更新:xanatos证明上述说法是错误的。9年前问题的答案也证明了这一点是错误的。下面是
InterlocatedIncrementRoundRobin
方法的两个实现。第一个是Alex Sorokoletov的答案的简化版本:

public static int InterlockedRoundRobinIncrement(ref int location, int modulo)
{
    // Arguments validation omitted (the modulo should be a positive number)
    uint current = unchecked((uint)Interlocked.Increment(ref location));
    return (int)(current % modulo);
}
此实现非常有效,但其缺点是,backing
int
值不能直接使用,因为它在
Int32
类型的整个范围内循环(包括负值)。可用信息来自方法本身的返回值,它保证在
[0..modulo]
范围内。如果要读取当前值而不增加值,则需要另一个类似的方法,执行相同的
int->uint->int
转换:

public static int InterlockedRoundRobinRead(ref int location, int modulo)
{
    uint current = unchecked((uint)Volatile.Read(ref location));
    return (int)(current % modulo);
}
它还有一个缺点,即每4294967296递增一次,除非
是2的幂,否则它会在达到
模-1
值之前过早返回
0
值。换句话说,滚动逻辑在技术上是有缺陷的。这可能是一个大问题,也可能不是,取决于应用程序

第二个实现是xanatos的修改版本:

public static int interlocatedroundrobiniincrement(参考int位置,int模)
{
//省略参数验证(模应为正数)
while(true)
{
int电流=联锁增量(参考位置);
如果(电流>=0&&电流<模)返回电流;
//溢出。请尝试将数字归零。
while(true)
{
int current2=联锁。比较交换(参考位置,0,当前);
if(current2==current)返回0;//成功
电流=电流2;
if(电流>=0&&电流<模)
{
break;//另一个线程将该数字归零。请重试增量。
}
}
}
}
效率稍低(尤其是对于较小的
值),因为偶尔会有
联锁。增量
操作会导致超出范围的值,该值会被拒绝并重复该操作。尽管它的优点是,在该方法的一些调用过程中,backing
int
值仍保持在
[0..modulo]
范围内,除了一些非常短的时间跨度之外。

不,该类不提供任何机制,允许您在
Int32
值溢出时将其恢复为零。原因是两个线程可以同时调用
var newIndex=Interlocked.Increment(ref-crt)语句,在这种情况下,两者都会使计数器溢出,然后都不会成功地将值更新回零。此功能超出了
联锁
类的功能。要使这样复杂的操作原子化,您需要使用其他一些同步机制,如


更新:xanatos证明上述说法是错误的。9年前问题的答案也证明了这一点是错误的。下面是
InterlocatedIncrementRoundRobin
方法的两个实现。第一个是Alex Sorokoletov的答案的简化版本:

public static int InterlockedRoundRobinIncrement(ref int location, int modulo)
{
    // Arguments validation omitted (the modulo should be a positive number)
    uint current = unchecked((uint)Interlocked.Increment(ref location));
    return (int)(current % modulo);
}
此实现非常有效,但其缺点是,backing
int
值不能直接使用,因为它在
Int32
类型的整个范围内循环(包括负值)。可用信息来自方法本身的返回值,它保证在
[0..modulo]
范围内。如果要读取当前值而不增加值,则需要另一个类似的方法,执行相同的
int->uint->int
转换:

public static int InterlockedRoundRobinRead(ref int location, int modulo)
{
    uint current = unchecked((uint)Volatile.Read(ref location));
    return (int)(current % modulo);
}
它还有一个缺点,即每4294967296递增一次,除非
是2的幂,否则它会在达到
模-1
值之前过早返回
0
值。换句话说,滚动逻辑在技术上是有缺陷的。这可能是一个大问题,也可能不是,取决于应用程序

第二个实现是xanatos的修改版本:

public static int interlocatedroundrobiniincrement(参考int位置,int模)
{
//省略参数验证(模应为正数)
while(true)
{
int电流=联锁。增量
public static int InterlockedRoundRobinIncrement(ref int location, int modulo)
{
    // Arguments validation omitted (the modulo should be a positive number)
    while (true)
    {
        int current = Interlocked.Increment(ref location);
        if (current >= 0 && current < modulo) return current;

        // Overflow. Try to zero the number.
        while (true)
        {
            int current2 = Interlocked.CompareExchange(ref location, 0, current);
            if (current2 == current) return 0; // Success
            current = current2;
            if (current >= 0 && current < modulo)
            {
                break; // Another thread zeroed the number. Retry increment.
            }
        }
    }
}
static int crt = -1;
static readonly IReadOnlyList<A> Items = Enumerable.Range(1, 15).Select(i => new A()).ToList();
static readonly int itemsCount = Items.Count;
static readonly int maxItemCount = itemsCount * 100;

static A GetInstance()
{
    int newIndex;

    while (true)
    {
        newIndex = Interlocked.Increment(ref crt);

        if (newIndex >= itemsCount)
        {
            while (newIndex >= itemsCount && Interlocked.CompareExchange(ref crt, -1, newIndex) != newIndex)
            {
                // There is an implicit memory barrier caused by the Interlockd.CompareExchange around the
                // next line
                // See for example https://afana.me/archive/2015/07/10/memory-barriers-in-dot-net.aspx/
                // A full memory barrier is the strongest and interesting one. At least all of the following generate a full memory barrier implicitly:
                // Interlocked class mehods
                newIndex = crt;
            }

            continue;
        }

        break;
    }

    var instance = Items[newIndex % itemsCount];
    //Console.WriteLine($"{DateTime.Now.Ticks}, {Guid.NewGuid()}, Got instance: {instance.ID}");
    return instance;
}
maxItemCount = (int.MaxValue - MaximumNumberOfThreads) / itemsCount * itemsCount;
if (newIndex >= itemsCount)
{
    int newIndex2;
    while (newIndex >= itemsCount && (newIndex2 = Interlocked.CompareExchange(ref crt, 0, newIndex)) != newIndex)
    {
        // If the Interlocked.CompareExchange is successfull, the while will end and so we won't be here,
        // if it fails, newIndex2 is the current value of crt
        newIndex = newIndex2;
    }

    continue;
}
 //this incurs some cost, but "should" ensure that the int range
 // is mapped to the unit range (int.MinValue is mapped to 0 in the uint range)
 static ulong toPositive(int i) => (uint)1 + long.MaxValue + (uint)i;

 static A GetInstance()
 {
    //this seems to overflow safely without unchecked
    var newCounter = Interlocked.Increment(ref crt);
    //convert the counter to a list index, that is map the unsigned value
    //to a signed range and get the value modulus the itemCount value
    var newIndex = (int)(toPositive(newCounter) % (ulong)itemsCount);
    var instance = Items[newIndex];
    //Console.WriteLine($"{DateTime.Now.Ticks}, Got instance: {instance.ID}");
    return instance;
 }