Go语言并发编程,进入并发编程,并发编程原理和相关术语介绍

概述

Meet Jane Sutton. Jane has been working at HSS International Accountancy as a software developer for three months. In her latest project, she has been looking at a problem in the payroll system. The payroll software module runs at the end of the month after the close of business, and it computes all the salary payments for the HSS clients’ employees. Jane’s manager has arranged a meeting with the product owner, the infrastructure team, and a sales representative to try to get to the bottom of the problem. Unexpectedly, Sarika Kumar, CTO, has joined the meeting room via video call.

这是简·萨顿。简在HSS国际会计公司做软件开发已经三个月了。在她最近的项目中,她一直在关注工资系统中的一个问题。工资单软件模块在每月月底结束营业后运行,计算HSS客户员工的所有工资支付。简的经理已经安排了一次会议,与会者包括产品负责人、基础设施团队和一名销售代表,目的是弄清问题的根源。出乎意料的是,首席技术官Sarika Kumar通过视频电话加入了会议室。

Thomas Bock, the product owner, starts: “I don’t understand. The payroll module has been working fine for as long as I can remember. Suddenly, last month, the payment calculations weren’t completed on time, and we got loads of complaints from our clients. It made us look really unprofessional to Block Entertainment, our new and biggest client yet, with them threatening to go to our competitor.”
产品负责人托马斯·博克(Thomas Bock)开始说:“我不明白。从我记事起,工资模块就一直工作得很好。突然,上个月,付款计算没有按时完成,我们收到了客户的大量投诉。这让我们在Block Entertainment(我们最大的新客户)面前显得非常不专业,因为他们威胁要投奔我们的竞争对手。”

Jane’s manager, Francesco Varese, chimes in: “The problem is that the calculations
are too slow and take too long. They are slow because of their complex nature, considering many factors such as employee absences, joining dates, overtime, and a thousand other factors. Parts of the software were written more than a decade ago in C++.
There are no developers left in the firm who understand how this code works.”
简的经理弗朗西斯科·瓦雷泽(Francesco Varese)插话说:“问题在于计算
速度太慢,耗时太长。考虑到许多因素,如员工缺勤、入职日期、加班和其他上千个因素,它们之所以进展缓慢,是因为它们的复杂性。部分软件是十多年前用c++编写的。
公司里已经没有理解这些代码如何工作的开发人员了。”

“We’re about to sign up our biggest client ever, a company with over 30,000 employees. They’ve heard about our payroll problem, and they want to see it resolved before they proceed with the contract. It’s really important that we fix this as soon as possible,” replies Rob Gornall from the Sales and Acquisitions department.
“我们即将签下有史以来最大的客户,一家拥有3万多名员工的公司。他们听说了我们的工资问题,他们希望在签订合同之前先解决这个问题。我们尽快解决这个问题真的很重要,”销售和收购部门的Rob Gornall回答道。

“We’ve tried adding more processor cores and memory to the server that runs the module, but this made absolutely no difference. When we execute the payroll calculation using test data, it’s taking the same amount of time, no matter how many resources we allocate. It’s taking more than 20 hours to calculate all the clients’ payrolls, which is too long for our clients,” continues Frida Norberg from Infrastructure.
“我们已经尝试在运行模块的服务器上添加更多的处理器内核和内存,但这绝对没有什么不同。当我们使用测试数据执行工资单计算时,无论我们分配多少资源,它所花费的时间都是一样的。计算所有客户的工资单需要20多个小时,这对我们的客户来说太长了,”基础设施部门的弗里达·诺伯格继续说道。

It’s Jane’s turn to finally speak. As the firm’s newest employee, she hesitates a little but manages to say, “If the code is not written in a manner that takes advantage of the additional cores, it won’t matter if you allocate multiple processors. The code needs to use concurrent programming for it to run faster when you add more processing resources.”
终于轮到简发言了。作为公司的新员工,她犹豫了一下,但还是说:“如果代码没有充分利用额外的核心,那么即使分配多个处理器也没关系。代码需要使用并发编程,以便在添加更多处理资源时运行得更快。”

Everyone seems to have acknowledged that Jane is the most knowledgeable about the subject. There is a short pause. Jane feels as if everyone wants her to come up with some sort of answer, so she continues. “Right. Okay. I’ve been experimenting with a simple program written in Go. It divides the payroll into smaller employee groups and then calls the payroll module with each group as input. I’ve programmed it so that it calls the module concurrently using multiple goroutines. I’m also using a Go channel to load-balance the workload. At the end, I have another goroutine that collects the results via another channel.”
每个人似乎都承认简对这门学科的知识最渊博。有一个短暂的停顿。简觉得好像每个人都希望她想出某种答案,所以她继续说下去。“对了。好吧。我一直在尝试用Go写一个简单的程序。它将工资单划分为更小的员工组,然后使用每个组作为输入调用工资单模块。我已经对它进行了编程,以便它使用多个线程程序并发地调用模块。我还使用Go通道来负载平衡工作负载。最后,我有另一个程序,通过另一个通道收集结果。”

