65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的同步域和任务并行库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2012 年 5 月 29 日

CPOL

3分钟阅读

viewsIcon

34676

downloadIcon

767

比较了两种相对较新的多线程应用程序开发方式:同步域和任务并行库。

引言

在多线程应用程序开发中,有很多方法可以实现这一目标,我尝试并使用了其中的许多方法。在本文中,我想讨论在 C# 编程语言中处理这个概念的两种相对较新的方法:同步域和任务并行库 (TPL)。前者我从 Juval Löwy 的著作《Programming .NET Components》1 中学到,后者从 Frank Hubbell 在纽约州北部视觉开发者会议上关于 TPL 的演讲2 中学到。

多线程在很多方面都很重要,其中包括关注点分离,这是 Chris Reade 的《函数式编程要素》一书中的一个短语3,它解释了避免排序细节如何更容易维护应用程序。它在最大化 CPU 利用率方面也很重要。

人体作为一个隐喻,是多线程的,因为我们可以同时移动多个肢体,同时说话、看和听。同步域的惊人之处在于它如何很好地交织多个线程,同时也将概念提升到更高的抽象级别,允许程序员避免显式编码锁、信号和其他同步概念。

同步域

使用语言属性 [Synchronization] 对派生自 ContextBoundObject 基类的类进行同步域编程,在 CPU 调用线程的公平性方面具有惊人的结果。公平性是指为每个线程分配相等的时间。但请记住,这种方法并非完美,尤其是在运行代码的开始和结束时,这是可以理解的。 对于绝对精度,需要像机器人技术中那样使用监视器等基本原理。

整个类都作为共享源

[Synchronization]
public class MySharedResource : ContextBoundObject
{
    public MySharedResource()
    { }

    public void DisplayThreadInfo(int id)
    {
        Console.Write(string.Format("{0}", id));
    }
}

然后,可以在将成为工作线程焦点的另一个类中使用此共享资源或 MySharedResource 类。同步域的魔力在于以无痛的方式控制共享资源,这是多线程应用程序中最危险的部分。

public class MyClass
{
    private MySharedResource mMySharedResource = null;
    private int mId = 0;
    public MyClass(MySharedResource MySharedResource, int id)
    {
        mMySharedResource = MySharedResource;
        mId = id;
    }
    public void ShowMessage()
    {
        Thread currentThread =
            Thread.CurrentThread;
        int threadID = currentThread.ManagedThreadId;
        for (int i = 0; i < 1000; i++)
        {
            //Monitor.Enter(MyLock);
            mMySharedResource.DisplayThreadInfo(mId);
            Count++;
            //Monitor.Wait(MyLock);
            //Monitor.Exit(MyLock);
        }
    }
}

上面的监视器代码被注释掉了,以说明同步域的壮观“公平性”。 取消对监视器行的注释,使其对每个线程绝对公平 - 即,每个线程获得完全相同的 CPU 时间片。

在应用程序(如本控制台应用程序)中,可以轻松创建工作线程

static void Main(string[] args)
{
    Thread currentThread =
        Thread.CurrentThread;
    int threadID =
        currentThread.ManagedThreadId;
    string threadName = "Main UI Thread";
    currentThread.Name = threadName;
    MySharedResource mySharedResource = new MySharedResource();

    /// Thread 1 //////////////////////////////////////////////
    MyClass obj = new MyClass(mySharedResource, 1);
    ThreadStart threadStart = obj.ShowMessage;
    Thread workerThread = new Thread(threadStart);
    ///////////////////////////////////////////////////////////
    /// Thread 2 //////////////////////////////////////////////
    MyClass obj2 = new MyClass(mySharedResource, 2);
    ThreadStart threadStart2 = obj2.ShowMessage;
    Thread workerThread2 = new Thread(threadStart2);
    ///////////////////////////////////////////////////////////
    /// Thread 3 //////////////////////////////////////////////
    MyClass obj3 = new MyClass(mySharedResource, 3);
    ThreadStart threadStart3 = obj3.ShowMessage;
    Thread workerThread3 = new Thread(threadStart3);
    ///////////////////////////////////////////////////////////
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
    workerThread.Start();
    workerThread2.Start();
    workerThread3.Start();
    while (workerThread.IsAlive
        || workerThread2.IsAlive
        || workerThread3.IsAlive)
    {
        if (Count == MaxThreads)
        {
            Count = 0;
        }
    }
    workerThread.Join();
    workerThread2.Join();
    workerThread3.Join();
    stopWatch.Stop();
    Console.WriteLine("\nExiting application.");
    TimeSpan ts = stopWatch.Elapsed;
    Console.WriteLine("{0:.} ms", ts.TotalMilliseconds);
    Console.WriteLine("press RETURN to exit");
    Console.ReadLine();
}

以上线程的创建与我们多年来的编码方式相同,但由于同步域,不必担心共享资源。 输出看起来在线程之间惊人地交织在一起

Task Parallel Library

现在 Microsoft 提供了一种新方法,可以更容易地进行多线程处理:不是操作共享资源,而是以一种特殊的方式(一种直观的方式)创建工作线程。 这就是任务并行库发挥作用的地方。

与之前相同的功能可以编写如下

namespace TaskParallelLibraryExample
{
    public class MySharedResource
    {
        public void Task(int id)
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.Write(string.Format("{0}", id));
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MySharedResource mySharedResource = new MySharedResource();
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            Parallel.Invoke(
                () => mySharedResource.Task(1),
                () => mySharedResource.Task(2),
                () => mySharedResource.Task(3));
            Console.WriteLine("\nExiting application.");
            TimeSpan ts = stopWatch.Elapsed;
            Console.WriteLine("{0:.} ms", ts.TotalMilliseconds);
            Console.WriteLine("press RETURN to exit");
            Console.ReadLine();
        }
    }
}

输出与创建多个线程的传统方式类似,并且分配给每个线程的 CPU 时间片似乎是随机的。 同样,共享资源得到了很好的管理。 因为线程不像同步域那样整齐地交织在一起,所以它的性能可以理解地更快(如果您从第一个代码中删除 [Synchronized],它会一样快,但不会同步)

关注点

有很多方法可以使用多个线程开发软件。 在本文中,对同步域和任务并行库进行简单比较研究,希望能对那些刚接触此类编程的人有所帮助。 这两种类型都作为 Visual Studio 2010 解决方案附加到本文。 我还添加了 TPL 示例的同步版本,它的性能与同步域相似,但代码中的 if 语句比我想要的要多。

参考文献

  • [1] Löwy, Juval. Programming .Net Components, 2nd Edition. O'Reilly. August 3, 2005.
  • [2] Hubbell, Frank presents Microsoft’s Task Parallel Library (TPL) http://www.vduny.org/pastmeet.asp August 25, 2011
  • [3] Reade, Chris. Elements of Functional Programming. Addison-Wesley. 1989.
© . All rights reserved.