本文是学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记。介绍Go 语言线程,信道以及互斥锁的概念和使用方法。
1. Go 线程
$GOPATH/src/go_note/gotour/concurrency/goroutine/goroutine.go
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * go 语言线程 */ package main import ( "fmt" ) func say(s string) { for i := 0; i < 2; i++ { fmt.Println(i, s) } } func main() { go say("world") say("hello") } |
Go 线程(goroutine)是由Go 运行时管理的轻量级线程。
1 2 |
go f(x, y, z) |
会启动一个新的 Go 线程程并执行
1 2 |
f(x, y, z) |
其中 f
、 x
、 y
和 z
的求值在当前的 Go 线程中进行,而 f
的执行发生在新的 Go 线程中。
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步,这可以通过使用sync
包或信道实现。
2. 信道
$GOPATH/src/go_note/gotour/concurrency/channel/channel.go
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/** * go 语言信道 */ package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum } func fibonacci(n int, c chan int) { x, y := 0, 1 for i := 0; i < n; i++ { c <- x x, y = y, x+y } close(c) // 关闭队列 } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c // 从 c 中获取数据 fmt.Println(x, y, x+y) // range 与 close f := make(chan int, 10) // 创建带有缓冲区的管道 go fibonacci(cap(f), f) for i := range f { fmt.Println(i) } } |
信道是带有类型的管道,通过信道操作符 <-
来从信道发送或者接收值。
1 2 3 |
ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v。 |
“箭头”就是数据流的方向。
信道在使用前必须创建:
1 2 |
ch := make(chan int) |
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
2.1 带缓冲的信道
信道可以是带缓冲的, 将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:
1 2 |
ch := make(chan int, 100) |
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
2.2 range 和 close
发送者可通过 close
关闭一个信道来表示没有需要发送的值。接收者可以通过为接收表达式的第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,该值为 false
1 2 3 |
v, ok := <-ch // ok == false |
循环 for i := range c
会不断从信道接收值,直到它被关闭。
注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序错误。信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有值需要发送的时候才有必要关闭,如终止一个 range
循环。
3. select 语句
$GOPATH/src/go_note/gotour/concurrency/select/select.go
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * go 语言 select */ package main import ( "fmt" ) // go 线程设置c 管道 func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c<-x: x, y = y, x+y case <-quit: fmt.Println("quit") return default: fmt.Println(" .") } } } func main() { c := make(chan int) quit := make(chan int) // go 线程读取c 管道 go func() { for i:= 0; i < 5; i++{ fmt.Println(<-c) } quit<-0 }() fibonacci(c, quit) } |
select
语句使一个 Go 线程可以等待多个通信操作。
select
会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
当 select
中的其它分支都没有准备好时,default
分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default
分支
4. sync.Mutex
$GOPATH/src/go_note/gotour/concurrency/mutex/mutex.go
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * go 互斥锁 */ package main import ( "fmt" "sync" "time" ) // 并发安全 type SafeCounter struct { v map[string]int mux sync.Mutex } // 增加计数器值 func (c *SafeCounter) Inc(key string) { c.mux.Lock() c.v[key]++ c.mux.Unlock() } // 返回当前计数器值 func (c *SafeCounter) Value(key string) int { c.mux.Lock() defer c.mux.Unlock() // 在Value 函数返回时解锁 return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) //1000 } |
互斥(mutual exclusion),指一次只有一个 Go 线程能够访问一个共享的变量。一般使用互斥锁(Mutex)来提供这种机制。
Go 标准库中提供了 sync.Mutex
互斥锁类型及其两个方法:
1 2 3 |
Lock() Unlock() |
通过在代码前调用 Lock()
方法,在代码后调用 Unlock()
方法来保证一段代码的互斥执行。另外可以用 defer
语句来保证互斥锁一定会被解锁。
参考
可以关注我的微博了解更多信息: @刚刚小码农
有疑问加站长微信联系(非本文作者)