Jane looks around quickly and sees blank looks on everyone’s faces, so she adds, “In simulations, it’s at least five times faster on the same multicore hardware. There are still a few tests to run to make sure there are no race conditions, but I’m pretty sure that I can make it run even faster, especially if I get some help from accounting to migrate some of the old C++ logic into clean Go concurrent code.”
简迅速环顾四周,看到每个人脸上都是茫然的表情,于是她补充道:“在模拟中,在同样的多核硬件上,它至少快了五倍。还有一些测试需要运行,以确保没有竞争条件,但我很确定我可以让它运行得更快,特别是如果我得到会计的帮助,将一些旧的c++逻辑迁移到干净的Go并发代码中。”

Jane’s manager has a big smile on his face now. Everyone else in the meeting seems surprised and speechless. The CTO finally speaks up and says, “Jane, what do you need to get this done by the end of the month?”
简的经理脸上露出了灿烂的笑容。会议上的其他人似乎都很惊讶,说不出话来。首席技术官终于开口说:“简,你需要什么才能在月底前完成这项工作?”

Concurrent programming is a skill that is increasingly sought after by tech companies. It is a technique used in virtually every field of development, from web development to game programming, backend business logic, mobile applications, crypto, and many others. Businesses want to utilize hardware resources to their full capacity, as this saves them time and money. To accomplish this, they understand that they have to hire the right talent—developers who can write scalable concurrent applications.
并发编程是一种越来越受科技公司追捧的技能。它是一种几乎用于所有开发领域的技术,从web开发到游戏编程、后端业务逻辑、移动应用程序、加密等等。企业希望充分利用硬件资源,因为这样可以节省时间和金钱。为了实现这一点,他们明白他们必须雇佣合适的人才——能够编写可伸缩并发应用程序的开发人员。

关于并发

In this book, we will focus on principles and patterns of concurrent programming. How can we program instructions that happen at the same time? How can we manage concurrent executions so they don’t step over each other? What techniques should we use to have executions collaborate toward solving a common problem? When and why should we use one form of communication over another? We will answer all these questions and more by making use of the Go programming language. Go gives us a full set of tools to illustrate these concepts.
在本书中,我们将关注并发编程的原则和模式。我们如何对同时发生的指令进行编程?我们如何管理并发执行,使它们不会相互重叠?我们应该使用什么技术来让执行协作解决共同的问题?什么时候以及为什么我们应该使用一种沟通方式而不是另一种?我们将通过使用Go编程语言来回答所有这些问题。Go为我们提供了一套完整的工具来说明这些概念。

If you have little or no experience in concurrency but have some experience in Go or a similar C-style language, this book is ideal. This book starts with a gentle introduction to concurrency concepts in the operating system and describes how Go uses them to model concurrency. We’ll then move on to explain race conditions and why they occur in some concurrent programs. Later, we’ll discuss the two main ways we can implement communication between our executions: memory sharing and message passing. In the final chapters of this book, we’ll discuss concurrency patterns, deadlocks, and some advanced topics such as spinning locks.
如果你几乎没有并发的经验,但有一些Go或类似的c风格语言的经验,这本书是理想的。本书首先简单介绍了操作系统中的并发概念,并描述了Go如何使用它们对并发进行建模。然后,我们将继续解释竞争条件,以及为什么它们会出现在一些并发程序中。稍后,我们将讨论在执行之间实现通信的两种主要方式:内存共享和消息传递。在本书的最后几章中,我们将讨论并发模式、死锁和一些高级主题,如旋转锁。

Apart from helping us to get hired or promoted as developers, knowing concurrent programming gives us a wider set of skills that we can employ in new scenarios. For example, we can model complex business interactions that happen at the same time. We can also use concurrent programming to improve our software’s responsiveness by picking up tasks swiftly. Unlike sequential programming, concurrent programming can make use of multiple CPU cores, which allows us to increase the work done by our programs by speeding up their execution. Even with a single CPU core, concurrency offers benefits because it enables time-sharing and lets us perform tasks while we’re waiting for I/O operations to complete. Let’s now look at some of these scenarios in more detail.
除了帮助我们作为开发人员获得聘用或晋升之外,了解并发编程还为我们提供了更广泛的技能,我们可以在新的场景中使用这些技能。例如,我们可以对同时发生的复杂业务交互进行建模。我们还可以使用并发编程,通过快速拾取任务来提高软件的响应能力。与顺序编程不同,并发编程可以利用多个CPU内核,这允许我们通过加速程序的执行来增加程序完成的工作。即使使用单个CPU核心,并发性也提供了好处,因为它支持分时,并允许我们在等待I/O操作完成时执行任务。现在让我们更详细地看看其中一些场景。

