C#并行编程高级教程:精通.NET 4 Parallel Extensions中文(第2部分)

5星(超过95%的资源)
所需积分/C币:48 2013-10-11 13:44:49 26.02MB PDF
59
收藏 收藏
举报

内容简介 您想淋漓尽致地发挥多核计算机系统的处理能力吗?《C#并行编程高级教程:精通NET 4 Parallel Extensions》将帮助您实现这一夙愿。这本精品书籍浓墨重彩地描述如何使用C# 4、Visual Studio 2010和.NET Framework 4高效地创建基于任务的并行应用程序,详细讲述最新的单指令、多数据流指令和向量化等并行编程技术,介绍现代并行库,讨论如何珠联璧合地使用高级Intel工具与C#,并指导您巧妙使用新引入的轻型协调结构来开发自己的解决方案并解决最棘手的并发编程问题。 主要内容 ◆介绍如何基于新Task Parallel Library和.NET 4设计稳定的可扩展并行应用程序。 ◆讲解命令式数据并行、命令式任务并行、并发集合以及协调数据结构。 ◆描述PLINQ高级声明式数据并行。 ◆讨论如何使用新的Visual Studio 2010并行调试功能来调试匿名方法、任务和线程。 ◆演示如何对数据源进行分区,以便在不同任务和线程之间合理分配工作负荷。 作者简介 Caston C.Hillar是一位独立软件咨询师,自1997年起便一直从事并行编程、多处理器和多核领域的研究,Gaston拥有使用C#和.NET Framework来设计和开发各种复杂并行解决方案的丰富经验,曾于2009年荣膺Intel Black Belt Software Developer奖。 目录 第1章 基于任务的程序设计 1.1 使用共享内存的多核系统 1.1.1 共享内存多核系统与分布式内存系统之间的区别 1.1.2 并行程序设计和多核程序设计 1.2 理解硬件线程和软件线程 1.3 理解Amdahl法则 1.4 考虑Gustafson法则 1.5 使用轻量级并发模型 1.6 创建成功的基于任务的设计 1.6.1 以并发的思想指导设计 1.6.2 理解交错并发、并发和并行之间的区别 1.6.3 并行化任务 1.6.4 尽量减少临界区 1.6.5 理解多核并行程序的设计原则 1.7 为NUMA架构和更高的可扩展性做好准备 1.8 判断是否适合并行化 1.9 小结 第2章 命令式数据并行 2.1 加载并行任务 2.1.1 System.Threading.Tasks.Parallel类 2.1.2 Parallel.Invoke 2.2 将串行代码转换为并行代码 2.2.1 检测可并行化的热点 2.2.2 测量并行执行的加速效果 2.2.3 理解并发执行 2.3 循环并行化 2.3.1 Parallel.For 2.3.2 Parallel.ForEach 2.3.3 从并行循环中退出 2.4 指定并行度 2.4.1 ParallelOptions 2.4.2 计算硬件线程 2.4.3 逻辑内核并不是物理内核 2.5 通过甘特图检测临界区 2.6 小结 第3章 命令式任务并行 3.1 创建和管理任务 3.1.1 System.Threading.Tasks.Task 3.1.2 理解Task状态和生命周期 3.1.3 通过使用任务来对代码进行并行化 3.1.4 等待任务完成 3.1.5 忘记复杂的线程 3.1.6 通过取消标记取消任务 3.1.7 从任务返回值 3.1.8 TaskCreationOptions 3.1.9 通过延续串联多个任务 3.1.10 编写适应并发和并行的代码 3.2 小结 第4章 并发集合 4.1 理解并发集合提供的功能 4.1.1 System.Collections.Concurrent 4.1.2 ConcurrentQueue 4.1.3 理解并行的生产者-消费者模式 4.1.4 ConcurrentStack 4.1.5 将使用数组和不安全集合的代码转换为使用并发集合的代码 4.1.6 ConcurrentBag 4.1.7 IProducerConsumerCollection 4.1.8 BlockingCollection 4.1.9 ConcurrentDictionary 4.2 小结 第5章 协调数据结构 5.1 通过汽车和车道理解并发难题 5.1.1 非预期的副作用 5.1.2 竞争条件 5.1.3 死锁 5.1.4 使用原子操作的无锁算法 5.1.5 使用本地存储的无锁算法 5.2 理解新的同步机制 5.3 使用同步原语 5.3.1 通过屏障同步并发任务 5.3.2 屏障和ContinueWhenAll 5.3.3 在所有的参与者任务中捕捉异常 5.3.4 使用超时 5.3.5 使用动态数目的参与者 5.
第5章协调数据结构 在这个示例中,这些字符串并不会像在前一个示例中那样根据时间排序。不过,您可 以在额外操作中完成排序。相比之前的版本,不带锁的新版本具有以下优点: 更为简单,因为不需要创建临界区,因此代码也更不容易出错。 更具可扩展性,因为没必要自旋等待获得锁,而且创建每一个字符串目志的代码 也不需要串行化 然而,这个不带锁的代码版本具有以下缺点: 需要消耗更多的内存,因为这段代码不是使用一个单独的 String Builder实例来创 建字符串目志,而是在每一个参与者中都使用了一个 String Builder实例,而且还 使用了一个汇总 String Builder实例来收集结果。 增加了从每一个任务采集结果并生成最终的字符串日志的额外开销。这个开销是 个串行操作产生的。 生成的字符串日志是无序的。如果您需要有序的日志,那么还需要添加一个排序 操作。 这个示例表明,在某些情况下,锁是可以避免的。为了选择最方便的替代 方案,您需要事先分析各种方案的优点和缺点。有时候,可以很方便地避免使 用锁;而在某些情况下,可能不允许无锁方案所带来的额外内存消耗和其他开 销。务必要记住的是,可以使用剖析功能来帮助您选择最方便的方案 在这个示例中,还有另外一种替换方案:您可以使用第4章介绍的新的并发集合来向 日志添加新的字符串。通过使用新的并发集合,可以省去需要添加显式同步的必要性,因 为这些新的并发集合已经准备好了应对并发地添加和删除元素。 55将自旋锁用作互斥锁原语 如果持有锁的时间总是非常短,而且锁的粒度很精细,那么自旋锁可以获得比其他锁 机制更好的性能。有时候,性能剖析和其他性能监测表明, Monitor互斥锁的开销非常大 在这种情况下,您可以对将 Monitor锁替换为自旋锁的结果进行测试。 下面的代码展示了程序清单5-1中所展示的Main方法第一部分的一个新版本。这个版 本的代码通过一个名为s的 SpinLock实例来获得一个锁,然后运行临界区内的代码,并 且在屏障的所有参与者内通过一个名为sb的 String Builder向日志字符串添加信息。这里将 Monitor获取锁和释放锁的方法替换为了 SpinLock实例及其方法。不过,这并不一定意味 着 SpinLock是替换 Monitor的更好的解决方案—这取决于不同的实际情况。事实上, String Builder要求内存分配,因此可能需要消耗未知的时间,因为潜在的分页操作可能会 消耗大量CPU周期。将 StringBuilder的 Append方法放在使用 Spin lock的临界区并不是一 个好方法。下面的代码展示了如何对代码进行修改,从而将 Monitor替换为 SpinLock 183 c#并行编程高级教程:精通NET4 Parallel Extensions static void Main(string[] args) 可从 tasks new Task[ participants]; Wrox com 下载源代码 barrier =new Barrier( participants, (barrier)=> Console. WriteLine("Current phase: 10]", barrier. Current PhaseNumber) }); // You pass false for enableThreadownerTracking // because you want the best performance out of SpinLock var sl new SpinLock(false)i var sb new stringBuilder()i for (int i=0;1< ticipants; i++) tasks[i]= Task. Factory. startNew((num)=> ar participantNumber =(int)numi f int 0;j<10;j++) CreatePlanets(participantNumber)i barrier. SignalAndWait()i Createstars (participantNumber)i barrier. SignalAndwait()i CheckcollisionsBetweenPlanets(participantNumber)i barrier. SignalAndwait()i CheckcollisionsBetweenStars(participantNumber)i barrier. signalAndwait(i RenderCollisions(participantNumber)i barrier. SignalAndwait()i var logline string. Format "Time:(0), Phase: (1), Participant: (21, Phase completed OK\n", DateTime. Now. TimeofDay, barrier. CurrentPhaseNumber, participantNumber)i bool lockTaken s false s1. Enter《xef1。 krAken); / SpinLock acquired a lock / Critical section sb. Append (logLine) / End of critical section finally / You need to make sure that /y。 u release the1ock 184 第5章协调数据结构 (1。 krAken) / Gives up the lock if it actually acquired it 7/ You want performance at the expense of fairness / Therefore, you pass false for useMemoryBarrier sl Exit(false)i },i); var finalTask Task Factory. ContinuewhenAll( tasks, (tasks)=> // Wait for all the tasks to ensure / the propagation of any exception occurred // in any of the tasks Task. WaitAll( tasks)i Console. Writeline "All the phases were executed. "); Console. WriteLine(sb)i //Dispose the Barrier instance barrier. Dispose()i }); / Wait for finalTask to finish finalTask, wait()i Console, Readline(i Snippet5-10中的代码片段 通过使用 SpinLock获得锁和释放锁的输出结果与之前通过使用 Monitor获得锁和释放 锁的输出结果是一致的。 String Builder实例sb的最终内容包含了所有参与者在临界区输出 的信息。 这段代码将 false作为参数传递给 SpinLock结构体的构造函数,并且将该实例存储在 sl局部变量中,这个局部变量可以被所有的参与者任务共享。 false值表示禁用作为调试目 的而使用的跟踪线程D的选项。如果您想要获得 SpinLock的最佳性能,那么可以像上面 这个示例一样将 false传递给构造函数。 SpinLock能够确保临界区将会串行执行,因此,在 SpinLock获得了一个互斥锁之后 也能安全地调用 Append方法。每一个参与者都将bool变量 lock Taken设置为 false,然后 调用 SpinLock实例s的 Enter方法,通过引用参数将 lock Taken传入。ty…, finally代码块 能够保证如果发生了错误,锁能够被释放。此时此刻, Enter方法将会试图获得锁,如果不 能获得锁,那么任务将会在一个循环内等待,并不断地检查锁是否变得可用。任务会一直自 旋,直到获得锁为止。一旦获得锁之后, lock Taken将会被设置为tue,并且∥ Critical section 这行代码后面的代码将会开始运行。如果此时有其他任务调用 Enter方法,则那些任务将 185 C#并行编程高级教程:精通NET4 Parallel Extensions 会开始自旋,直到运行临界区的任务调用 SpinLock实例sl的Ei方法将锁释放。因此, 确保这个方法一定会得到调用是一件非常重要的事情,即使在临界区内发生了异常也是如 此。 finally区域中的代码会检测 lockTaken是否设置为tue,从而确保具有在真正获得了锁 的时候才释放锁。当这一行代码执行之后,其他任务中的某一个任务就可以获得锁,并且 执行同样顺序的操作。为了迅速地将退出临界区的操作发布给其他线程,代码在调用Exit 方法的时候传入了 false参数,表示不用产生内存屏障( memory fence)。因为 SpinLock没有 使用内存屏障,所以可以通过牺牲公平性来获得性能提升。 bool lockTaken false try 可从 下载源代码 sl Enter(ref lockTaken); // SpinLock acquired a lock // Critical section sb. Append(logLine)i // End of critical section finally // You need to make sure that / you release the lock if(1。 krAken) / Gives up the lock if it actually acquired it // You want performance at the expense of fairness / Therefore, you pass false for useMemoryBarrier sl Exit(false)i Snippet5_11中的代码片段 不要将 SpinLock声明为只读字段,因为如果这么做的话,会导致每次调 用这个字段都返回 SpinLock的一个新副本,而不是原始的那个。所有对 Enter 方法的调用都能成功获得锁,因此受保护的临界区不会按照预期进行串行化。 551使用超时 与前面所介绍的 Monitor锁一样, SpinLock也提供了在指定的超时时间内获得锁的方 法。通过使用 SpinLock实例s的 TryEnter方法而不是 Enter方法可以建立超时。这个方法 有一种定义,可以在第1个参数中接受要等待的亳秒数,在第2个参数中接收一个bool变 量。与 Enter不同, TryEnter不会无限制地等待锁变得有效,而是会阻塞直到锁变得有效或 者指定的超时时间已经达到。因此,在调用 TryEnter之后,有必要检测bol变量的值,因 为 false值将表示因为超时而并没有获得锁。 下面的代码展示了一个新版本的代码,这段代码试图获得一个带有2000毫秒超时的 186 第5章协调数据结构 锁,然后运行临界区。调用 TryEnter方法的下一行代码会检查 lockTaken是否为 false,如 果为 false,则抛出一个 Timeout Exception异常。在这个示例中,异常并没有被捕捉到,因 此任务会出错退出并且停止执行。 bool lockTaken false tr y 可从 Hiro.on 下载源代码 sl. TrYEnter(2000, ref lockTaken)i i£(!1。 kraken / It was not possible to acquire the lock cons。e. Writeline《 Lock timeout for participant: 0)", participantNumber) row new TimeoutException string. Format( Participants are requiring more than [0] seconds w+ to acquire the lock at the Phase (11 2000, barrier. CurrentPhaseNumber)); / SpinLock acquired a lock // Critical section sb. Append(logLine) / End of critical section finally / You need to make sure that // you release the lock if (lockTaken) / Gives up the lock if it actually acquired it // SpinLock doesn't / You want performance at the expense of fairness / Therefore, you pass false for useMemoryBarrier sl Exit(false)i Snippet5_12中的代码片段 552使用基于自旋的等待 如果等待某个条件满足需要的时间很短,而且您不希望发生昂贵的上下文切换,那么 基于自旋的等待是一种很好的替换方案。 Spin Wait类型不仅提供了基本的自旋功能,而且 还提供了 SpinUntil)法,使用这个方法能够自旋直到满足某个条件为止。此外, Spin Wait 是一个 struct,而且只不过Int32大小,因此并不会产生不必要的内存分配的开销。从内存 使用的角度来说, Spin Wait的开销非常小 187 C并行编程高级教程:精通NET4 Parallel Extensions 长时间的自旋并不是很好的做法,因为自旋会阻塞更高优先级的线程及其相关的任 务,还会阻塞垃圾回收器。相反,如果自旋的时间过长, Spin Wai会让出底层线程的时间 片,并触发上下文切换。这种同步类型被设计为在大部分情况下提供正确的自旋行为,而 不会和简单的循环行为产生混淆。 Spin Wait还提供了一种智能的行为,能够判断何时停止 自旋并触发一次上下文切换。这种上下文切换开销很大,但是继续长时间自旋的开销更大。 Spin Wait通常会在自旋时间达到内核切换所需的时间时停止自旋。 SpinLock只不过是对 Spin Wait的简单包装 当一个线程自旋时,它会将一个内核放入到一个繁忙的循环中,而这不会让出当前处 理器时间片剩余的部分。然而,如前所述, Spin Wait结构中包含的智能逻辑会在自旋达到 足够长的时间时停止自旋并且让壯处理器。当一个任务或线程调用 Thread. Sleep方法的时 候,底层的线程可能会让出当前处理器时间片的剩余部分,这是一个大开销的操作。因此, 在大部分情祝下,不要在循环内调用 Thread. Sleep方法等待特定条件的满足。然而,需要 注意的特别重要的一点是,如果给 Thread. Sleep传入了0,那么当前线程会被挂起,从而允 许其他等待的线程开始执行 第4章介绍了 ConcurrentBag,并列举了一个小例,这个示例并发地加载了3个方法,分 别表示生产者和消费者。 Capitalize WordsIn Sentences既是 Producesentences的消费者,也是 RemoveLettersInSentences F生产者。 Capitalize WordsInSentences通过 Spint Wait. SpinUntil方 法等待共享的bol变量 producing Sententes变成tue之后才开始工作,如下所示 private static void CapitalizeWordsInSenterces ( 可从 char[l delimiterchars =. WTOX COLa ";"I"," 下载源代吗 it //. Start after Produce sentences began working System. Threading SpinWait, SpinUntil((.=>. producingsentences)i try apitalizingWords= true i This example uses this spinning for educati onal purposes // This conditi on running in a loop (spinning)is very inefficient / A. It isn't a best practice as explained in chapter 4 // Chapter 4 explained an improved version while((! sentencesBag, IsEmpty)II( producingsentence s) tring sentence iff sentencesBag. TryTake(out sentence)) capWords InsentencesBag Add t italizewords (delimiterchar ntence ")) finally 188 第5章协调数据结构 capitalizingWords false Snippet5_-13中的代码片段 如果 producing Sentences永远都不变为true,那么运行 Capitalize Sentences的 任务就会永远阻塞下去。如果将 Produce Sentences方法中给这个变量赋予true值的那一行 注释掉,就很容易理解这个状况了: private static woid Produce Sentences ( 可从 string[] possiblesentences MIUR. OIml 下载源代配 ConcurrentBag is included in the System Concurrent Collections namespace", Is parallelism important for cloud-computing? ", Parallelism is very important for cloud-computing! ", Concurrentqueue is one of the new concurrent collections added in.NET Framework 4 Concurrentstack is a concurrent collection that represents a LIFo collection lConcurrentQueue is a concurrent collection that represents a FIFo collection" } t //producingSentences =true; Snippet5_14中的代码片段 运行消费者 Capitalize WordsInSentences和 RemoveLettersIn Sentences的任务会永远等待 下去。 Spin Wait SpinUntil方法中指定的委托永远都不会返回tmue。如果使用 Break All 1命令 中止执行,并且选择 Debug| Windows| Parallel tasks打开 Parallel Tasks窗口,可以看出运 行 Produce Sentences的任务的状态为 Running,而其他两个任务的状态都是 Waiting,如图 5-13所示。这里的问题在于这些任务都会永远等待下去,而主线程也会一直等待 Parallel Invoke方法结束。因此,这个应用程序永远都不会结束执行。 ne scheduled task maghi be missing. try estarbng the debug sesac u Locaton 幽醒 AecOm A Running Snippets_&, rogr arm. ProduceSente Main An 5144 Worker Threa 1(Snippet Snippets_& Program. RemeveLefter MainAn 55 Werker This 1 Snippet 2 Wanting 68.Pregram, Capitalize or Main An 5848(Worker Threa I (Snippet_ Parallel Invoke( (->ProduceSentences(), (=> CapitalizewordsInSentenceso) (=>RemoweLettersInSentences() 图5-13 189 c#井行编程高级教程:精通NET4 Parallel Extensions 您可以使用 Spin Wait. SpinUntil方法的另一个定义,这个定义可以在第2个参数中接收 要等待的毫秒数。这个方法会返回一个bool值,表示委托是否在指定时间内返回了true。 下面的代码是一个新版本的 Capitalize WordsIn Sentences方法,其中使用了一个超时,在5秒 钟后结束等待: //5,000 milliseconds =5 seconds timeout private const int TIMEOUT = 5000: 可从 wrox.comprivatestaticvoidCapitalizewordsinsentences() char [] delimiterChars =I t } // Start after Produce sentences began working // use a 5 seconds timeout if ( System Threading Spinwait. SpinUntil( producingsentences, TIMEOUT) throw new TimeoutException( string. Format( "CapitalizeNordsInSentences has been waiting t "for【o】 seconds t。 access sentences.", TIMEOUT))i Snippet515中的代码片段 在这个示例中, Spin Wait. Spin Until方法会等待5秒钟,等待 producing Sentences变为tue 当超过这个时间之后,这个方法就会返回 false。这段代码会抛出一个 TimeoutException异 常,调用这个方法的任务会因为错误而退出。这段代码需要一个取消机制,Main方法应该 检查任务的状态并且捕捉 Aggregate Exception异常,这些在之前的示例中都已经解释过了。 553自旋和处理器出让 之前的示例使用了 Spin Wait. Spin Until方法。您可以创建 Spin Wait实例,并且利用其 方法和属性实现基本的自旋功能,并且判断新的自旋是否会让出底层线程的时间片并触发 上下文切换。 Spin Wait实例还提供了以下两个方法 Reset-一重置自旋计数器。调用这个方法能够使得 Count属性变为0, Spin Wait会 重新开始自旋计数器,就好像没有对 Spin wait实例调用过 Spinonce方法一样。 SpinOnce-执行一次自旋。然而, Spin Wait实例执行一定次数的自旋之后,如有 必要,会让出底层线程的时间片并且触发一次上下文切换。因此, Spin Wait实例 的行为会根据已经执行的次数而发生改变 Spin Wait实例公开了两个只读属性: Count-提供 Spin Wait实例通过 SpinOnce方法执行单次自旋的次数。 190

