@@ -135,25 +135,25 @@ slowdown occurs):
135
135
136
136
在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定用它之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置替换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在必要的地方去使用它。
137
137
138
- 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解为多个部分,并在单独的处理器上运行每个部分。随着我们提高时钟速度的能力耗尽(至少对传统芯片而言),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使程序运行得更快,您必须学会利用那些额外的处理器 (译者注:处理器一般代表 CPU 的一个逻辑核心),这是并发所带来的好处之一。
138
+ 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解为多个部分,并在单独的处理器上运行每个部分。随着我们提高时钟速度的能力耗尽(至少对传统芯片而言),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使程序运行得更快,你必须学会利用那些额外的处理器 (译者注:处理器一般代表 CPU 的一个逻辑核心),这是并发所带来的好处之一。
139
139
140
140
对于多处理器机器,可以在这些处理器之间分配多个任务,这可以显著提高吞吐量。强大的多处理器 Web 服务器通常就是这种情况,它可以在程序中为 CPU 分配大量用户请求,每个请求分配一个线程。
141
141
142
- 但是,并发性通常可以提高在单个处理器上运行的程序的性能 。这听起来有点违反直觉。你会这么想 ,由于上下文切换的成本增加(从一个任务切换到另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。表面上看,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更低 。
142
+ 但是,并发通常可以提高在单处理器上运行的程序的性能 。这听起来有点违反直觉。如果你仔细想想 ,由于上下文切换的成本增加(从一个任务切换到另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。从表面上看,将程序的所有部分作为单个任务运行,并且节省上下文切换的成本,这样看似乎更划算 。
143
143
144
144
使这个问题变得有些不同的是阻塞。如果程序中的某个任务由于程序控制之外的某种情况而无法继续(通常是 I/O),我们就称该任务或线程已阻塞(在我们的科幻故事中,就是克隆人已经敲门并等待它打开)。如果没有并发,整个程序就会停下来,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻塞时,程序中的其他任务可以继续执行,因此整个程序得以继续运行。事实上,从性能的角度来看,如果没有任务会阻塞,那么在单处理器机器上使用并发是没有意义的。
145
145
146
146
单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些耗时操作,最终忽略用户输入导致无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中都检查它的状态(轮询)。这会产生笨拙的代码,也无法保证程序员不会忘了检查。没有并发,生成可响应用户界面的唯一方法是让所有任务都定期检查用户输入。通过创建单独的线程以执行用户输入的响应,能够让程序保证一定程度的响应能力。
147
147
148
- 实现并发的一种简单方式是使用操作系统级别的进程。与线程不同,进程是在其自己的地址空间中运行的独立程序。进程的优势在于,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程之间会共享内存和 I/O 等资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以免这些资源不会同时被多个资源访问 。
148
+ 实现并发的一种简单方式是使用操作系统级别的进程。与线程不同,进程是在其自己的地址空间中运行的独立程序。进程的优势在于,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程之间会共享内存和 I/O 等资源,因此编写多线程程序最基本的困难,在于协调不同线程驱动的任务之间对这些资源的使用,以免这些资源同时被多个任务访问 。
149
149
<!-- 文献引用未加,因为暂时没看到很好的解决办法 -->
150
150
有些人甚至提倡将进程作为唯一合理的并发实现方式[ ^ 1 ] ,但遗憾的是,通常存在数量和开销方面的限制,从而阻止了进程在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”)
151
151
152
- 一些编程语言被设计为将并发任务彼此隔离 。这些通常被称为_函数式语言_ ,其中每个函数调用不产生副作用(不会干扰到其它函数),所以可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果发现程序的某一部分必须大量使用并发,并且在尝试构建该部分时遇到了过多的问题,那么可以考虑使用这些专用的并发语言创建程序的这个部分。
152
+ 一些编程语言旨在将并发任务彼此隔离 。这些通常被称为_函数式语言_ ,其中每个函数调用不产生副作用(不会干扰到其它函数),所以可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果发现程序的某一部分必须大量使用并发,并且在尝试构建该部分时遇到了过多的问题,那么可以考虑使用这些专用的并发语言创建程序的这个部分。
153
153
<!-- 文献标记 -->
154
154
Java 采用了更传统的方法[ ^ 2 ] ,即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分叉外部进程,线程是在表示执行程序的单个进程内创建任务。
155
155
156
- 并发会带来各种成本,包括复杂性成本,但可以被程序设计, 资源平衡和用户便利性方面的改进所抵消。通常,并发性使你能够创建更低耦合的设计;另一方面,你必须特别关注那些使用了并发操作的代码。
156
+ 并发会带来各种成本,包括复杂性成本,但可以被程序设计、 资源平衡和用户便利性方面的改进所抵消。通常,并发性使你能够创建更低耦合的设计;另一方面,你必须特别关注那些使用了并发操作的代码。
157
157
158
158
<!-- The Four Maxims of Java Concurrency -->
159
159
## Java 并发的四句格言
@@ -181,13 +181,13 @@ Java 采用了更传统的方法[^2],即在顺序语言之上添加对线程
181
181
182
182
### 2.没有什么是真的,一切可能都有问题
183
183
184
- 不使用并发编程,你已经料到了你的世界具有确定的顺序和一致性 。对于变量赋值这样简单的操作,很明显它应该总是能够正常工作。
184
+ 不使用并发编程,你已经预料到你的世界具有确定的顺序和一致性 。对于变量赋值这样简单的操作,很明显它应该总是能够正常工作。
185
185
186
186
在并发领域,有些事情可能是真的而有些事情却不是,以至于你必须假设没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能不会按预期的方式工作,事情从这里开始迅速恶化。我已经熟悉了这样一种感觉:我认为应该明显奏效的东西,实际上却行不通。
187
187
188
- 所有在非并发编程中可以忽略的事情突然之间都因为并发而变得重要起来 。例如,你必须了解处理器缓存以及保持本地缓存与主内存一致等问题。您必须理解对象构造的深层复杂性,以便构造函数不会意外地将数据暴露给其他线程更改。诸如此类问题不胜枚举 。
188
+ 在非并发编程中你可以忽略的各种事情,在并发下突然变得很重要 。例如,你必须了解处理器缓存以及保持本地缓存与主内存一致的问题,你必须理解对象构造的深层复杂性,这样你的构造函数就不会意外地暴露数据,以致于被其它线程更改。这样的例子不胜枚举 。
189
189
190
- 因为这些主题太复杂,以至于本章无法为你提供更专业的知识(再次参见 Java Concurrency in Practice),但你必须意识到它们 。
190
+ 虽然这些主题过于复杂,无法在本章中给你提供专业知识(同样,请参见 Java Concurrency in Practice),但你必须了解它们 。
191
191
192
192
### 3.仅仅是它能运行,并不意味着它没有问题
193
193
0 commit comments