与并发世界交互

We live and work in a concurrent world. The software that we write models complex business processes that interact concurrently. Even the simplest of businesses typically have many of these concurrent interactions. For example, consider multiple people ordering online at the same time or a consolidation process grouping packages together while coordinating with ongoing shipments, as shown in figure 1.1.
我们生活和工作在一个并行的世界。我们编写的软件为并发交互的复杂业务流程建模。即使是最简单的业务通常也有许多这样的并发交互。例如,考虑多人同时在线订购,或者一个整合流程将包裹分组在一起,同时协调正在进行的发货,如图1.1所示。

在这里插入图片描述

In our everyday life, we deal with concurrency all the time. Every time we drive a car, we interact with multiple concurrent actors, such as other cars, cyclists, and pedestrians. At work, we may put a task on hold while we’re waiting for an email reply and pick up the next task. When cooking, we plan our steps so we maximize our productivity and shorten the cooking time. Our brain is perfectly comfortable managing concurrent behavior. In fact, it does this all the time without us even noticing.
在我们的日常生活中,我们一直在处理并发问题。每次我们开车时,我们都会与多个并发的参与者进行交互,例如其他汽车、骑自行车的人和行人。在工作中,我们可能会在等待电子邮件回复时暂停一项任务,然后继续下一项任务。当我们做饭时,我们计划我们的步骤,这样我们可以最大限度地提高效率,缩短烹饪时间。我们的大脑能够很好地处理同时发生的行为。事实上,它一直在这样做,而我们甚至没有注意到。

Concurrent programming is about writing code so that multiple tasks and processes can execute and interact at the same time. If two customers place an order simultaneously and only one stock item remains, what happens? If the price of a flight ticket goes up every time a client buys a ticket, what happens when multiple tickets are booked at the same exact instant? If we have a sudden increase in load due to extra demand, how will our software scale when we increase the processing and memory resources? These are all scenarios that developers deal with when they are designing and programming concurrent software.
并发编程是关于编写代码,以便多个任务和进程可以同时执行和交互。如果两个客户同时下订单,只剩下一个库存项目,会发生什么?如果每次客户购买机票,机票的价格都会上涨,那么在同一时刻预订多张机票会发生什么呢?如果由于额外需求导致负载突然增加,那么当我们增加处理和内存资源时,我们的软件将如何扩展?这些都是开发人员在设计和编程并发软件时要处理的场景。

增加吞吐量

For the modern developer, it is increasingly important to understand how to program concurrently. This is because the hardware landscape has changed over the years to benefit this type of programming.
对于现代开发人员来说,了解如何并行编程变得越来越重要。这是因为多年来硬件环境发生了变化,有利于这种类型的编程。

Prior to multicore technology, processor performance increased proportionally to clock frequency and transistor count, roughly doubling every two years. Processor engineers started hitting physical limits due to overheating and power consumption, which coincided with the explosion of more mobile hardware, such as notebooks and smartphones. To reduce excessive battery consumption and CPU overheating while increasing processing power, engineers introduced multicore processors.
在多核技术出现之前,处理器的性能随着时钟频率和晶体管数量的增加而增加,大约每两年翻一番。由于过热和功耗,处理器工程师开始触及物理极限,这与笔记本电脑和智能手机等更多移动硬件的爆炸式增长相一致。为了减少电池过度消耗和CPU过热,同时提高处理能力,工程师们引入了多核处理器。

In addition, the rise of cloud computing services has given developers easy access to large, cheap processing resources where they can run their code. This extra computational power can only be harnessed effectively if our code is written in a manner that takes full advantage of the extra processing units.
此外,云计算服务的兴起使开发人员能够轻松访问大型、廉价的处理资源,并在其中运行代码。如果我们的代码以充分利用额外处理单元的方式编写,则只能有效地利用这种额外的计算能力。

DEFINITION Horizontal scaling is when we improve system performance by distributing the load over multiple processing resources, such as processors and server machines (see figure 1.2). Vertical scaling is when we improve the existing resources, such as by getting a faster processor.
水平扩展是指我们通过在多个处理资源(如处理器和服务器机器)上分配负载来提高系统性能(参见图1.2)。纵向扩展是指我们改进现有资源,例如通过获得更快的处理器。

在这里插入图片描述

Having multiple processing resources means we can scale horizontally. We can use the extra processors to execute tasks in parallel and finish them more quickly. This is only possible if we write code in a way that takes full advantage of the extra processing resources.
拥有多个处理资源意味着我们可以横向扩展。我们可以使用额外的处理器并行执行任务,并更快地完成任务。这只有在我们以充分利用额外处理资源的方式编写代码时才有可能。

