并发编程, 目的就是提高程序的吞吐能力和性能, 能够同时处理很多业务, 而不管这些业务是相同的还是不同的.

为什么并发编程能够提升程序的吞吐能力和性能呢?

Go 特别适合并发编程

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 的值