我正在学习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。
由于
答案 0 :(得分:2)
我在这段代码中是对的
明智的实施是的,但理解明智不,因为你在尝试实现逻辑时混淆了新旧世界,让我试着详细解释。
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();
}
}
使用线程检测时,您会发现两个循环的可能性0,1
,managed thread id
相同,因此无需像前面所述那样需要线程安全
答案 1 :(得分:1)
回答1:
这是您发布的代码的线程安全。
然而,正如Sam指出的那样,在一般情况下,这当前不是线程安全的,因为增加的字段是静态的,但锁定对象不是静态的。
这意味着可以在两个单独的线程上创建两个单独的ThreadTest
实例,然后可以从这些线程调用RunMe()
,并且因为每个实例都有一个单独的锁定对象,所以锁定不会工作。
这里的解决方案是使锁定对象也是静态的。
回答2:
您无需使用Interlocked.Increment()
明确锁定即可执行此操作:
public void RunMe()
{
Interlocked.Increment(ref sum);
}
答案 2 :(得分:1)
现在,我正在添加一些关于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();
}
}