What about a system that has only one processor? Is there any advantage to writing concurrent code if our system does not have multiple processors? It turns out that writing concurrent programs has a benefit even in this scenario.
如果一个系统只有一个处理器呢?如果我们的系统没有多个处理器,那么编写并发代码有什么优势吗?事实证明,即使在这种情况下,编写并发程序也有好处。

Most programs spend only a small proportion of their time executing computations on the processor. Think, for example, about a word processor that waits for input from the keyboard, or a text-file search utility that spends most of its running time waiting for portions of the text files to load from disk. We can have our program perform different tasks while it’s waiting for I/O. For example, the word processor could perform a spell check on the document while the user is thinking about what to type next. We can have the file search utility look for a match with the file that we already loaded in memory while we are reading the next file into another portion of memory.
大多数程序只花一小部分时间在处理器上执行计算。例如,请考虑等待键盘输入的字处理器,或者花费大部分运行时间等待从磁盘加载部分文本文件的文本文件搜索实用程序。我们可以让程序在等待I/O时执行不同的任务。例如,当用户考虑接下来要输入什么时,文字处理器可以对文档执行拼写检查。当我们将下一个文件读入内存的另一部分时,我们可以让文件搜索实用程序查找与我们已经加载到内存中的文件匹配的文件。

As another example, think of cooking or baking a favorite dish. We can make more effective use of our time if, while the dish is in the oven or on the stove, we perform some other actions instead of just waiting around (see figure 1.3). In this way, we are making more effective use of our time, and we are more productive. This is analogous to our program executing other instructions on the CPU while it waits for a network message, user input, or a file to be written. This means our program can get more work done in the same amount of time.
再举一个例子,想想烹饪或烘烤你最喜欢的菜。我们可以更有效地利用我们的时间,当菜在烤箱或炉子上,我们做一些其他的行动,而不是只是等待(见图1.3)。通过这种方式,我们更有效地利用了我们的时间,我们更富有成效。这类似于我们的程序在等待网络消息、用户输入或文件写入时在CPU上执行其他指令。这意味着我们的程序可以在相同的时间内完成更多的工作。

在这里插入图片描述

提高响应能力

Concurrent programming makes our software more responsive because we dont need to wait for one task to finish before responding to a user’s input. Even if we have one processor, we can always pause the execution of a set of instructions, respond to the user’s input, and then continue with the execution while we’re waiting for the next user’s input.
并发编程使我们的软件响应更快,因为我们不需要在响应用户输入之前等待一个任务完成。即使只有一个处理器,我们也可以暂停一组指令的执行,响应用户的输入,然后在等待下一个用户输入的同时继续执行。

If we again think of a word processor, multiple tasks might be running in the background while we are typing. There is a task that listens to keyboard events and displays each character on the screen. We might also have a task that checks our spelling and grammar in the background. Another task might be running to give us stats on our document (word count, page count, etc.). Periodically, we may have another task that autosaves our document. All these tasks running together give the impression that they are somehow running simultaneously, but what’s happening is that these tasks are being fast-switched by the operating system on CPUs. Figure 1.4 illustrates a simplified timeline showing these three tasks executing on a single processor. This interleaving system is implemented by using a combination of hardware interrupts and operating system traps.
如果我们再想想文字处理器,当我们打字时,可能有多个任务在后台运行。有一个任务监听键盘事件并在屏幕上显示每个字符。我们也可能有一个任务在后台检查我们的拼写和语法。另一个任务可能是为我们提供文档的统计信息(字数、页数等)。定期地,我们可能会有另一个自动保存文档的任务。所有这些一起运行的任务给人的印象是它们在某种程度上是同时运行的,但实际情况是,这些任务被cpu上的操作系统快速切换。图1.4显示了在单个处理器上执行这三个任务的简化时间轴。这种交错系统是通过结合使用硬件中断和操作系统陷阱来实现的。

在这里插入图片描述

Go中的并发编程

Go is a very good language to use when learning about concurrent programming because its creators designed it with high-performance concurrency in mind. Their aim was to produce a language that was efficient at runtime, readable, and easy to use.
在学习并发编程时,Go是一种非常好的语言,因为它的创建者在设计时就考虑到了高性能并发性。他们的目标是产生一种运行时高效、可读且易于使用的语言。

一目了然

Go uses a lightweight construct, called a goroutine, to model the basic unit of concurrent execution. As we shall see in the next chapter, goroutines give us a system of userlevel threads running on a set of kernel-level threads and managed by Go’s runtime.
Go使用一种轻量级结构(称为goroutine)来为并发执行的基本单元建模。在下一章中我们将会看到,goout程为我们提供了一个运行在一组内核级线程上的用户级线程系统,并由Go的运行时管理。

