可重复使用的并行数据结构与算法
执行效率优化技术:提升程序运行速度与响应时间

执行效率优化技术:提升程序运行速度与响应时间引言在当今数字时代,快速的程序运行和响应时间对于软件和应用程序的用户体验至关重要。
无论是在电子商务、金融、游戏还是其他领域,用户对于快速的反馈和流畅的操作已经成为常态。
因此,对于开发人员来说,优化代码的执行效率是至关重要的任务。
本文将介绍一些常用的执行效率优化技术,以帮助开发人员提升程序的运行速度和响应时间。
1. 选择合适的算法和数据结构算法和数据结构是程序效率的基础。
选择合适的算法和数据结构可以大大减少程序的执行时间。
开发人员应该根据具体的应用需求和数据规模选择最适合的算法和数据结构。
例如,如果需要频繁搜索和插入操作的情况下,使用散列表(hash table)可能比使用数组更高效。
2. 优化循环和迭代循环和迭代是程序中最常见的操作之一。
优化循环和迭代过程可以显著提高程序的执行效率。
以下是一些优化循环和迭代的技巧:a) 减少循环次数尽量减少循环的次数,可以通过以下几种方式实现: - 使用更有效的循环条件- 使用合适的数据结构和算法 - 避免不必要的重复计算b) 局部变量优化在循环和迭代过程中,避免在循环体内频繁声明和初始化变量。
将局部变量的声明和初始化放在循环体外部,可以减少不必要的开销。
c) 循环展开循环展开是一种将循环展开为多个重复的操作的技术。
这样可以减少循环次数,从而提高程序的执行效率。
然而,循环展开的效果取决于具体的应用场景和硬件环境。
开发人员应根据实际情况进行权衡和测试。
3. 缓存优化缓存是用于存储频繁访问数据的高速存储器。
合理利用缓存可以减少访问主存的次数,从而提高程序的执行效率。
以下是一些缓存优化的技巧:a) 数据局部性数据局部性是指程序中访问数据的特点。
根据数据的访问模式,可以将频繁访问的数据放在缓存中,从而减少访问主存的次数。
例如,使用局部变量来存储频繁访问的数据,可以有效地利用缓存。
b) 数据对齐对齐数据可以使缓存访问更加高效。
在一些体系结构中,访问未对齐的数据需要额外的开销。
《高性能并行运行时系统:设计与实现》随笔

《高性能并行运行时系统:设计与实现》读书随笔目录一、内容综述 (2)1.1 背景与动机 (3)1.2 高性能并行运行时系统的意义 (3)二、并行运行时系统的基本概念 (4)2.1 并行计算与并行运行时系统 (6)2.2 并行运行时系统的组成部分 (7)三、高性能并行运行时系统的设计要素 (9)3.1 性能优化策略 (10)3.2 可扩展性与可维护性 (12)3.3 容错与稳定性 (13)四、典型高性能并行运行时系统分析 (15)4.1 MapReduce及其应用场景 (16)4.2 Spark的工作原理与应用 (17)4.3 分布式内存计算系统TBB (19)五、并行运行时系统的实现技术 (21)5.1 编程模型与语言支持 (22)5.2 数据存储与管理 (24)5.3 网络通信与通信协议 (25)六、高性能并行运行时系统的测试与调试 (27)6.1 测试方法与工具 (28)6.2 常见问题与解决方案 (30)七、总结与展望 (31)7.1 本书主要内容回顾 (32)7.2 对未来发展的展望 (34)一、内容综述《高性能并行运行时系统:设计与实现》是一本关于高性能并行计算的经典著作,作者是著名的计算机科学家和教授。
本书详细介绍了高性能并行运行时系统的设计与实现过程,旨在为读者提供一套完整的理论框架和技术方法,以便在实际项目中构建高效、可扩展的并行计算系统。
本书共分为五个部分,分别是:并行计算基础、并行编程模型、并行数据结构与算法、并行运行时系统设计及实例分析和总结。
在前三部分中,作者首先介绍了并行计算的基本概念、原理和技术,包括共享内存模型、消息传递接口(MPI)等;接着详细讲解了并行编程模型,如任务划分、同步与互斥、负载均衡等;作者还介绍了一些常用的并行数据结构与算法,如哈希表、B树、红黑树等。
在第四部分中,作者深入探讨了并行运行时系统的设计与实现,包括线程管理、进程管理、资源分配等方面。
通过一系列实例分析,作者展示了如何根据具体问题选择合适的并行计算模型和编程技术,以及如何在实际项目中实现高效的并行运行时系统。
LabVIEW编程中的数据结构与算法优化技巧

