Welcome

首页 / 软件开发 / .NET编程技术 / 并发数据结构: SpinWait

并发数据结构: SpinWait2011-08-07 博客园 Angel Lucifer老实说,没有哪个开发人员愿意在其编码时还要考虑线程同步。更糟糕的情况是,编写线程同步代码 一点也不好玩。稍一不慎,就会导致共享资源状态不一致,从而引发程序未预期行为。此外,当我们添加 线程同步代码时还会导致程序运行变慢,损害性能和可伸缩性。从这点上来看,线程同步简直一无是处。 可惜,这也是现实生活中必要的一部分。尤其在多核CPU成为主流的今天。

考虑下这种情况:只有一个线程试图访问某个资源。在此种状况下,那些线程同步代码纯粹是额外开 销。那么如何保护性能呢?

首先,不要让线程从用户模式转到内核模式。在Windows上,这是个非常昂贵的操作。比如大家熟知的 Critical Section(在.NET上我们叫它Monitor)在等待持有锁时,便在用户模式先旋转等待一段时间,实 在不行才转入内核模式。

其次,当线程进入等待状态时,它便停止运行。很显然,停止运行的线程还有啥性能可言。所幸的是 ,假如线程进入内核模式,它也就只能进入等待状态。所以,如果你遵守第一条规则(避免进入内核),那 么第二条规则(不要让线程进入等待状态)将会自动遵守。我们应当让线程大多数时间在运行而不是在等待 。

最后,尽可能用尽量少的线程来完成任务。最理想的情况是,从不让存在的线程数多于你的计算机上 的CPU个数。因此,当你的机器只有四个CPU的时候,最好也不要超过四个线程的数量。为什么?因为当超 过四个线程的时候,操作系统就会以每隔几毫秒~几十毫秒的频率调度线程到CPU。在Windows上,这个数 字大概是20毫秒。线程上下文切换花费不少时间,而且这纯粹是额外开销。在Windows上,线程上下文切 换需要几千个时钟周期。如果进行切换的线程属于另一个进程,开销还会更大,因为操作系统还要切换虚 拟地址空间。如果可运行的线程数量等于或小于CPU数量的话,操作系统只运行这些线程,而不会发生上 下文切换。在这种情况下,当然性能最佳。

但是,这仅在理想的世界中才可行。所以,我们通常可以看到机器上存在的线程数量远多于CPU数量。 事实上,在我的单核CPU机器上,目前就有378个线程活动,这跟理想情况差了岂止十万八千里。Windows 上每个进程都有自己的线程,这主要是为了进程彼此隔离。假如一个进程进入死循环,其他进程还可以继 续执行。现在我的机器上运行着29个进程,也就是说,至少有29个线程在运行了。那剩下的349个线程呢 ?很多程序通常还会用多线程来进行隔离。比如,CLR就专门有一个GC线程来负责回收资源,而不管其他 线程在干什么。

当然,还有其他众多原因。其中一个比较重要的原因就是相当多的(或者我大胆的说绝大多数)开发人 员并不知道如何有效的使用线程。多年来,我们从各种知识资源(甚至不少讲解多线程的教材)上学习到的 编程方式,实际上对构建高性能,高可伸缩性程序有害。比如在《Windows System Programming, Third Edition》中,作者多处提到他偏爱使用线程而不是系统提供的异步方式。很明显的一个例子就是作者采 用线程编写的同步I/O API要快于系统异步I/O API。虽然异步和线程有着很亲密的关系,但我们得到的暗 示是鼓励使用线程而不是异步。考虑下当有大量线程争用I/O时的情景:我们的CPU忙着线程上下文切换, 磁盘的磁头数量有限,不够这些线程来回捣腾,而每一个I/O操作却又耗时良多。再比如MSDN上Microsoft 提供的Windows API示例。它们大多数均使用顺序思维编写。大多数情况下这无可厚非,而且符合人类直 觉。然而这却让我们学习模仿,甚至变成了习惯。在并行程序设计上相当有害。

如果你发现思考下列问题,那么很可能你错误的架构了你的程序。而且需要重新架构它。

Windows支持的最大线程数是多少?

在一个单CPU上,我能最大创建多少线程?

在线程池中,我能最大拥有多少线程?