Given the lightweight nature of goroutines, the premise of the language is that we should focus mainly on writing correct concurrent programs, letting Go’s runtime and hardware mechanics deal with parallelism. The principle is that if you need something to be done concurrently, create a goroutine to do it. If you need many things done concurrently, create as many goroutines as you need, without worrying about resource allocation. Then, depending on the hardware and environment that your program is running on, your solution will scale.
考虑到Go程序的轻量级特性,该语言的前提是我们应该主要关注编写正确的并发程序,让Go的运行时和硬件机制处理并行性。原则是,如果您需要同时完成某些事情,则创建一个程序来完成它。如果您需要并发地完成许多事情,那么创建尽可能多的例程,而不必担心资源分配。然后,根据您的程序所运行的硬件和环境,您的解决方案将进行扩展。

In addition to goroutines, Go provides us with many abstractions that allow us to coordinate the concurrent executions on a common task. One of these abstractions is known as a channel. Channels allow two or more goroutines to pass messages to each other. This enables the exchange of information and synchronization of the multiple executions in an easy and intuitive manner.
除了例程之外,Go还提供了许多抽象,使我们能够协调公共任务上的并发执行。其中一种抽象称为通道。通道允许两个或多个程序相互传递消息。这使得以一种简单而直观的方式交换信息和同步多个执行成为可能。

用CSP和原语建模并发性

In 1978, C.A.R. Hoare first described communicating sequential processes (CSP) as a formal language for expressing concurrent interactions. Many languages, such as Occam and Erlang, have been influenced by CSP. Go tries to implement many of CSP’s ideas, such as the use of synchronized channels.
1978年,C.A.R. Hoare首先将通信顺序过程(CSP)描述为一种表达并发交互的形式语言。许多语言,如Occam和Erlang,都受到了CSP的影响。Go尝试实现许多CSP的想法,比如使用同步通道。

This concurrency model of having isolated goroutines communicating and synchronizing using channels (see figure 1.5) reduces the risk of race conditions— types of programming errors that occur in bad concurrent programming and that are typically very hard to debug and lead to data corruption and unexpected behavior. This type of modeling concurrency is more akin to how concurrency happens in our everyday lives, such as when we have isolated executions (people, processes, or machines) working concurrently, communicating with each other by sending messages back and forth.
这种使用通道进行通信和同步的独立程序的并发模型(参见图1.5)降低了竞态条件的风险——竞态条件是在糟糕的并发编程中发生的编程错误类型,通常很难调试,并导致数据损坏和意外行为。这种类型的并发建模更类似于我们日常生活中并发发生的方式,例如当我们有独立的执行(人、流程或机器)并发工作时,通过来回发送消息相互通信。

在这里插入图片描述

Depending on the problem, the classic concurrency primitives used with memory sharing (such as mutexes and condition variables, found in many other languages) will sometimes do a better job and result in better performance than using CSP-style programming. Luckily for us, Go provides us with these tools in addition to the CSPstyle tools. When CSP is not the appropriate model to use, we can fall back on the other classic primitives.
根据问题的不同,与内存共享一起使用的经典并发原语(例如在许多其他语言中发现的互斥锁和条件变量)有时会比使用csp风格的编程做得更好,并产生更好的性能。幸运的是,除了CSPstyle的工具之外,Go还为我们提供了这些工具。当CSP不是适合使用的模型时,我们可以求助于其他经典原语。

In this book, we will purposely start with memory sharing and synchronization using classic primitives. The idea is that by the time we get to discussing CSP-style concurrent programming, you will have a solid foundation in the traditional locking and synchronization primitives.
在本书中,我们将从使用经典原语的内存共享和同步开始。我们的想法是,当我们开始讨论csp风格的并发编程时,您将对传统的锁定和同步原语有了坚实的基础。

构建我们自己的并发工具

In this book, you will learn how to use various tools to build concurrent applications. This includes concurrency constructs such as mutex, condition variables, channels, semaphores, and so on.
在本书中,您将学习如何使用各种工具来构建并发应用程序。这包括互斥锁、条件变量、通道、信号量等并发构造。

Knowing how to use these concurrency tools is good, but what about understanding their inner workings? Here, we’ll go one step further and take the approach of building them together from scratch, even if they are available in Go’s libraries. We will pick common concurrency tools and see how they can be implemented using other concurrency primitives as building blocks. For example, Go doesn’t come with a bundled semaphore implementation, so apart from understanding how and when to use semaphores, we’ll go about implementing one ourselves. We’ll also do this for some of the tools that are available in Go, such as waitgroups and channels.
知道如何使用这些并发性工具是很好的,但是如何理解它们的内部工作原理呢?在这里,我们将更进一步,采用从头开始构建它们的方法,即使它们在go的库中可用。我们将选择常见的并发工具,并查看如何使用其他并发原语作为构建块来实现它们。例如,Go没有绑定信号量实现,所以除了理解如何以及何时使用信号量之外,我们将自己实现一个信号量。我们还将对Go中可用的一些工具(如等待组和通道)进行这样的处理。

