我目前正在审查/重构多线程应用程序,该应用程序应该是多线程的,以便能够使用所有可用内核并理论上提供更好/更优越的性能(优越的商业术语更好:P)< / p>
编写多线程应用程序时我应该注意哪些事项?
我的意思是会对性能产生很大影响的事情,甚至可能会让您在多线程中没有获得任何东西,但却因设计复杂性而失去了很多。多线程应用程序有哪些重要的危险信号?
我是否应该开始质疑锁定并寻找无锁策略,还是应该点亮警告灯还有其他更重要的点?
编辑:我想要的答案类似于Janusz的答案,我希望在代码中查找红色警告,我知道应用程序的性能不如它应该,我需要知道从哪里开始寻找,应该担心什么以及我应该在哪里努力。我知道这是一个普遍的问题,但我无法发布整个程序,如果我可以选择一段代码,那么我首先不需要问。
我正在使用Delphi 7,虽然该应用程序将在明年移植/重新制作.NET(c#),所以我宁愿听取适用于一般做法的评论,如果它们必须特定于这些语言中的任何一种
答案 0 :(得分:6)
要明确避免的一件事是对线程的相同缓存行进行大量写访问。
例如:如果使用计数器变量来计算所有线程处理的项目数,这将严重影响性能,因为只要其他CPU写入变量,CPU缓存行就必须同步。
答案 1 :(得分:5)
降低性能的一件事是拥有两个具有大量硬盘访问权限的线程。硬盘驱动器会从为一个线程提供数据跳转到另一个线程,并且两个线程都会一直等待磁盘。
答案 2 :(得分:5)
锁定时要记住的事项:锁定尽可能短的时间。例如,而不是:
lock(syncObject)
{
bool value = askSomeSharedResourceForSomeValue();
if (value)
DoSomethingIfTrue();
else
DoSomtehingIfFalse();
}
这样做(如果可能的话):
bool value = false;
lock(syncObject)
{
value = askSomeSharedResourceForSomeValue();
}
if (value)
DoSomethingIfTrue();
else
DoSomtehingIfFalse();
当然,此示例仅在DoSomethingIfTrue()
和DoSomethingIfFalse()
不需要同步时才有效,但它说明了这一点:锁定尽可能短的时间,但可能并不总能提高您的性能,将提高代码的安全性,因为它减少了同步问题的表面积。
在某些情况下,它会提高性能。长时间保持锁定意味着等待访问某些资源的其他线程将等待更长时间。
答案 3 :(得分:4)
更多线程然后有核心,通常意味着程序没有以最佳状态执行。
因此,产生大量线程的程序通常不是以最佳方式设计的。这种做法的一个很好的例子是经典的Socket示例,其中每个传入的连接都有自己的线程来处理连接。这是一种非常不可扩展的方法。线程越多,操作系统用于线程之间上下文切换的时间就越多。
答案 4 :(得分:3)
您应该首先熟悉Amdahl's law。
如果您使用的是Java,我推荐书籍Java Concurrency in Practice;但是,它的大多数帮助都是特定于Java语言(Java 5或更高版本)。
通常,减少共享内存量会增加可能的并行度,并且性能应该是主要考虑因素。
使用GUI进行线程处理是另一件需要注意的事情,但看起来它与此特定问题无关。
答案 5 :(得分:2)
当两个或多个线程共享相同的资源时,杀死性能的原因是什么。这可以是既使用的对象,也可以是既使用网络又使用网络的文件,也可以是两者都使用的处理器。您无法避免对共享资源的这些依赖性,但如果可能,请尽量避免共享资源。
答案 6 :(得分:1)
运行时分析器可能无法与多线程应用程序一起使用。但是,任何使单线程应用程序变慢的因素也会使多线程应用程序变慢。将应用程序作为单线程应用程序运行并使用分析器来查找其性能热点(瓶颈)的位置可能是一个想法。
当它作为多线程应用程序运行时,您可以使用系统的性能监视工具来查看锁是否有问题。假设您的线程将锁定而不是忙等待,那么为多个线程使用100%的CPU表明锁定不是问题。相反,在双处理器计算机上看起来总体CPU利用率为50%的情况表明只有一个线程在运行,因此锁定是一个阻止多个并发线程的问题(在计算CPU数量时)你的机器,要注意多核和超线程。)
锁不仅存在于您的代码中,还存储在您使用的API中:例如堆管理器(无论何时分配和删除内存),可能在您的记录器实现中,可能在某些O / S API中等。
我应该开始质疑锁定并寻求无锁策略
我总是质疑锁,但从未使用过无锁策略;相反,我的目标是在必要时使用锁,这样它始终是线程安全的,但永远不会死锁,并确保在很短的时间内获取锁(例如,不超过推动或弹出锁定所花费的时间。指向线程安全队列的指针),这样线程可能被阻塞的最长时间与执行有用工作所花费的时间相比是微不足道的。
答案 7 :(得分:1)
你没有提到你正在使用的语言,所以我将对锁定作一般性陈述。锁定相当昂贵,尤其是许多语言原生的天真锁定。在许多情况下,您正在阅读共享变量(而不是写入)。只要不与写入同时进行,读取就是线程安全的。但是,您仍然需要将其锁定。这种锁定最天真的形式是将读取和写入视为相同类型的操作,限制从其他读取和写入访问共享变量。读/写锁可以显着提高性能。一位作家,无限读者。在我所使用的应用程序上,当切换到此构造时,我看到了35%的性能提升。如果您使用的是.NET,则正确的锁定是ReaderWriterLockSlim。
答案 8 :(得分:1)
如果是服务器应用程序,我建议考虑在同一进程中运行多个进程而不是多个线程。
在一台计算机上划分多个进程之间的工作的好处是,当需要比单个服务器提供更多性能时,很容易增加服务器数量。
您还可以降低复杂多线程应用程序所涉及的风险,因为死锁,瓶颈等会降低总体性能。
在负载平衡和分布式队列处理方面,存在简化服务器软件开发的商业框架,但与多线程应用程序中常见的情况相比,开发自己的负载共享基础架构并不复杂。 / p>
答案 9 :(得分:1)
我正在使用Delphi 7
您可能正在使用COM对象,显式或隐式;如果你是,COM对象有自己的并发症和线程限制:Processes, Threads, and Apartments。
答案 10 :(得分:0)
首先应该有一个工具来监视特定于您的语言,框架和IDE的线程。你自己的记录器也可以做得很好(恢复时间,睡眠时间+持续时间)。从那里你可以检查执行不良的线程,这些线程没有执行太多或者等待太长时间才能发生某些事情,你可能想要让它们等待的事件尽早发生。
如果您想使用这两个内核,您应该使用一个工具来检查内核的使用情况,该工具可以仅为您的应用程序绘制两个内核上的处理器使用情况,或者只是确保您的计算机尽可能空闲。
除此之外,您应该对应用程序进行概要分析,以确保在线程中执行的操作是有效的,但请注意过早优化。如果线程本身表现不好,则无法优化多处理。
寻找无锁策略可以提供很多帮助,但并不总能让您的应用程序以无锁方式执行。
答案 11 :(得分:0)
线程总是与性能不同。
在某些操作系统中,与其他操作系统相比,事情要好得多,但是如果你可以睡觉或放弃它的时间直到它发出信号......或者没有为几乎所有事情开始一个新的过程,那么你就可以避免在上下文切换中阻塞应用程序。