go-并发
Go程
Go程(goroutine)是由Go运行时管理的轻量级线程。
go f(x,y,z)
会启动一个新的Go程并执行
f(x,y,z)
f,x,y和z的求值发生在当前的Go程中,而f的执行发生在新的Go程中。
例子:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
信道
信道是带有类型的管道,你可以通过它用信道操作符<-来发送或者接收值。
ch <- v //将v发送至信道 ch
v := <-ch //从ch接收值并赋予 v
“箭头”就是数据流的方向。
和映射与切片一样,信道在使用前必须创建:
ch :=make(chan int)
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得Go程可以在没有显示的锁或竞态量的情况下进行同步。
例子:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
//将sum发送至管道c
c <- sum
}
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)
// 一旦两个Go程完成了它们的计算,它就能算出最终的结果
x, y := <-c, <-c
fmt.Println(x, y, x+y)
}
带缓冲的信道
信道是可以缓冲的。将缓冲长度作为第二个参数提供给make来初始化一个带缓冲的信道:
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
例子:
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
有了缓冲区就可以在同一个协程写读。
range和close
发送者可通过close关闭一个信道来表示没有需要发送的值了。接受者可以通过为接收表达式分配第二个参数来测试是否关闭;若没有值可以接收且信道被关闭,那么在执行完
v,ok := <- ch
之后ok会被设置为false。
循环for i := range c会不断从信道接收值,直到它被关闭。
只有发送者才能关闭信道,而接受者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再需要发送的值时才有必要关闭,例如终止一个 range
循环。
例子:
package main
import "fmt"
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() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
select 语句
select 语句使用一个Go程可以等待多个通信操作。
select会阻塞到某个分支可以继续执行为止,这时救护执行该分支。当多个分支都准备好时随机选择一个执行。
package main
import "fmt"
func fibonacci1(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci1(c, quit)
}
默认选择
当select中的其他分支都没准备好时,default分支就会执行。
为了在尝试发送或者接收时不发送阻塞,可以使用default分支:
select {
case i := <-c:
// 使用 i
default:
// 从 c 中接收会阻塞时执行
}
例子:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
sync.Mutex
我们已经看到信道非常合适在各个Go程间进行通信。
但是如果我们并不需要通信呢?比如说,若我们只想保证每次只有一个Go程能够访问一个共享的变量,从而避免冲突?
这里涉及的概念叫做 互斥(mutualexclusion) ,我们通常使用 互斥锁(Mutex) 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex
互斥锁类型及其两个方法:
Lock
Unlock
我们可以通过在代码前调用 Lock
方法,在代码后调用 Unlock
方法来保证一段代码的互斥执行。参见 Inc
方法。
我们也可以用 defer
语句来保证互斥锁一定会被解锁。参见 Value
方法。
例子:
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()
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"))
}
转载自:https://juejin.cn/post/7128017689473187876