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<int> aggregrations = new ThreadLocal<int>();
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < tasks.Length; i++)
{
aggregrations.Value = 0;
int tempi = i;
tasks[tempi] = new Task<int>(() =>
{
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<int> aggregrations = new ThreadLocal<int>();
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < tasks.Length; i++)
{
aggregrations.Value = 0;
int tempi = i;
tasks[tempi] = new Task<int>(() =>
{
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();
}
}
}
Why do i get different result with the following to sections of code
Code Sample 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<int> aggregrations = new ThreadLocal<int>();
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < tasks.Length; i++)
{
aggregrations.Value = 0;
int tempi = i;
tasks[tempi] = new Task<int>(() =>
{
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();
}
}
}
Sample 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<int> aggregrations = new ThreadLocal<int>();
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < tasks.Length; i++)
{
aggregrations.Value = 0;
int tempi = i;
tasks[tempi] = new Task<int>(() =>
{
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();
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
为什么您甚至尝试在此处使用 ThreadLocal 存储,而不仅仅是任务中的局部变量?任务并行库很可能会重用一个线程来执行多个任务,并且您的线程本地存储将被覆盖。在第一个示例中,它可能会起作用,因为您不会在每次重用线程时重置它,但这会更好:
解释代码实际执行的操作:
在第一个示例中,您初始化一个线程在启动线程上将线程局部值设置为 0,但您执行了多次(显然不是您将初始化放入 for 循环中的意图 - bug #1)。您在任务局部变量中进行累积,这很好,但随后您会用结果覆盖线程局部值,即使该线程局部值可能在顺序执行的多个任务之间共享(例如,每个核心一个线程) - bug #2。这将导致一些任务共享相同的线程本地值。 Bug #3:当您返回线程本地值时,您会很幸运,因为它将与 temp 相同,并且没有其他线程可以更改它,因此它相当于仅在任务中使用本地变量。
在第二个示例中,您在初始化时犯了同样的错误。但随后您会继续重复计数值,因为线程本地值不会在每个任务开始时重置,因此如果两个任务在同一线程上运行,第一个任务可能返回 1+2+3,第二个任务可能返回 6+1+ 2+3。
Why are you even trying to use
ThreadLocal
storage here instead of just a local variable within the Task? The Task Parallel library may well reuse a thread to execute more than one Task and your thread local storage will get overwritten. In the first example it might work since you aren't resetting it each time a thread is reused but this would be better:Explanation as to what your code actually does:
In you first example you initialize a single thread local value to 0 on the startup thread but you do it multiple times (clearly not what you intended by putting the initialization in the for loop - bug #1). You accumulate in a task-local variable which is good but you then overwrite the thread local value with the result even though that thread-local value may be shared between multiple tasks executing sequentially (e.g. one thread per core) - bug #2. This will cause some tasks to share the same thread local value. Bug #3: when you return the thread local value you get lucky because it will be the same as temp and no other thread can have changed it so it's equivalent to just using a local variable within the task.
In your second example you make the same mistake on initialization. But then you go on to double count values because the thread local value is not reset at the start of each task so if two tasks run on the same thread the first might return 1+2+3 and the second might return 6+1+2+3.