目录

一、goroutine

        goroutine 是 Go 语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU 。
        Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine;所有 goroutine 在 main() 函数结束时会一同结束。

1. 创建 goroutine

        Go 程序中使用 go 关键字为一个函数创建一个 goroutine 。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。

(1)格式

        为一个函数创建 goroutine 的写法如下:

go 函数名(参数列表)
  • 使用 go 关键字创建 goroutine 时,被调用函数的返回值会被忽略;
  • 如果需要在 goroutine 中返回数据,需要使用通道(channel)特性,通过通道把数据从 goroutine 中作为返回值传出。

(2)示例

func main(){
	go run()
	for i := 0; i < 3; i++ {
		fmt.Println(i)
	}
}

func run(){
	fmt.Println("I'm running.")
}
// 第一次运行结果:
0
1
I'm running.
2
// 第二次运行结果:
0
I'm running.
1
2

        该例中,Go 程序在启动时,运行时(runtime)会默认为 main() 函数创建一个 goroutine 。在 main() 函数的 goroutine 中执行到 go run() 语句时,归属于 run() 函数的 goroutine 被创建,run() 函数开始在自己的 goroutine 中执行。此时,main() 继续执行,两个 goroutine 通过 Go 程序的调度机制同时运作。

2. 协程管理

func main(){
	var wg sync.WaitGroup	// 协程管理器
	wg.Add(2)	// 有2个协程
	go run(&wg)
	go run(&wg)
	// 让协程管理器执行等待,等待 wg.Add(2) 的数字减到 0 才结束
	wg.Wait()
}

func run(wg *sync.WaitGroup){
	fmt.Println("I'm running.")
	wg.Done()	// 关闭协程
}

/*
输出结果:
I'm running.
I'm running.
*/
二、channel

        channel 是 goroutine 之间通信的一种方式,其本身还需关联一个类型,也就是 channel 可以发送数据的类型(例如:发送 int 类型消息的 channel 写作 chan int )。

1. channel 的创建

// 声明一个 chan int 类型的 channel
ch := make(chan int)    // 可读可写无缓存
// 声明一个 chan int 类型的 channel
ch := make(chan int, 10)    // 可读可写有缓存

        channel 和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true 。

2. channel 的类型

(1)无缓存的 channel

        从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。

(2)有缓存的 channel

        有缓存的 channel 类似一个循环队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。

        通过 len() 函数可以获得 channel 中的元素个数,通过 cap() 函数可以获得 channel 的缓存长度。

(3)单向 channel

c := make(chan int, 7)
// 以下两个单向 channel 共享 c,主要是在声明方法时使用,防止 channel 的滥用
var readc <-chan int = c    // readc 是属于 c 的只可读取的 channel
var writec chan<- int = c    // writec 是属于 c 的只可写入的 channel

3. channel 的读写操作

c := make(chan int)
go func() {
	for i := 0; i < 10; i++ {
		c <- i // 写
	}
}()
for i := 0; i < 10; i++{
	fmt.Printf("%d ", <-c) // 读
}

/*
输出结果:
0 1 2 3 4 5 6 7 8 9
*/

4. channel 的关闭

ch := make(chan int)
// golang 提供了内置的 close 函数对 channel 进行关闭操作
close(ch)

注意:

  1. 关闭一个未初始化(nil)的 chennel 会产生 panic;
  2. 同一个 channel 只能关闭一次,重复关闭会产生 panic;
  3. 向一个已关闭的 channel 发送消息会产生 panic;
  4. 从已关闭的 channel 中读取消息永远不会产生 panic,能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的默认值,此时还会返回一个为 false 的 ok-idiom(多返回值中用一个名为 ok 的布尔值来标记操作是否成功),可以用来判断 channel 是否关闭;
  5. 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息。
// 注意的第4点
c := make(chan int, 2)
c <- 7
close(c)
val1, ok1 := <-c
fmt.Println(val1, ok1)	// 7 true
val2, ok2 := <-c
fmt.Println(val2, ok2)	// 0 false

5. channel 的遍历

        对 channel 使用 range 遍历时,会一直从 channel 读取消息,直到有 goroutine 对该 channel 使用 close() 操作,循环才会结束,也可以在遍历前先关闭 channel 。

c := make(chan int, 3)
c <- 0
c <- 1
c <- 2
close(c)
for v := range c {
	fmt.Printf("%d ", v)    // 0 1 2 
}

6. channel 与 select 配合使用

select {
    case <- ch1:
    ...
    case <- ch2:
    ...
    case ch3 <- 7:
    ...
    default:
    ...
}
  • select 可以同时监听多个 channel 的读取或写入;
  • 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个 case 块;
  • 若有多个 case 通过,则随即挑选一个 case 执行;
  • 当所有 case 均阻塞时,若定义了 default ,则执行 default 块;若未定义 default ,则 select 语句阻塞,直到有 case 被唤醒;
  • 可以使用 break 跳出 select 块。

7. 通过 channel 实现 goroutine 的通信

func main(){
	c := make(chan int, 3)
	var readc <-chan int = c
	var writec chan<- int = c
	go SetChan(writec)
	GetChan(readc)
}

func SetChan(writec chan<-int){
	for i := 0; i < 3; i++ {
		writec <- i
	}
}
func GetChan(readc <-chan int){
	for i := 0; i < 3; i++ {
		fmt.Printf("%d ", <-readc)
	}
}

/*
输出结果:
0 1 2 
*/