并发编程, 目的就是提高程序的吞吐能力和性能, 能够同时处理很多业务, 而不管这些业务是相同的还是不同的.
为什么并发编程能够提升程序的吞吐能力和性能呢?
合理地拆分程序的逻辑, 使不同的业务模块可以并发执行, 同时可以处理多个业务模块, 从而在相同时间内有更多业务单位可以被处理. 在串行编程的模式下, 必须一个模块执行完, 才能执行下一个模块, 其中一个模块在执行的时候, 其他模块就一直等待, 同一个时间点只能有一个模块执行. 需注意的是程序拆解并不是一件容易的事, 因为业务逻辑中有些部分必须等待其他并发部分全部或者部分完成后才能执行.如统计访问几大搜索完整的平均延迟,统计模块需要等待访问百度, bing, 谷歌等并发模块都返回结果后才能统计. 需要多个并发模块协调处理叫作编排. 对于任务的编排也不是一件容易的事情. 有的时候, 并发使用某个资源还会涉及竞争的问题或者相关共享数据可能出现意想不到的结果, 这时候就需要同步原语的支持
同步原语指帮助你开发并发编程的一些库和数据结构, 专门用来做并发同步的数据结构
对于 I/O 敏感型的程序, 并发编程对其性能提升巨大. I/O 敏感型的程序会将大量的时间耗费在等待 I/O 完成上, 如从网络中读取数据, 往磁盘写入批量的持久化数据,或者访问一个数据库等. 对于串行编程模型, 程序在等待的过程不能执行其他业务逻辑(即使 CPU 空闲); 但是对于并行编程模型, 程序在等待 I/O 完成的过程中完全可以处理其他业务逻辑, 程序不会被阻塞, 所以并行对 I/O 敏感程序的性能提升有时候是巨大的
并发编程可以充分利用现代 CPU 的多核能力.
Go 语言从语言设计上为并发编程提供了最简单的方式, 并且设计了比线程跟更轻量级的 goroutine, 还为 CSP 模型提供了容易使用的 channel 类型, 并将其作为内建类型直接提供
在 Go 语言中, 实现并发编程非常简单, 在函数的执行语句的前面加上 go 关键字, 该函数就会自动生成一个 goroutine 来执行
package main
import (
"fmt"
"net/http"
"time"
)
func getFromBaidu() {
resp, err := http.Get("<http://www.baidu.com>")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.Status)
}
func main() {
go func() {
fmt.Println("Hello from a goroutine")
}() // 并发输出
go getFromBaidu() // 并发访问
time.Sleep(time.Second)
}
在函数和方法的调用前面加上 go, 就会创建一个 goroutine, 这个 goroutine 会加入 Go 调度器的队列进行调度或者执行, 调用者不会被阻塞, 而是会继续执行, 所以避免了程序直接退出. go 语句并不理会函数的返回值的数量和类型.
func add(x, y int) (int error) {
if y == 0 {
return 0, fmt.Errorf("y can not be zero")
}
return x + y, nil
}
func main() {
go add(1, 2)
time.Sleep(time.Second)
}
这样语句会被 go linter 工具检查出不符合要求, 因为 error 并没有被处理. 可以在不想做 golangcli-lint 检查的那一行添加了注释 nolint:
go add(1, 2) // nolint
如果需要处理 error 信息, 则可以使用匿名函数将其封装起来
go func() {
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println(err)
}
}()
go 语句的参数求值和正常的函数/方法调用都是一样的
var list []int
go func(l int) {
time.Sleep(time.Second)
fmt.Printf("passed len: %d, current len: %d\\n", l, len(list))
}(len(list))
list = append(list, 1)
time.Sleep(2 * time.Second)
// passed len: 0, current len: 1
go 语句的这一点和 defer 语句是类似的.
list := []int{1}
foo := func(l int) {
time.Sleep(time.Second)
fmt.Printf("passed len: %d, current len: %d\\n", l, len(list))
}
fmt.Println("before go")
go foo(len(list))
foo = func(l int) {
fmt.Printf("passed len: %d, current len: %d\\n", l*100, len(list)*100)
}
time.Sleep(2 * time.Second)
fmt.Println("after go")
foo(len(list))
// passed len: 1, current len: 1
// passed len: 100, current len: 100
在上面的代码, go 语句执行的还是被修改前的 foo 的值