(五) Go特性
goroutine
goroutine 是由 Go 运行时环境管理的轻量级线程。
go f(x, y, z)开启一个新的 goroutine 执行
f(x, y, z)f, x, y 和 z 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 f。
goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync提供了方法,不过在 Go 中并不经常用到,因为有其他的方法。
goroutine 使用:
func Show(text string) { time.Sleep(time.Millisecond) fmt.Println(text) } func main() { go Show("vim - go") Show("Welcome QuestionMark!") }
channel
channel 是有类型的管道,可以用channel操作符 <- 对其发送或者接收值
("<-" 箭头 就是数据流的方向。)
与 map 和 slice 一样。channel 使用前必须用 make 创建
func Show(ch chan int) { for { v, ok := <-ch if !ok { return } fmt.Println("v := ", v) } } func main() { ch := make(chan int) go Show(ch) ch <- 1 ch <- 2 ch <- 3 time.Sleep(1000 * time.Millisecond) close(ch) fmt.Println("Complate") } /* 执行结果: v := 1 v := 2 v := 3 Complete */默认情况下,在另一端准备好之前,发送和接收都会阻塞,这使得 goroutine 可以在没有明确锁或竞态变量的情况下进行同步。
注意 channel 的接收也是按从右向左的顺序来的 符合编程执行顺序
num, num2 := <-ch, <-ch所以 num2 接收到的值 一定在 num 的前面 从右向左看。
channel 也是可以带缓冲的。为 make 提供的第二参数来声明缓冲长度 并且 初始化一个缓冲
ch := make(chan int, 100)向带缓冲的 chanel 发送数据的时候,只有在缓冲区满的时候才会阻塞。而当缓冲区为空的时候接收操作会阻塞。
func Show(ch chan int) { for { v, ok := <-ch if !ok { return } fmt.Println("v := ", v) time.Sleep(2000 * time.Millisecond) } } func main() { ch := make(chan int, 1) go Show(ch) ch <- 1 ch <- 2 ch <- 3 close(ch) fmt.Println("Complate") }
channel 配合 range 使用
func Show(ch chan int) { for v := range ch { fmt.Println("v := ", v) time.Sleep(2000 * time.Millisecond) } } func main() { ch := make(chan int, 1) go Show(ch) ch <- 1 ch <- 2 ch <- 3 close(ch) fmt.Println("Complate") }在循环中 使用 range 'channel' 会不断从 'channel' 中接收值,直到它被关闭 (close) 。
只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic 。
channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range。
关闭一个 channel 用 close(channel)。
channel 配合 select 使用
func Show(ch chan string, quit chan int) { for { select { case v := <-ch: fmt.Println("ch : ", v) case <-quit: fmt.Println("quit") return default: fmt.Println("No received from the channel value") time.Sleep(1000 * time.Millisecond) } } } func main() { ch := make(chan string) quit := make(chan int) go Show(ch, quit) ch <- "hello" ch <- "QuestionMark" quit <- 1 time.Sleep(1000 * time.Millisecond) }select 语句使得一个 goroutine 在多个通讯操作上等待。
select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。但多个都准备好的时候,会随机选择一个。
select 中的其他条件分支都没有准备好的时候, default 分支会被执行。
为了非阻塞的发送或者接收,可使用 default 分支。
Sync.Mutex
如果在使用 channel 的同时我们需要这些 channel 获取使用共享数据
(比如食堂的故事/比如澡堂的故事/就是各种需要排队的故事咯······)
也就是说我们想保证 channel 拿到的共享数据并且在使用完共享数据的这个过程中 要保证这个共享数据的值是唯一的且不被改变影响的。
这个概念叫做 互斥,通常使用 _互斥锁_(mutex)_ 来提供这个限制。
Go 标准库中提供了 sync_Mutex 类型及两个方法:
Lock
Unlock
如果函数体中需要一直使用共享数据到函数结束的话,则可以用 defer 配合 Unlock 来保证函数结束后一定会执行解锁操作来保证其它的 goroutine 正常执行。
var _IncNum = 0 var _Mux sync.Mutex func Inc() { for { _Mux.Lock() if _IncNum < 1000 { _IncNum++ } else { _Mux.Unlock() return } _Mux.Unlock() time.Sleep(5 * time.Millisecond) } } func PrintIncNum(complateChannel chan int) { for { if !CheckIncNumCondition() { break } time.Sleep(50 * time.Millisecond) } complateChannel <- 1 } func CheckIncNumCondition() bool { defer _Mux.Unlock() _Mux.Lock() return _IncNum < 1000 } func main() { complateCh := make(chan int) go Inc() go Inc() go Inc() go Inc() go Inc() go PrintIncNum(complateCh) if result := <-complateCh; result > 0 { fmt.Println("_IncNum : ", _IncNum) } }