This idea is analogous to having the knowledge to implement well-known algorithms. We might not need to know how to implement a sorting algorithm to use a sorting function; however, learning how the algorithm works exposes us to different scenarios and new ways of thinking, making us better programmers. We can then apply those scenarios to different problems. In addition, knowing how a concurrency tool is built allows us to make better-informed decisions about when and how to use it.
这个想法类似于拥有实现已知算法的知识。我们可能不需要知道如何实现排序算法来使用排序函数;然而,学习算法如何工作使我们接触到不同的场景和新的思维方式,使我们成为更好的程序员。然后我们可以将这些场景应用于不同的问题。此外,了解并发工具是如何构建的,使我们能够更好地决定何时以及如何使用它。

扩展性能

Performance scalability is the measure of how well a program speeds up in proportion to the increase in the number of resources available to the program. To understand this, let’s try to make use of a simple analogy.
性能可伸缩性是衡量程序的速度与程序可用资源数量的增加成正比的程度。为了理解这一点,让我们试着用一个简单的类比。

Imagine a world where we are property developers. Our current project is to build a small multi-story residential house. We give the architectural plan to a builder, and they set off to finish the small house. The work is all completed in a period of eight months.
想象一个我们都是房地产开发商的世界。我们目前的项目是建造一座小型多层住宅。我们把建筑平面图交给建筑商,他们就着手完成那座小房子。这项工作在八个月内全部完成。

As soon as that project is finished, we get another request for the same build but in another location. To speed things up, we hire two builders instead of one. This time around, the builders complete the house in just four months.
一旦该项目完成,我们就会在另一个位置收到相同构建的另一个请求。为了加快进度,我们雇佣了两个而不是一个建筑工人。这一次,建筑商只用了四个月就建成了这座房子。

The next time we are asked to build the same house, we hire even more help, so that the house is finished quicker. This time we pay four builders, and it takes them two and a half months to complete. The house has cost us a bit more to build than the previous one. Paying four builders for two and a half months costs more than paying two builders for four months (assuming they all charge the same rate).
下次我们被要求建造同样的房子时,我们雇佣了更多的帮手,这样房子就能更快完工。这次我们雇了四个建筑商,他们花了两个半月的时间才完工。建造这所房子比建造前一所花了我们一些钱。向四个建筑商支付两个半月的费用比向两个建筑商支付四个月的费用要高(假设他们的费率相同)。

We repeat the experiment twice more, once with 8 builders and then with 16. With both 8 and 16 builders, the house takes two months to complete. It seems that no matter how many hands we put on the job, the build cannot be completed in less than two months. In geek speak, we say that we have hit our scalability limit. Why does this happen? Why can’t we continue to double our resources (people, money, or processors) and always reduce the time spent by half?
我们又重复了两次实验,一次是8个建造者,然后是16个。这座房子有8名和16名建筑工人,需要两个月的时间才能完工。看来无论我们投入多少人手,都不可能在两个月内完成这项工作。用极客的话来说,我们说我们已经达到了可伸缩性的极限。为什么会发生这种情况?为什么我们不能继续将我们的资源(人员、资金或处理器)增加一倍,并将花费的时间减少一半呢?

Amdahl法则

In 1967, Gene Amdahl, a computer scientist, presented a formula at a conference that measured speedup with regard to a problem’s parallel-to-sequential ratio. This became known as Amdahl’s law.
1967年,计算机科学家吉恩·阿姆达尔(Gene Amdahl)在一次会议上提出了一个公式,用来衡量一个问题的并行顺序比的加速。这就是著名的阿姆达尔定律。

DEFINITION Amdahl’s law states that the overall performance improvement gained by optimizing a single part of a system is limited by the fraction of time that the improved part is actually used.
Amdahl定律指出,通过优化系统的单个部分而获得的整体性能改进受到改进部分实际使用时间的限制。

In our house build scenario, the scalability is limited by various factors. For starters, our approach to solving the problem might be limiting us. For example, one cannot construct the second floor before constructing the first. In addition, several parts of the build can only be done sequentially. For example, if a single road leads to the building site, only one transport can use the road at any point in time. In other words, some parts of the building process are sequential (one after the other), and other parts can be done in parallel (at the same time). These factors influence and limit the scalability of our task.
在我们的房屋构建场景中,可扩展性受到各种因素的限制。首先,我们解决问题的方法可能会限制我们。例如,不能先建第二层再建第二层。此外,构建的几个部分只能按顺序完成。例如,如果一条道路通往建筑工地,那么在任何时间点,只有一种运输工具可以使用这条道路。换句话说,构建过程的某些部分是顺序的(一个接一个),而其他部分可以并行完成(同时)。这些因素影响并限制了我们任务的可扩展性。