LabVIEW编程中的数据结构与算法优化技巧在LabVIEW编程中,数据结构和算法的选择与优化对于程序的性能和可维护性至关重要。
本文将介绍在LabVIEW编程中常用的数据结构和算法优化技巧,帮助开发人员提高程序的效率和可靠性。
一、数据结构的选择在LabVIEW编程中,选择合适的数据结构是实现功能的关键。
以下是几种常见的数据结构及其适用场景:1. 数组(Array):用于存储同类型的数据,并且数据的大小是固定的。
数组适用于需要按顺序访问和操作数据的场景,例如存储一组测量数据或图像像素。
2. 队列(Queue):用于实现先进先出(FIFO)的数据存储和访问方式。
队列适用于需要按顺序处理数据的场景,例如数据采集和处理时的数据缓存。
3. 栈(Stack):用于实现后进先出(LIFO)的数据存储和访问方式。
栈适用于需要按相反顺序处理数据的场景,例如函数调用的递归操作。
4. 链表(Linked List):用于存储具有动态长度的数据。
链表适用于频繁插入和删除数据的场景,例如数据缓存和排序等算法。
5. 图(Graph):用于表示多个实体之间的关系,并且这些关系保存在边中。
图适用于复杂网络分析和路径搜索等算法。
在选择数据结构时,需要考虑数据的特性、访问方式和操作需求,以及程序的性能要求等因素,综合评估后选择最合适的数据结构。
二、算法的优化除了选择合适的数据结构之外,优化算法也是提高LabVIEW程序性能的重要手段。
下面是几个常见的算法优化技巧:1. 减少循环次数:循环是LabVIEW程序中常用的操作,但过多的循环会增加程序的执行时间。
在编写程序时,应尽量减少循环次数,例如通过向量化操作或者使用矩阵运算来代替循环运算。
2. 缓存数据:对于需要频繁访问的数据,可以将其存储在缓存中,以减少对内存的访问次数。
例如使用Shift Register或者Local Variable来保存中间计算结果,避免重复计算。
3. 并行计算:LabVIEW支持并行计算,在多核处理器上可以充分利用硬件资源,提高程序的执行效率。
C语言技术的使用方法和实际场景应用分享

C语言技术的使用方法和实际场景应用分享C语言作为一种广泛应用于系统软件开发和嵌入式系统的编程语言,具有高效、灵活和可移植等特点。
在软件开发领域,C语言技术的使用方法和实际场景应用非常丰富。
本文将分享一些常见的C语言技术使用方法和实际场景应用,希望能够对读者有所启发。
一、内存管理技术内存管理是C语言中一个非常重要的技术,它直接影响到程序的性能和稳定性。
在C语言中,我们可以使用动态内存分配函数malloc和free来进行内存管理。
动态内存分配可以根据程序的需要在运行时分配和释放内存,从而提高内存的利用率。
实际场景中,动态内存分配常用于数据结构的动态扩展,如链表、树等。
通过合理地使用malloc和free函数,我们可以在程序运行过程中根据需要动态调整内存大小,提高程序的灵活性和效率。
二、文件操作技术文件操作是C语言中常用的一项技术,它可以实现对文件的读写操作。
在实际应用中,我们经常需要读取和处理外部文件中的数据。
C语言提供了一系列的文件操作函数,如fopen、fclose、fread、fwrite等,可以方便地进行文件的打开、关闭、读取和写入等操作。
例如,在图像处理领域,我们可以使用C语言读取图像文件中的像素数据,并进行各种处理,如滤波、边缘检测等。
文件操作技术的灵活运用可以帮助我们处理各种类型的数据,并实现更复杂的功能。
三、多线程编程技术多线程编程是C语言中的一项重要技术,它可以实现程序的并发执行,提高程序的执行效率。
在现代计算机系统中,多核处理器已经成为主流,多线程编程可以充分利用多核处理器的性能优势。
C语言提供了一些多线程编程的库函数,如pthread_create、pthread_join等,可以方便地创建和管理线程。
实际场景中,多线程编程常用于网络通信、并行计算等领域。
例如,在网络服务器开发中,我们可以使用多线程编程技术实现并发处理客户端请求,提高服务器的吞吐量和响应速度。
四、数据结构与算法数据结构与算法是计算机科学中的核心内容,也是C语言技术的重要应用领域。
Java大规模数据处理解析海量数据的技巧

