Go语言学习之旅 4 - Go 的并发原语
Go语言学习笔记 3 - Go 的并发原语
概述
连续三节的内容如下:
- 第一节覆盖了基本语法及数据结构
- 第二节讨论了方法与接口
- 第三节则简单介绍了 Go 的并发原语。
Go 程
Go 程(goroutine)是由 Go 运行时管理的轻量级线程。
go 函数名
会启动一个新的 Go 程并执行这个函数。
比如:
go say("world") // 将启动一个线程来执行 say 函数
这里说"线程"其实不严谨,不过便于理解。
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。
sync 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法。
func say(s string){
for i:=0;i<5;i++ {
//fmt.Println(time.Millisecond)
time.Sleep(100 * time.Millisecond);
fmt.Println(s,i)
}
}
func main() {
go say("jack")
say("lucy")
}
信道
信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
(“箭头”就是数据流的方向。)
和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
示例:
func su(ch chan int){
ch <- 1
ch <- 2
}
func main() {
ch := make(chan int)
go su(ch)
x := <- ch
y := <- ch
fmt.Println(x,y)
}
带缓冲的信道
信道可以是 带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
func main() {
ch := make(chan int,2)
ch <- 1
ch <- 2// 缓冲区是2,此时还未堵塞。当再次写入时,就会阻塞。
x := <- ch
y := <- ch
fmt.Println(x,y)
}
range 和 close
发送者可通过 close 关闭一个信道,它表示没有需要发送的值了。
接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
v, ok := <-ch
之后 ok 会被设置为 false。
这样写个循环:
for i := range c //会不断从信道接收值,直到它被关闭。
注意:
- 只有发送者才能关闭信道,而接收者不能。
- 向一个已经关闭的信道发送数据会引发程序崩溃。
注意:
- 信道与文件不同,通常情况下无需关闭它们。
- 只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。
select 语句
select 语句使一个 Go 程可以等待多个通信操作。
select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
默认选择
当 select 中的其它分支都没有准备好时,default 分支就会执行。
为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:
select {
case i := <-c:
// 使用 i
default:
// 从 c 中接收会阻塞时执行
}
sync.Mutex
我们已经看到信道非常适合在各个 Go 程间进行通信。
但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
这里涉及的概念叫做 互斥(mutualexclusion)* ,我们通常使用 互斥锁(Mutex) 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
- Lock
- Unlock
我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。
我们也可以用 defer 语句来保证互斥锁一定会被解锁。
更多学习资料
http://docscn.studygolang.com/doc/
https://go-zh.org/#
https://tour.go-zh.org/list
END