Amdahl’s law tells us that the non-parallel parts of an execution act as a bottleneck and limit the advantage of parallelizing the execution. Figure 1.6 shows this relationship between the theoretical speedup obtained as we increase the number of processors.
Amdahl定律告诉我们,执行的非并行部分会成为瓶颈,限制并行执行的优势。图1.6显示了随着处理器数量的增加而获得的理论加速之间的关系。

在这里插入图片描述

If we apply this chart to our construction problem, when we use a single builder and they spend 5% of their time on the parts that can only be done sequentially, the scalability follows the topmost line in our chart (95% parallel). This sequential portion is the part that can only be done by one person, such as trucking in the building materials through a narrow road.
如果我们将此图表应用于我们的构建问题,当我们使用单个构建者并且他们将5%的时间花在只能按顺序完成的部分上时,可扩展性遵循图表中最上面的线(95%并行)。这个顺序部分是只能由一个人完成的部分,例如通过狭窄的道路用卡车运送建筑材料。

As you can see from the chart, even with 512 people working on the construction, we would only finish the job about 19 times faster than if we had just 1 person. After this point, the situation does not improve much. We’ll need more than 4,096 builders to finish the project just 20 times faster. We hit a hard limit around this number. Contracting more workers does not help at all, and we would be wasting our money.
从图表中可以看出,即使有512人参与建设,我们完成工作的速度也只比只有1人快19倍。在这一点之后,情况并没有改善多少。我们将需要超过4096名建筑工人来完成这个项目,而完成速度要快20倍。我们达到了这个数字的极限。雇用更多的工人根本没有帮助,而且我们会浪费我们的钱。

The situation is even worse if a lower percentage of work is parallelizable. With 90%, we would hit this scalability limit around the 512-workers mark. With 75%, we get there at 128 workers, and with 50% at just 16 workers. Notice that it’s not just this limit that goes down—the speedup is also greatly reduced. When the work is 90%, 75%, and 50% parallelizable, we get maximum speedups of 10, 4, and 2, respectively.
如果可并行化的工作比例较低,情况就更糟了。如果是90%,我们就会达到512个工作人员的可伸缩性极限。75%的情况下,我们有128名工人,50%的情况下,只有16名工人。注意,不仅这个极限降低了,加速也大大降低了。当工作的并行度达到90%、75%和50%时,我们的最大速度分别为10、4和2。

Amdahl’s law paints a pretty bleak picture of concurrent programming and parallel computing. Even with concurrent code that has a tiny fraction of serial processing, the scalability is greatly reduced. Thankfully, this is not the full picture.
Amdahl定律描绘了一幅并行编程和并行计算的惨淡图景。即使并发代码只占串行处理的一小部分,可伸缩性也会大大降低。值得庆幸的是,这还不是全部。

Gustafson定律