Java大规模数据处理解析海量数据的技巧在处理大规模数据时,Java是一种常用的编程语言。
然而,由于海量数据的处理可能涉及到效率、内存管理以及算法优化等方面的挑战,开发人员需要掌握一些技巧来解析这些数据。
本文将介绍一些Java大规模数据处理的技巧,帮助开发人员更好地处理海量数据。
一、数据分块处理在处理大规模数据时,内存管理是一个重要的问题。
当数据量超过内存限制时,我们需要将数据分块处理,以避免内存溢出。
可以使用Java的流式处理机制,通过迭代的方式读取数据,每次处理一块数据,减少内存的消耗。
例如,可以使用BufferedReader的readLine()方法逐行读取文件,然后对每行数据进行处理。
二、并行处理并行处理是指同时处理多个数据块的技术,可以显著提高处理大规模数据的效率。
Java提供了多线程和线程池的机制,可以将数据分成多个部分,并行地处理每个部分。
通过合理设置线程池的大小,可以充分利用计算资源,提高程序的运行效率。
三、使用适当的数据结构在处理大规模数据时,选择适当的数据结构非常重要。
不同的数据结构对于不同的操作具有不同的时间复杂度,选择合适的数据结构可以提高程序的效率。
例如,如果需要频繁地插入和删除数据,可以选择链表或树等数据结构;如果需要随机访问数据,可以选择数组或哈希表等数据结构。
根据不同的需求,选择合适的数据结构可以提高程序的性能。
四、优化算法算法的选择也是解析海量数据的关键。
优化算法可以提高程序的效率,减少资源的消耗。
例如,对于排序操作,可以选择高效的排序算法,如快速排序或归并排序,而不是简单的冒泡排序。
另外,可以使用适当的数据结构和算法来进行数据过滤、去重等操作,减少不必要的计算。
五、使用缓存缓存是提高程序性能的有效方式之一。
当程序需要频繁地访问某些数据时,可以使用缓存将这些数据存储起来,避免重复计算和访问。
在Java中,可以使用HashMap等数据结构来实现缓存。
通过在内存中存储一部分数据,可以提高程序的响应速度和效率。
高性能计算导论:并行计算性能评价

