繁体   English   中英

任务并行库(TPL)中的线程同步

[英]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,1managed 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM