[英]Thread synchronization in the Task Parallel Library (TPL)
我正在学习TPL并坚持不懈。 这只是为了学习目的,我希望人们能指导我正确的方向。
我只想要一个线程一次访问变量sum
,这样它就不会被覆盖。
我的代码如下。
using System;
using System.Threading.Tasks;
class ThreadTest
{
private Object thisLock = new Object();
static int sum = 0;
public void RunMe()
{
lock (thisLock)
{
sum = sum + 1;
}
}
static void Main()
{
ThreadTest b = new ThreadTest();
Task t1 = new Task(()=>b.RunMe());
Task t2= new Task(() => b.RunMe());
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(sum.ToString());
Console.ReadLine();
}
}
问题 -我在这段代码中是对的?
问题 -我没有锁就可以做到,因为我在某个地方读到它应该避免,因为它不允许任务彼此通信。我已经看到了一些async和await的例子,但我使用的是.Net 4.0。
谢谢
我在这段代码中是对的
明智的实施是的,但理解明智不,因为你在尝试实现逻辑时混淆了新旧世界,让我试着详细解释。
Task t1 = new Task(()=>b.RunMe());
并不意味着每次都是Thread
API的新线程 Task
API将调用线程池线程,因此很可能两个Task对象t1,t2
在大多数情况下在同一个线程上执行,用于短运行逻辑,并且在尝试更新时,从不需要显式lock
的竞争条件shared object
Sum object
出现竞争条件的更好方法是Interlocked.Increment(ref sum)
,这是一种对primitive types
进行基本操作的线程安全机制 对于你正在做的更好的API操作类型是Parallel.For
,而不是创建一个单独的Task
,好处是你可以用最少的努力运行任意数量的这种increment
操作,而不是创建一个单独的任务,它自动阻止主线程,因此您的代码应如下所示:
using System; using System.Threading.Tasks; class ThreadTest { public static int sum; } static void Main() { Parallel.For(0, 1, i => { // Some thread instrumentation Console.WriteLine("i = {0}, thread = {1}", i, Thread.CurrentThread.ManagedThreadId); Interlocked.Increment(ref ThreadTest.sum); }); Console.WriteLine(ThreadTest.sum.ToString()); Console.ReadLine(); } }
在使用Thread检测时,您会发现两个循环0,1
, managed thread id
相同的可能性,从而避免了之前建议的线程安全性需求
答案1:
这是您发布的代码的线程安全。
但是,正如Sam指出的那样,在一般情况下,这当前不是线程安全的,因为增加的字段是静态的,但锁定对象不是静态的。
这意味着可以在两个单独的线程上创建两个独立的ThreadTest
实例,然后可以从这些线程调用RunMe()
,并且因为每个实例都有一个单独的锁定对象,所以锁定不起作用。
这里的解决方案是使锁定对象也是静态的。
答案2:
您可以使用Interlocked.Increment()
没有显式锁定的情况下执行此操作:
public void RunMe()
{
Interlocked.Increment(ref sum);
}
现在到了这一点,因为我添加了一些关于downvotes的不满意的评论,没有理由:)。
您的方案正在运行,并且是经典的多线程使用。 经典因为使用系统锁,即锁实际上是操作系统的WinAPI锁,所以为了进行同步,代码必须设法响应操作系统并返回,当然,由于某些争用,可能会因切换线程而浪费一些时间。特别是如果您在运行给定任务的每个线程中多次访问RunMe或者创建更多并发任务,感谢2。
请尝试查看原子操作。 对于你的场景,它可以很好地工作,Interlocked.Increment(ref sum)。 从那里你必须限制自己直接访问总和,但这不是问题,因为Increment方法返回最新结果。
另一种选择是使用SpinLock,即如果您的操作非常快。 永远不会打开某些东西,比如Console.WriteLine或任何其他系统操作或长时间运行的计算等。
这里有许多例子:
using System;
using System.Threading;
using System.Threading.Tasks;
class ThreadTest
{
/// <summary> DO NOT TOUCH ME DIRECTLY </summary>
private static int sum/* = 0 zero is default*/;
private static int Add(int add) => Interlocked.Add(ref sum, add);
private static int Increment() => Interlocked.Increment(ref sum);
private static int Latest() => Interlocked.Add(ref sum, 0);
private static void RunMe() => Increment();
static void Main()
{
Task t1 = new Task(RunMe);
Task t2 = new Task(RunMe);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(Latest().ToString());
Console.ReadLine();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.