如流水线技术、分治算法等,通过将任务划分为多个子任 务,分配给不同的处理单元并行执行,从而实现任务的快 速完成。
消息传递并行算法
如MPI(Message Passing Interface)算法,通过进程 间通信来协调不同处理单元上的任务执行,适用于分布式 内存系统。
算法优化策略与方法探讨
结果分析和改进建议
结果分析
对实验结果进行深入分析,找出性能 瓶颈和影响性能的关键因素。
改进建议
根据分析结果提出针对性的改进建议,如优 化算法、改进系统结构、提高硬件性能等。 同时,也可以对实验方法和流程进行反思和 改进,以提高评估的准确性和有效性。
05 案例分析:并行计算性能 评价实践
案例背景和目标设定
加速比
并行算法相对于串行算法 的执行速度提升倍数。
效率
用于衡量并行系统中处理 器利用率的指标,通常表 示为加速比与处理器数量 的比值。
可扩展性与规模性指标
1 2
等效性
在增加处理器数量时,保持问题规模和计算复杂 度不变的情况下,系统性能的提升能力。
弱可扩展性
在增加处理器数量的同时,增加问题规模,保持 每个处理器的负载不变,系统性能的提升能力。
功耗与能效比指标
功耗
01
并行计算系统在运行过程中的总功率消耗。
能效比
02
用于衡量并行计算系统每消耗一单位能量所能完成的计算量或
任务量的指标。
节能技术
03
采用低功耗处理器、动态电压频率调整、节能算法等技术降低
并行计算系统的功耗。
03 并行算法设计与优化策略
典型并行算法介绍及原理剖析
数据并行算法
如数组运算、矩阵乘法等,通过将数据划分为多个部分, 在多个处理单元上并行执行相同的操作来提高性能。
数据结构与算法在互联网项目中的应用
数据结构与算法在互联网项目中的应用随着互联网的快速发展,各种互联网项目如雨后春笋般涌现,而数据结构与算法作为计算机科学的基础,在互联网项目中扮演着至关重要的角色。
本文将探讨数据结构与算法在互联网项目中的应用,以及它们对项目性能和效率的重要性。
一、数据结构在互联网项目中的应用1. 数组(Array)数组是最基本的数据结构之一,它在互联网项目中被广泛应用。
比如在网站开发中,我们经常需要处理一系列的数据,比如用户信息、商品信息等。
而数组正是最适合存储这类数据的数据结构。
通过数组,我们可以高效地访问和操作这些数据,提高网站的性能和用户体验。
2. 链表(Linked List)链表是另一种常见的数据结构,在互联网项目中也有着重要的应用。
比如在实现消息队列、缓存等功能时,链表可以帮助我们高效地管理数据,实现快速的插入和删除操作。
此外,链表还可以用于实现一些高级数据结构,如栈和队列,为互联网项目的开发提供了便利。
3. 栈(Stack)和队列(Queue)栈和队列是两种基本的数据结构,它们在互联网项目中也有着广泛的应用。
比如在实现搜索功能时,我们可以利用栈来实现深度优先搜索,利用队列来实现广度优先搜索。
此外,在处理请求和消息时,队列也可以帮助我们实现异步处理,提高系统的并发能力。
4. 哈希表(Hash Table)哈希表是一种高效的数据结构,它在互联网项目中被广泛用于实现缓存、索引等功能。
通过哈希表,我们可以快速地查找和更新数据,提高系统的响应速度。
此外,哈希表还可以帮助我们解决一些复杂的计算问题,如查找重复元素、计算频率等。
5. 树(Tree)和图(Graph)树和图是两种复杂的数据结构,它们在互联网项目中也有着重要的应用。
比如在实现社交网络、推荐系统等功能时,我们可以利用树和图来建立用户关系、物品关系等模型,为用户提供个性化的推荐服务。
此外,在处理复杂的算法问题时,树和图也可以帮助我们高效地解决各种挑战。
二、算法在互联网项目中的应用1. 搜索算法搜索算法是互联网项目中常用的算法之一,它可以帮助我们高效地查找和定位数据。
如何通过超级计算技术的参数调优与性能优化方法提高计算效率与任务完成速度
如何通过超级计算技术的参数调优与性能优化方法提高计算效率与任务完成速度随着计算机技术的不断发展,超级计算机已经成为科学研究、工程设计和商业决策等领域中不可或缺的工具。
然而,由于计算复杂度的增加和资源限制的限制,提高超级计算机的计算效率和任务完成速度成为一个重要的挑战。
本文将介绍如何通过超级计算技术的参数调优与性能优化方法来提高计算效率和任务完成速度。
在超级计算领域,参数调优是提高计算效率和任务完成速度的一个关键步骤。
参数调优是指通过调整超级计算机的配置参数,以优化计算过程中的性能和效率。
首先,我们需要了解超级计算机的性能参数,例如处理器频率、内存大小、磁盘带宽等。
然后,通过调整这些参数,达到提高计算效率和任务完成速度的目的。
一种常见的参数调优方法是并行计算。
在并行计算中,任务被划分为多个子任务,并在不同的处理器上同时进行计算。
这样可以充分利用超级计算机的多个处理器,从而提高计算效率和任务完成速度。
例如,可以将一个复杂的计算问题划分成多个小问题,并在不同的处理器上并行求解,然后再将结果进行合并。
这样,不仅可以提高计算速度,还可以充分利用超级计算机的资源。
除了并行计算,还有一些性能优化方法可以提高超级计算机的计算效率和任务完成速度。
一种常见的方法是使用高效的算法和数据结构。
通过选择合适的算法和数据结构,可以减少计算步骤和存储需求,从而提高计算效率。
例如,可以使用快速排序算法代替冒泡排序算法,使用哈希表代替线性查找。
这些优化方法可以大大减少计算时间和资源消耗。
另外,合理使用缓存机制也是提高计算效率和任务完成速度的重要方法。
超级计算机通常具有较大的缓存,可以存储和重复使用计算过程中的中间结果。
通过将中间结果存储在缓存中,可以避免重复计算,并减少对内存和磁盘的访问次数,从而提高计算效率和任务完成速度。
同时,合理的缓存设计也可以降低内存带宽和存储需求,从而节省资源,提高计算效率。
除了参数调优和性能优化方法,还有一些其他技术可以提高超级计算机的计算效率和任务完成速度。
数据结构与算法 试题及答案
数据结构与算法试题及答案数据结构与算法试题及答案在计算机科学领域,数据结构与算法是非常重要的基础知识。
数据结构是一种组织和存储数据的方式,而算法则是解决问题的方法和步骤。
掌握好数据结构与算法,有助于提高程序的运行效率和解决实际问题。
下面是一些关于数据结构与算法的试题及其答案,希望能够帮助大家更好地理解和应用这方面的知识。
试题一:什么是数据结构?请举例说明。
答案一:数据结构是一种组织和存储数据的方式。
它可以使数据的操作更加高效。
常见的数据结构有数组、链表、栈、队列、树和图等。
举个例子,数组是一种线性数据结构,可以存储一组相同类型的元素。
试题二:什么是算法?请举例说明。
答案二:算法是一种解决问题的方法和步骤。
它是一个精确的描述,用于解决特定问题。
常见的算法有排序算法、查找算法、递归算法等。
例如,冒泡排序算法是一种比较简单的排序算法,通过不断交换相邻元素的位置来达到排序的目的。
试题三:什么是时间复杂度和空间复杂度?答案三:时间复杂度和空间复杂度是衡量算法性能的两个指标。
时间复杂度是指算法执行所需要的时间,通常用大O符号表示。
空间复杂度是指算法执行所需要的额外空间,通常也用大O符号表示。
它们都是描述算法随着输入规模增大而变化的趋势。
试题四:介绍一下常见的数据结构和相应的操作。
答案四:常见的数据结构有数组、链表、栈、队列、树和图等。
- 数组是一种线性数据结构,可以随机访问元素,并且在插入和删除元素时需要移动其他元素。
- 链表是一种动态数据结构,不需要固定的内存空间,但只能通过指针进行元素的访问。
- 栈是一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除元素的操作。
- 队列是一种先进先出(FIFO)的数据结构,只能在队尾插入元素,在队头删除元素。
- 树是一种非线性数据结构,由节点和指向子节点的边组成。
常见的树有二叉树、二叉搜索树和AVL树等。
- 图是一种复杂的数据结构,由节点和边组成,可以表示各种关系。
数据结构与算法 c语言描述
数据结构与算法 c语言描述
《数据结构与算法C语言描述》介绍一种将算法和数据结构与C 语言结合,实现更有效率的程序开发方式。
C语言作为一门老牌的编程语言,一直被用于许多应用开发。
C
语言的优势在于它的简洁精炼,能够尽可能地利用有限的计算资源,而又不失程序的可重复性。
它有利于程序员们清楚地表明自己的意图以及程序的完成形式。
然而,C语言在编写算法和数据结构时,不易操作性较高,容易造成错误,且不利于程序的可扩展性。
因此,需要对C语言进行进一步的改进,以便将C语言的灵活性与算法和数据结构的高效率相结合。
《数据结构与算法 C语言描述》就是这样一本著作,为C语言
开发者提供了一套有效的解决方案。
通过在C语言中实现一系列的函数和抽象数据结构,能够有效地解决算法与数据结构的应用问题,并为程序开发提供了更高效的编程工具。
此外,《数据结构与算法 C语言描述》还通过具体的实例来说明如何将C语言用于复杂的算法和数据结构。
比如,可以使用C语言来实现快速排序、冒泡排序、二分查找、图的遍历算法等。
此外,书中还介绍了如何使用C语言构建复杂的数据结构,比如堆、二叉搜索树、哈希表、跳表等。
最后,《数据结构与算法 C语言描述》的作者还介绍了一些先进的计算机科学技术,这些技术包括串行化技术、内存管理技术、网络编程技术、模型和并行计算技术等。
这些技术可以帮助开发者更有效
地开发出高性能的程序。
总而言之,《数据结构与算法 C语言描述》旨在帮助C语言开发者有效地实现算法和数据结构,将C语言的高效性与算法与数据结构的有效性相结合。
它给C语言开发者提供了帮助,使他们能够更高效地开发出性能优越的应用程序。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
9 种可重复使用的并行数据结构和算法目录倒计数锁存(Countdown Latch)可重用旋转等待(Spin Wait)屏障(Barrier)阻塞队列受限缓冲区(Bounded Buffer)Thin 事件无锁定LIFO 堆栈循环分块并行分拆总结本专栏并未涉及很多公共语言运行库(CLR) 功能的机制问题,而是更多介绍了如何有效使用您手头所具有的工具。
身为一名程序员,必须做出很多决策,而选择正确的数据结构和算法无疑是最常见的,也是最重要的决策之一。
错误的选择可能导致程序无法运行,而大多数情况下,则决定了性能的好坏。
鉴于并行编程通常旨在改进性能,并且要难于串行编程,因此所作的选择对您程序的成功就更为重要。
在本专栏中,我们将介绍九种可重复使用的数据结构和算法,这些结构和算法是许多并行程序所常用的,您应该能够轻松将它们应用到自己的.NET 软件中。
专栏中每个示例随附的代码都是可用的,但尚未经过完全定型、测试和优化。
这里列举的模式虽然并不详尽,但却代表了一些较为常见的模式。
如您所见,很多示例都是互为补充的。
在开始前,我想还是先介绍一些相关内容。
Microsoft® .NET Framework 提供了几个现有的并发基元。
虽然我要为您讲解如何构建自己的基元,但实际上现有基元是足以应付大多数情况的。
我只是想说某些可选的方案有时也是有参考价值的。
此外,了解这些技巧如何应用于实际操作也有助于加深您对并行编程的整体理解。
在开始讲解前,我假定您对现有基元已经有了一个基本的了解。
您也可以参阅《MSDN® 杂志》2005 年8 月版的文章“关于多线程应用程序:每个开发人员都应了解的内容”,以全面了解其概念。
一、倒计数锁存(Countdown Latch)Semaphore 之所以成为并发编程中一种较为知名的数据结构,原因是多方面的,而并不只是因为它在计算机科学领域有着悠久的历史(可以追溯到19 世纪60 年代的操作系统设计)。
Semaphore 只是一种带有一个计数字段的数据结构,它只支持两种操作:放置和取走(通常分别称为P 和V)。
一次放置操作会增加一个semaphore 计数,而一次取走操作会减少一个semaphore 计数。
当semaphore 计数为零时,除非执行一项并发的放置操作使计数变为非零值,否则任何后续的取走尝试都将阻塞(等待)。
这两种操作均为不可再分(atomic) 操作,并发时不会产生错误,能够确保并发的放置和取走操作有序地进行。
Windows 具有基础内核和对semaphore 对象的Win32支持(请参阅CreateSemaphore 和相关API),并且在.NET Framework 中这些对象可以通过System.Threading.Semaphore 类公开到上层。
Mutex 和Monitor 所支持的临界区,通常被认为是一种特殊的semaphore,其计数会在0 和1 之间来回切换,换句话说,是一个二进制的semaphore。
另外还有一种“反向semaphore”也是非常有用。
也就是说,有时您需要数据结构能够等待数据结构计数归零。
Fork/join 式并行模式在数据并行编程中是极为常见的,其中由单个“主”线程控制执行若干“辅助”线程并等待线程执行完毕。
在这类情况下,使用反向semaphore 会很有帮助。
大多数时候,您其实并不想唤醒线程来修改计数。
因此在这种情况下,我们将结构称为倒计数“锁存”,用来表示计数的减少,同时还表明一旦设置为“Signaled”状态,锁存将保持“signaled”(这是一个与锁存相关的属性)。
遗憾的是,Windows 和.NET Framework 均不支持这种数据结构。
但令人欣慰的是,构建这种数据闭锁并不难。
要构建倒计数锁存,只需将其计数器初始值设为n,并让每项辅助任务在完成时不可再分地将n 减掉一个计数,这可以通过为减量操作加上“锁”或调用Interlocked.Decrement 来实现。
接下来,线程可以不执行取走操作,而是减少计数并等待计数器归零;而当线程被唤醒时,它就可以得知已经有n 个信号向锁存注册。
在while (count != 0) 循环中,让等待的线程阻塞通常是不错的选择(这种情况下,您稍后将不得不使用事件),而不是使用旋转。
public class CountdownLatch {private int m_remain;private EventWaitHandle m_event;public CountdownLatch(int count) {m_remain = count;m_event = new ManualResetEvent(false);}public void Signal(){// The last thread to signal also sets the event.if (Interlocked.Decrement(ref m_remain) == 0)m_event.Set();}public void Wait() {m_event.WaitOne();}}这看上去极为简单,但要正确运用还需要技巧。
稍后我们将通过一些示例来讲解如何使用这种数据结构。
请注意,此处所示基本实现还有很多可以改进地方,例如:在事件上调用WaitOne 之前添加某种程度的旋转等待、缓慢分配事件而不是在构造器中进行分配(以防足够的旋转会避免出现阻塞,如本专栏稍后介绍的ThinEvent 演示的那样)、添加重置功能以及提供Dispose 方法(以便在不再需要内部事件对象时将对象关闭)。
二、可重用旋转等待(Spin Wait)虽然忙碌等待(busy waiting) 更容易实现阻塞,但在某些情况下,您也许的确想在退回到真正的等待状态前先旋转(spin) 一段时间。
我们很难理解为何这样做会有帮助,而大多数人之所以一开始就避免旋转等待,是因为旋转看上去像是在做无用功;如果上下文切换(每当线程等待内核事件时都会发生)需要几千个周期(在Windows 上确实是这样),我们称之为c,并且线程所等待的条件出现的时间少于2c 周期时间(1c 用于等待自身,1c 用于唤醒),则旋转可以降低等待所造成的系统开销和滞后时间,从而提升算法的整体吞吐量和可伸缩性。
如果您决定使用旋转等待,就必须谨慎行事。
因为如果这样做,您可能需要注意很多问题,比如:要确保在旋转循环内调用Thread.SpinWait,以提高Intel 超线程技术的计算机上硬件对其他硬件线程的可用性;偶尔使用参数1 而非0 来调用Thread.Sleep,以避免优先级反向问题;通过轻微的回退(back-off) 来引入随机选择,从而改善访问的局部性(假定调用方持续重读共享状态)并可能避免活锁;当然,在单CPU 的计算机最好不要采用这种方法(因为在这种环境下旋转是非常浪费资源的)。
SpinWait 类需要被定义为值类型,以便分配起来更加节省资源。
现在,我们可以使用此算法来避免前述CountdownLatch 算法中出现的阻塞。
public struct SpinWait {private int m_count;private static readonly bool s_isSingleProc =(Environment.ProcessorCount == 1);private const int s_yieldFrequency = 4000;private const int s_yieldOneFrequency = 3*s_yieldFrequency;public int Spin() {int oldCount = m_count;// On a single-CPU machine, we ensure our counter is always// a multiple of ‘s_yieldFrequency’, so we yield every time.// Else, we just increment by one.m_count += (s_isSingleProc ? s_yieldFrequency : 1);// If not a multiple of ‘s_yieldFrequency’ spin (w/ backoff).int countModFrequency = m_count % s_yieldFrequency;if (countModFrequency > 0)Thread.SpinWait((int)(1 + (countModFrequency * 0.05f)));elseThread.Sleep(m_count <= s_yieldOneFrequency ? 0 : 1);return oldCount;}private void Yield(){Thread.Sleep(m_count < s_yieldOneFrequency ? 0 : 1);}}private const int s_spinCount = 4000;public void Wait(){SpinWait s = new SpinWait();while (m_remain > 0){if (s.Spin() >= s_spinCount) m_event.WaitOne();}}不可否认,选择频率和旋转计数是不确定的。
与Win32 临界区旋转计数类似,我们应该根据测试和实验的结果来选择合理的数值,而且即使合理的数值在不同系统中也会发生变化。
例如,根据Microsoft Media Center 和Windows kernel 团队的经验,MSDN 文档建议临界区旋转计数为4,000 ,但您的选择可以有所不同。
理想的计数取决于多种因素,包括在给定时间等待事件的线程数和事件出现的频率等。
大多数情况下,您会希望通过等待事件来消除显式让出时间,如锁存的示例中所示。
您甚至可以选择动态调整计数:例如,从中等数量的旋转开始,每次旋转失败就增加计数。
一旦计数达到预定的最大值,就完全停止旋转并立即发出WaitOne。
逻辑如下所示:您希望立即增加达到预定的最大周期数,但却无法超过最大周期数。
如果您发现此最大值不足以阻止上下文切换,那么立即执行上下文切换总的算来占用的资源更少。
慢慢您就会希望旋转计数能够达到一个稳定的值。
三、屏障(Barrier)屏障,又称集合点,是一种并发性基元,它无需另一“主”线程控制即可实现各线程之间简单的互相协调。
每个线程在到达屏障时都会不可再分地发出信号并等待。