1


0

ThreadLocal集約とタスク並列ライブラリ

以下のコードのセクションで異なる結果が得られるのはなぜですか

コードサンプル1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ThreadLocalTasks
{
    class Program
    {
        static void Main(string[] args)
        {

            ThreadLocal aggregrations = new ThreadLocal();
            Task[] tasks = new Task[10];

            for (int i = 0; i < tasks.Length; i++)
            {
                aggregrations.Value = 0;
                int tempi = i;
                tasks[tempi] = new Task(() =>
                {
                    int temp = 0;
                    for (int j = 1; j <= 3; j++)
                    {
                        temp += j;
                    }
                    aggregrations.Value = temp;
                    return aggregrations.Value;
                });

            }

            tasks.ToList().ForEach(x => {
                x.Start();
            });

            Task.WaitAll(tasks);

            int sum = 0;

            tasks.ToList().ForEach(x =>
            {
                sum += x.Result;
            });

            Console.WriteLine("Sum: {0}", sum);

            Console.WriteLine("Press any key to quit..");
            Console.ReadKey();
        }
    }
}

サンプル2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ThreadLocalTasks
{
    class Program
    {
        static void Main(string[] args)
        {

            ThreadLocal aggregrations = new ThreadLocal();
            Task[] tasks = new Task[10];

            for (int i = 0; i < tasks.Length; i++)
            {
                aggregrations.Value = 0;
                int tempi = i;
                tasks[tempi] = new Task(() =>
                {
                    for (int j = 1; j <= 3; j++)
                    {
                        aggregrations.Value += j;
                    }
                    return aggregrations.Value;
                });

            }

            tasks.ToList().ForEach(x => {
                x.Start();
            });

            Task.WaitAll(tasks);

            int sum = 0;

            tasks.ToList().ForEach(x =>
            {
                sum += x.Result;
            });

            Console.WriteLine("Sum: {0}", sum);

            Console.WriteLine("Press any key to quit..");
            Console.ReadKey();
        }
    }
}

1 Answer


1


タスク内のローカル変数だけでなく、ここで「ThreadLocal」ストレージを使用しようとするのはなぜですか? Task Parallelライブラリは、スレッドを再利用して複数のタスクを実行する可能性があり、スレッドのローカルストレージは上書きされます。 最初の例では、スレッドが再利用されるたびにリセットしないため、動作する可能性がありますが、これはより良いでしょう:

for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = new Task(() =>
            {
                int sum = 0;
                for (int j = 1; j <= 3; j++)
                {
                    sum += j;
                }
                return sum;
            });

        }

コードが実際に行うことに関する説明:

最初の例では、起動スレッドで単一スレッドのローカル値を0に初期化しますが、それを複数回行います(forループに初期化を配置することで意図したとおりではありません-バグ#1)。 あなたは良いタスクローカル変数に蓄積しますが、そのスレッドローカル値が連続して実行する複数のタスク間で共有される場合でも、結果でスレッドローカル値を上書きします(例: コアごとに1つのスレッド)-バグ#2。 これにより、一部のタスクが同じスレッドローカル値を共有します。 バグ#3:スレッドのローカル値を返すとき、それはtempと同じであり、他のスレッドがそれを変更することはできず、タスク内でローカル変数を使用するのと同等であるため、幸運になります。

2番目の例では、初期化で同じ間違いをします。 ただし、各タスクの開始時にスレッドローカル値がリセットされないため、2つの値をダブルカウントします。同じスレッドで2つのタスクを実行すると、1つ目は1 + 2 + 3を返し、2つ目は6 + 1 + 2 + 3。