一、goroutine
goroutine是由Go运行时管理的轻量级线程
go f(x, y, z)
会启动一个新的go协程并执行
f(x, y, z)
f, x, y和z的求值发生在当前的go协程中,而f的执行发生在新的go协程中。
go协程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync包提供了这种能力,不过在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")
}
/*
world
hello
hello
world
world
hello
hello
world
world
hello
二、信道
信道是带有类型的管道,可以通过它用信道操作符<-来发送或者接收值。
ch <- v //将v发送至信道ch
v := <- ch //从ch接收值并赋予v
(箭头就是数据流的方向)
和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得goroutine可以在没有显式的锁或静态变量的情况下进行同步。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum //发送sum到c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
/*
对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果*/
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) //-5 17 12
}
1. 带缓冲的信道
信道可以是带缓冲的。将缓冲长度作为第二个参数提供给make来初始化 一个带缓冲的信道:
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
2. 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)
}
}
/*
0
1
1
2
3
5
8
13
21
34
3. select语句
select语句使一个goroutine可以等待多个通信操作。
select会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
package main
import "fmt"
// fibonacci 函数生成斐波那契数列,并通过通道 c 发送出去。
// 当从 quit 通道接收到信号时,函数打印 "quit" 并返回。
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
// 将当前的 x 发送到通道 c
case c <- x:
// 更新 x 和 y 的值
x, y = y, x+y
// 从 quit 通道接收到信号时,打印 "quit" 并返回
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
// 创建一个整型通道 c
c := make(chan int)
// 创建一个整型通道 quit
quit := make(chan int)
// 启动一个新的 goroutine
go func() {
// 循环 10 次,从通道 c 接收值并打印
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
// 向 quit 通道发送信号,表示完成
quit <- 0
}()
// 调用 fibonacci 函数,传入通道 c 和 quit
fibonacci(c, quit)
}
当select中的其他分支都没有准备好时,default分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用default分支:
select { case i := <-c: // 使用 i default: // 从 c 中接收会阻塞时执行 }
三、sync.Mutex
信道已经非常适合在各个goroutine之间进行通信。但如果不需要通信呢?比如说,若我们只想保证每次只有一个goroutine能够访问一个共享的变量,从而避免冲突呢?
此时涉及的概念叫做互斥,通常使用互斥锁(Mutex)来提供这种机制。
Go标准库中提供了sync.Mutex互斥锁类型及两个方法:
Lock
UnLock
可以通过在代码前调用Lock方法,在代码后调用UnLock方法来保证 一段代码的互斥执行。
也可以用defer语句来保证互斥锁一定会被解锁。
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 是并发安全的
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc 对给定键的计数加一
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
c.v[key]++
c.mu.Unlock()
}
// Value 返回给定键的计数的当前值。
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
defer c.mu.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"))
}