互锁并发循环奇数

时间:2016-03-09 23:36:00

标签: c# concurrency

所以,我有这段代码:

using System.IO;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Bar foo = new Bar();
        ConcurrentBag<int> items = new ConcurrentBag<int>();
        const int times = 200;
        Parallel.For(0, times, i=>{
            foreach(var j in foo.RequestValues(times)){
                items.Add(j);
            }
        });
        Dictionary<int, int> frequency = new Dictionary<int,int>();
        foreach (var item in items){
            if (!frequency.ContainsKey(item)){
                frequency[item] = 0;
            }
            else{
                frequency[item]++;
            }
        }
        foreach(var pair in frequency){
            Console.WriteLine(pair.Key + " occurred " + pair.Value + "times.");
        }
    }
    class Bar{
        const int max = 20;
        int val = -1;
        internal int[] RequestValues(int count){
            int[] result = new int[count];
            for (int i = 0; i < result.Length; i++){
                int read;
                while ((read = Interlocked.Add(ref val, 1)) >= max){
                    int check = Interlocked.CompareExchange(ref val, 0, read);
                    if (check == 0){
                        read = 0;
                        break;
                    }
                }
                result[i] = read;
            }
            return result;
        }
    }
}

但是当我运行它时,我得到一个这样的输出:

3 occurred 2098times.
2 occurred 2098times.
1 occurred 2098times.
19 occurred 2098times.
18 occurred 2098times.
17 occurred 2098times.
16 occurred 2098times.
15 occurred 2098times.
14 occurred 2098times.
13 occurred 2098times.
12 occurred 2098times.
11 occurred 2098times.
10 occurred 2098times.
9 occurred 2098times.
0 occurred 118times.

我绝对不会被0发生,只发生了118次而不是2098次。是否有一个原因?有人可以向我解释一下吗?

编辑:看来TLDR答案为&#34; Interlocked.CompareExchange返回旧值&#34;,在这种情况下,我如何知道值被交换?在我可以使用之前,该值可能已被另一个线程更改。

1 个答案:

答案 0 :(得分:1)

当忽略代码的线程部分时,它会变得更加清晰。用以下内容替换Parallel.For

for (var i = 0; i <= times; i++)
{
    foreach(var j in foo.RequestValues(times))
    {
        items.Add(j);
    }
}

显示0出现0次。但是调试时,很明显应该至少有一个条目,因为循环是:

for (int i = 0; i < result.Length; i++){

问题是frequency[item] = 0;(应为= 1;)。现在我们正确地发现了1,但这仍然无法解答您的问题。

那为什么会这样呢?

在非线程上下文中查看此代码:

for (int i = 0; i < result.Length; i++){
    int read;
    while ((read = Interlocked.Add(ref val, 1)) >= max){
        int check = Interlocked.CompareExchange(ref val, 0, read);
        if (check == 0){
            read = 0;
            break;
        }
    }
    result[i] = read;
}

read什么时候会0?我们有两个地方更改read

while ((read = Interlocked.Add(ref val, 1)) >= max){

read = 0;

0val时,第一个只会给我们-1的值。 (这只是在程序的最开始)。

第二行将其设置为0,但在序列化上下文中,它将永远不会执行,因为CompareExchange会返回val的当前值(这将是&{ #39; s >= max)。

因此,让我们回到线程的上下文。

CompareExchange 可以返回0,但前提是val在我们的增量和比较之间发生了变化。它会返回什么情况0

每当val = 0。并且val仅在read = val时为0,并且仅在之前的执行中为0。

所以read = 0的步骤如下:

  1. 两个主题必须输入whileval > 20
  2. 在执行CompareExchange之前,一个线程阻塞。
  3. 另一个线程执行CompareExchangeval现设置为0
  4. 然后第一个线程执行CompareExchange并将check设置为0。
  5. 请注意,如果发生任何其他线程修改valcheck将不会是0。它基本上需要两个线程完全同步read才能设置为0.而且其他线程很容易破坏流程。

    修改

    关于以下问题:

      

    @ BrainStorm.exe:好的,在这种情况下,我怎么知道该值设置为0?在java中,AtomicInteger的compare和swap返回一个bool,所以我可以知道,但是我怎么知道呢?

    您是否通过比较check == read知道它是否已设置(如果这是真的,那么您可以确定CompareExchange实际修改了val)。这适用于您的情况,因为线程之间不共享read。但请注意,val之后立即检查CompareExchange并不能保证val仍为0