In 1988, two computer scientists, John L. Gustafson and Edwin H. Barsis, reevaluated Amdahl’s law and published an article addressing some of its shortcomings (“Reevaluating Amdah’s Law,” https://dl.acm.org/doi/pdf/10.1145/42411.42415). The article gives an alternative perspective on the limits of parallelism. Their main argument is that, in practice, the size of the problem changes when we have access to more resources.
1988年,两位计算机科学家John L. Gustafson和Edwin H. Barsis重新评估了Amdahl定律,并发表了一篇文章,指出了它的一些缺点(“reevaluate Amdahl’s law”,https://dl.acm.org/doi/pdf/10.1145/42411.42415)。这篇文章给出了关于并行限制的另一种观点。他们的主要论点是,在实践中,当我们获得更多资源时,问题的规模就会改变。

To continue with our house-building analogy, if we did have thousands of builders available at our disposal, it would be wasteful to put them all into building a small house when we have future projects in the pipeline. Instead, we would try to put the optimal number of builders on our house construction and allocate the rest of the workers to other projects.
继续我们的房屋建设类比,如果我们确实有成千上万的建筑商可供我们使用,当我们有未来的项目在管道中时,把他们都投入建造一个小房子是浪费的。相反,我们会尽量让最优数量的建筑商从事房屋建设,并将其余工人分配给其他项目。

Suppose we were developing software and we had a large number of computing resources. If we noticed that utilizing half the resources resulted in the same software performance, we could allocate the extra resources to do other things, such as increasing the accuracy or quality of that software in other areas.
假设我们正在开发软件,我们有大量的计算资源。如果我们注意到使用一半的资源导致相同的软件性能,我们可以分配额外的资源去做其他事情,例如在其他领域增加软件的准确性或质量。

The second argument against Amdahl’s law is that when you increase the problem’s size, the non-parallel part of the problem typically does not grow in proportion with the problem size. In fact, Gustafson argues that for many problems, this remains constant. Thus, when you take these two points into account, the speedup can scale linearly with the available parallel resources. This relationship is shown in figure 1.7.
反对阿姆达尔定律的第二个论点是,当你增加问题的规模时,问题的非并行部分通常不会与问题的规模成比例地增长。事实上,Gustafson认为,对于许多问题,这是不变的。因此,当考虑到这两点时,加速可以随着可用的并行资源线性扩展。这种关系如图1.7所示。

在这里插入图片描述

Gustafson’s law tells us that as long as we find ways to keep our extra resources busy, the speedup should continue to increase and not be limited by the serial part of the problem. However, this is only true if the serial part stays constant as we increase the problem size, which, according to Gustafson, is the case in many types of programs.
Gustafson定律告诉我们,只要我们找到让额外资源忙碌的方法,加速就应该继续增加,而不受问题的串行部分的限制。然而,只有当我们增加问题规模时,串行部分保持不变时,这才是正确的,根据Gustafson的说法,这在许多类型的程序中都是如此。

To fully understand both Amdahl’s and Gustafson’s laws, let’s take a computer game as an example. Let’s say a particular computer game with rich graphics was written to make use of multiple computing processors. As time goes by and computers become more powerful, with more parallel processing cores, we can run that same game with a higher frame rate, giving us a smoother experience. Eventually, we get to a point where we’re adding more processors, but the frame rate is not increasing further. This happens when we hit the speedup limit. No matter how many processors we add, the game won’t run with higher frame rates. This is what Amdahl’s law is telling us—that there is a speedup limit for a particular problem of fixed size if it has a nonparallel portion.
为了充分理解Amdahl和Gustafson定律,让我们以一款电脑游戏为例。让我们假设一个具有丰富图像的特定电脑游戏是为了使用多个计算处理器而编写的。随着时间的推移,计算机变得越来越强大,拥有更多的并行处理核心,我们可以以更高的帧率运行同一款游戏,从而获得更流畅的体验。最终,我们会增加更多的处理器,但帧率并没有进一步提高。当我们达到加速极限时就会发生这种情况。无论我们添加多少处理器,游戏都不会以更高的帧率运行。这就是阿姆达尔定律告诉我们的,对于一个固定大小的特定问题,如果它有一个非并行部分,就会有一个加速限制。

However, as technology improves and processors get more cores, the game designers will put those extra processing units to good use. Although the frame rate might not increase, the game can now contain more graphic detail and higher resolution due to the extra processing power. This is Gustafson’s law in action. When we increase the resources, there is an expectation of an increase in the system’s capabilities, and the developers will make good use of the extra processing power.
然而,随着技术的进步和处理器拥有更多核心,游戏设计师将更好地利用这些额外的处理单元。虽然帧率可能不会增加,但由于额外的处理能力,游戏现在可以包含更多的图像细节和更高的分辨率。这就是Gustafson定律的应用。当我们增加资源时,期望系统的能力也会增加,开发人员将会很好地利用额外的处理能力。

总结

  • Concurrent programming allows us to build more responsive software.

  • Concurrent programs can also provide increased speedup when running on multiple processors.

  • We can increase throughput even when we have only one processor if our concurrent programming makes effective use of the I/O wait times.

  • Go provides us with goroutines, which are lightweight constructs for modeling concurrent executions.

  • Go provides us with abstractions, such as channels, that enable concurrent executions to communicate and synchronize.

  • Go allows us the choice of building our concurrent application either using the communicating sequential processes (CSP)-style model or using the classical primitives.

  • Using a CSP-style model, we reduce the chance of certain types of concurrent errors; however, for certain problems, using the classical primitives will give us better results.

  • Amdahl’s law tells us that the performance scalability of a fixed-size problem is limited by the non-parallel parts of an execution.

  • Gustafson’s law tells us that if we keep on finding ways to keep our extra resources busy, the speedup should continue to increase and not be limited by the serial part of the problem.

  • 并发编程允许我们构建响应更快的软件。

  • 并发程序也可以在多处理器上运行时提供更高的加速。

  • 如果我们的并发编程有效地利用了I/O等待时间,即使我们只有一个处理器,我们也可以提高吞吐量。

  • Go为我们提供了Go例程,它们是用于建模并发执行的轻量级结构。

  • Go为我们提供了抽象,例如通道,使并发执行能够通信和同步。

  • Go允许我们选择使用通信顺序进程(CSP)风格模型或使用经典原语来构建并发应用程序。

  • 使用csp风格的模型,我们减少了某些类型的并发错误的机会;然而,对于某些问题,使用经典原语会得到更好的结果。

  • 阿姆达尔定律告诉我们,固定大小问题的性能可扩展性受到执行的非并行部分的限制。

  • Gustafson定律告诉我们,如果我们不断寻找方法来保持我们的额外资源繁忙,加速应该继续增加,而不是被问题的串行部分所限制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Python私教

创业不易,请打赏支持我一点吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值