...展开详情
立即下载
限时抽奖 低至0.43元/次
身份认证后 购VIP低至7折
一个资源只可评论一次,评论内容不能少于5个字
trly 非常好的资料,谢谢分享
2018-08-06
回复
zhengxiayuhexue 很不错,正在研读
2016-05-27
回复
MJ_dangerous MVC使用不错,正在研究MVC,希望可以帮到我
2016-05-10
回复
landongjun 非常好的资料,谢谢分享!
2016-03-22
回复
qinghdsndskf 很好的资料,值得学习。
2015-11-05
回复
公号:Java高级架构师 很好的一本书
2015-05-28
回复
ZhangGangHappy 学习并行编程的好资料
2015-01-06
回复
侠客久久 学习并行编程的好资料
2014-11-14
回复
appleandbin 不错,很好的书籍
2014-10-12
回复
yaoyaxian1985 谢谢分享,这个是第二部分,第一部分要10分
2014-09-22
回复
您会向同学/朋友/同事推荐我们的CSDN下载吗?
谢谢参与!您的真实评价是我们改进的动力~
  • 签到新秀

关注 私信
上传资源赚钱or赚积分
最新推荐
C#并行编程高级教程:精通.NET 4 Parallel Extensions中文(第2部分) 48积分/C币 立即下载
1/0