goroutine运行

只需在调用前加一个go关键字。例如:

1
2
3
4
5
6
7
8
9
10
11

func main() {
// go 关键字启用goroutine
go sleepyGopher() // 分支线路
time.Sleep(4 * time.Second) // 主线路
}

func sleepyGopher() {
time.Sleep(3 * time.Second)
fmt.Println(" ...snore... ")
}

每次使用go关键字都会产生一个新的goroutine。

表面goroutine似乎在同时运行,事实上由于处理器使用分时技术,并没有真的同时运行。

通道 channel

通道可以在多个goroutine之间安全的传值。

作出发送操作的goroutine会一直等待直到有另一个goroutine在通道内作出接收值操作。

同样的,作出接收操作的goroutine也会一直等待直到有另一个goroutine在通道内尝试向该通道进行发送操作为止。等待保证了

看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

func main() {
c := make(chan int)
for i := 0; i < 5; i++ {
go sleepyGopher(i, c)
}

for i := 0; i < 5; i++ {
// 等待有goroutine发送值
gopherID := <-c
fmt.Println("gopher ", gopherID, " has finished sleeping")
}
}

func sleepyGopher(id int, c chan int) {
time.Sleep(3 * time.Second)
fmt.Println(" ... ", id, " snore ... ")
// 结束即发送值
c <- id
}

b6970940-27ea-4682-8ce1-08e7ed1aaf13

使用 select 处理多个通道

  1. 等待不同类型的值。
  2. time.After 函数,返回一个通道,该通道在指定时间后会接收到一个
    值(发送该值的 goroutine 是 Go 运行时的一部分)
  3. select 和 switch 有点像。
    该语句包含的每个 case 都持有一个通道,用来发送或接收数据。
    select 会等待直到某个 case 分支的操作就绪,然后就会执行该 case 分支。

一个常见的模式:

1
2
3
4
5
6
7
8
9
10
timeout := time.After(2 * time.Second)
for i := 0; i < 5; i++ {
select {
case gopherID := <-c:
fmt.Println("gopher ", gopherID, " has finished sleeping")
case <-timeout:
fmt.Println("my patience ran out")
return
}
}

注意:即使已经停止等待goroutine,但只要main函数还没返回,仍在运行的goroutine将会继续占用内存。

select语句在不包含case的情况下将永远等下去。

nil通道

  • 如果不使用 make 初始化通道,那么通道变量的值就是 nil(零值)
  • 对 nil 通道进行发送或接收不会引起 panic,但会导致永久阻塞。
  • 对 nil 通道执行 close 函数,那么会引起 panic
  • nil 通道的用处:
    • 对于包含 select 语句的循环,如果不希望每次循环都等待 select 所涉及的所有通道,那么可以先将某些通道设为 nil,等到发送值准备就绪之后,再将通道变成一个非 nil 值并执行发送操作。

阻塞和死锁

  • 当 goroutine 在等待通道的发送或接收时,我们就说它被阻塞了。
  • 除了 goroutine 本身占用少量的内存外,被阻塞的 goroutine 并不消耗任何其它资源。
    • goroutine 静静的停在那里,等待导致其阻塞的事情来解除阻塞。
  • 当一个或多个 goroutine 因为某些永远无法发生的事情被阻塞时,我们称这种情况为死锁。而出现死锁的程序通常会崩溃或挂起。

下面两行代码将引发死锁:

1
2
3
4
func main() {
c := make(chan int)
<- c
}

Go 允许在没有值可供发送的情况下通过 close 函数关闭通道。例如close(c)

尝试读取被关闭的通道会获得与通道类型对应的零值。

执行以下代码可得知通道是否被关闭:v, ok := <- c;

如果ok为false说明通道关闭了。

可以利用 defer 让 goroutine 在函数结束前都得到处理。

常用模式

  • 从通道里面读取值,直到它关闭为止。
    • 可以使用 range 关键字达到该目的。

Go的互斥锁(mutex

互斥锁定义在被保护的变量之上

互斥锁的隐患

  • 死锁
  • 为保证互斥锁的安全使用,我们须遵守以下规则:
    • 尽可能的简化互斥锁保护的代码
    • 对每一份共享状态只使用一个互斥锁

互斥锁适用于比较简单的共享状态,面对复杂情况时应该使用更强有力的工具来保证并发的安全。

2022-03-13
Contents

⬆︎TOP