并发编程
并发编程允许开发者实现并行的算法和编写能够充分利用多核和多处理器的程序。不好的一面是,在大多数语言中(C, C++, and Java)在写并发程序,维护,调试时相对于单线程的程序更加难。此外,将一个处理分成多个线程处理,并不总是值得的。由于线程的自身的开销,使我们不能达到我们期望的性能。同时多线程的程序更加容易出现错误。
一个解决方案是完全避免使用线程。比如,我们可以通过多处理将任务传递给操作系统。但这有一个缺点,我们需要负责多进程间的通信,这通常比共享内存的并发开销更大。
Go的解决方法是一举三得的,第一,Go为并发编程提供了高级别的支持,使得在Go语言中做并发更加容易;第二,并发处理是在goroutines上完成,goroutine比线程更加轻量; 第三,自动垃级回收解决了程序员在做并发编程时的复杂的内存管理。
Go内制编写并发程序的高级API是基于CSP(Communicating Sequential Process顺序进程通信). 这意味着不需要明确的加锁和解锁,通过在线程安全的channel中发送和接收数据实现同步,这大大的简化了并发编程的难度。而且几十个线程就可以使得一台标准的桌面电脑超负荷运行,相同的机器去可以轻松的应对上百,上千,甚至上万的goroutine. Go这种方式使得程序员更加容易做并发编程的原因是,只让程序员关注它们想要实现的功能,而不需要处理锁和其它更低层的细节。
虽然其他大多数语言提供了非常低级别的的并发操作(原子级别的添加,比较和交换), 以及一些低级别的机制,比如互斥锁。但它们都没有像Go一样,提供内置的高级并发支持。
在本章中除了高级并发支持,Go也像其它语言一样,提供了一些低级别的函数。在最低级别的标准库sync/atomic包中提供了操作原子添加,比较,交换的函数。这些高级函数是用来实现线程安全的同步算法和数据结构——它们并不适用于应用程序员。Go的sync packager提供了传统的低级并发原语:等待条件和互斥。这对于其它大部分语言,都是高级特性,所以应用程序员经常被强迫使用它们。
Go期望应用程序员在做并发编程时使用Go的高级工具——channels 和 goroutine. 另外也可以使用sync.Once和sync.WaitGrou这,sync.Once类型可使得一个函数只被调用一次,而不管在程序中有多少次调用, Sync.WaitGroup类型用于提供高级别的同步机制,这些之后都会讲。
我们在之前的第5章讲到了channels和goroutines的基础使用。这里不会在重复这些知识点,所以在继续本章之前,最好重读或者浏览一遍这些章节。
本章首先概述了Go并发编程的一些关键概念。随后的章节通过5个完整的程序来阐述Go的并发编程和它们之间表现的标准模式的使用。第一个例子展示了如何创建一个管道。管道的每个部分都运行在它们自己的goroutine, 以实现的吞吐量。第二个例子展示了如何将工作划分成固定数量的goroutines中去,并且独立的输出各自的结果。第三个例子展示了如何创建一个线程安全的数据结构——不需要看得见的锁和低级原语。 第四个例子展示了如何在固定数量的goroutine中,每个goroutine中执行独立的工作,并且将它们的结果合并。第五个例子展示了如何创建大量在处理时可依赖的goroutine. 并且合并这个goroutine的工作到单个的结果集中。