友情提示:380元/半年,儿童学编程,就上码丁实验室。
虽然一些编程语言的框架在不断地提高多核资源使用效率,例如 Java 的 Netty 等,但仍然需要开发人员花费大量的时间和精力搞懂这些框架的运行原理后才能熟练掌握。
Go 语言在多核并发上拥有原生的设计优势。Go 语言从 2009 年 11 月开源,2012 年发布 Go 1.0 稳定版本以来,已经拥有活跃的社区和全球众多开发者,并且与苹果公司的 Swift 一样,成为当前非常流行的开发语言之一。
很多公司,特别是中国的互联网公司,即将或者已经完成了使用 Go 语言改造旧系统的过程。经过 Go 语言重构的系统能使用更少的硬件资源而有更高的并发和 I/O 吞吐表现。
Go语言从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验就可以轻松地在 Go 语言运行时来帮助开发者决定怎么使用 CPU 资源。
Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go 语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用CPU性能。
多个 goroutine 中,Go 语言使用通道(channel)进行通信,程序可以将需要并发的环节设计为生产者模式和消费者的模式,将数据放入通道。通道的另外一端的代码将这些数据进行并发计算并返回结果,如下图所示。

下面代码中的生产者每秒生成一个字符串,并通过通道传给消费者,生产者使用两个 goroutine 并发运行,消费者在 main() 函数的 goroutine 中进行处理。
package main import ( "fmt" "math/rand" "time" ) // 数据生产者 func producer(header string, channel chan<- string) { // 无限循环, 不停地生产数据 for { // 将随机数和字符串格式化为字符串发送给通道 channel <- fmt.Sprintf("%s: %v", header, rand.Int31()) // 等待1秒 time.Sleep(time.Second) } } // 数据消费者 func customer(channel <-chan string) { // 不停地获取数据 for { // 从通道中取出数据, 此处会阻塞直到信道中返回数据 message := <-channel // 打印数据 fmt.Println(message) } } func main() { // 创建一个字符串类型的通道 channel := make(chan string) // 创建producer()函数的并发goroutine go producer("cat", channel) go producer("dog", channel) // 数据消费函数 customer(channel) }
运行结果:
dog: 2019727887
cat: 1298498081
dog: 939984059
cat: 1427131847
cat: 911902081
dog: 1474941318
dog: 140954425
cat: 336122540
cat: 208240456
dog: 646203300
对代码的分析:
- 第03行,导入格式化(fmt)、随机数(math/rand)、时间(time)包参与编译。
- 第10行,生产数据的函数,传入一个标记类型的字符串及一个只能写入的通道。
- 第13行,for{}构成一个无限循环。
- 第15行,使用rand.Int31()生成一个随机数,使用fmt.Sprintf()函数将header和随机数格式化为字符串。
- 第18行,使用time.Sleep()函数暂停1秒再执行这个函数。如果在goroutine中执行时,暂停不会影响其他goroutine的执行。
- 第23行,消费数据的函数,传入一个只能写入的通道。
- 第26行,构造一个不断消费消息的循环。
- 第28行,从通道中取出数据。
- 第31行,将取出的数据进行打印。
- 第35行,程序的入口函数,总是在程序开始时执行。
- 第37行,实例化一个字符串类型的通道。
- 第39行和第40行,并发执行一个生产者函数,两行分别创建了这个函数搭配不同参数的两个goroutine。
- 第42行,执行消费者函数通过通道进行数据消费。
整段代码中,没有线程创建,没有线程池也没有加锁,仅仅通过关键字 go 实现 goroutine,和通道实现数据交换。