Go的并发
有的地方称为协程
Go 语言可以通过 go 关键字来开启 goroutine 即可实现多线程的并发任务处理。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
- 我们通过这一个实例认识一下:
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")
}
在运行结果中,hello和word是随机出现的,说明并发模式下执行的语句没有先后顺序,并且每次运行的结果都是不同的
通道(channel)
通道(channel)是用来传递数据的一个数据结构。
- 通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
- 操作符 <- 用于指定通道的方向,发送或接收。
- 如果未指定方向,则为双向通道。
ch <- v
// 把 v 发送到通道 ch
v := <-ch
// 从 ch 接收数据 、并把值赋给 v
创建一个’通道’可以使用关键字chan,并且在使用前必须创建
一个实例:
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}
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)
}
这段代码的效果是通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和
缓冲信道(Buffer Channels)
我们可以创建一个有缓冲(Buffer)的信道。只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。
- 通过向 make 函数再传递一个表示容量的参数(指定缓冲的大小),可以创建缓冲信道。
ch := make(chan type, capacity)
- 要让一个信道有缓冲, capacity(容量) 应该大于 0。无缓冲信道的容量默认为 0。
这个例子创建了一个缓冲信道。
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
fmt.Println(<- ch)
fmt.Println(<- ch)
}
在上面程序里的第 9 行,我们创建了一个缓冲信道,其容量为 2。由于该信道的容量为 2,因此可向它写入两个字符串,而且不会发生阻塞。在第 10 行和第 11 行,我们向信道写入两个字符串,该信道并没有发生阻塞。我们又在第 12 行和第 13 行分别读取了这两个字符串。
该程序输出结果为
naveen
paul
下面缓冲信道的实例中有一个并发的 Go 协程来向信道写入数据,而 Go 主协程负责读取数据。这个例子解释了向缓冲信道写入数据时,什么时候会发生阻塞。
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(2 * time.Second)
}
}
在上面的程序中,在Go 主协程中创建了容量为 2 的缓冲信道 ch,而第 17 行把 ch 传递给了 write 协程。接下来 Go 主协程休眠了两秒。在这期间,write 协程在并发地运行。write 协程有一个 for 循环,依次向信道 ch 写入 0~4。而缓冲信道的容量为 2,因此 write 协程里立即会向 ch 写入 0 和 1,接下来发生阻塞,直到 ch 内的值被读取。因此,该程序立即打印出下面两行:
successfully wrote 0 to ch
successfully wrote 1 to ch
打印上面两行之后,write 协程中向 ch 的写入发生了阻塞,直到 ch 有值被读取到。而 Go 主协程休眠了两秒后,才开始读取该信道,因此在休眠期间程序不会打印任何结果。主协程结束休眠后,在第 19 行使用 for range 循环,开始读取信道 ch,打印出了读取到的值后又休眠两秒,这个循环一直到 ch 关闭才结束。所以该程序在两秒后会打印下面两行:
successfully wrote 0 to ch
successfully wrote 1 to ch
- 该过程会一直进行,直到信道读取完所有的值,并在 write 协程中关闭信道。最终输出如下:
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch
长度与容量
-
缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。
-
缓冲信道的长度是指信道中当前排队的元素个数。
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch))
fmt.Println("length is", len(ch))
fmt.Println("read value", <-ch)
fmt.Println("new length is", len(ch))
}
在这个程序中,我们创建了一个容量为 3 的信道,于是它可以保存 3 个字符串。
接下来分别在第 9 行和第 10行向信道写入了两个字符串。于是信道有两个字符串排队,因此其长度为 2。
在第13行,又从信道读取了一个字符串。现在该信道内只有一个字符串,因此其长度变为 1。
该程序会输出:
capacity is 3
length is 2
read value naveen
new length is 1