C# 中的同步域和任务并行库
比较了两种相对较新的多线程应用程序开发方式:同步域和任务并行库。
- 下载 TaskParallelLibraryExampleSynchronized - 28 KB
- 下载 TaskParallelLibrary - 23.7 KB
- 下载 SyncronizationDomainWoMonitor - 29.5 KB
引言
在多线程应用程序开发中,有很多方法可以实现这一目标,我尝试并使用了其中的许多方法。在本文中,我想讨论在 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.