在学习Go语言的入门课程,简单记录一些课程中的内容
在线学习教程 https://tour.golang.org/
第三部分 并发
3.1 协程(Goroutine)
goroutine是由Go运行时管理的轻量级线程,使用goroutine来实现并发。
go f(x, y, z)
启动一个新的goroutine运行
f(x, y, z)
f、x、y 和 z 的计算发生在当前 goroutine 中,而f的执行发生在新goroutine中。Goroutine 运行在相同的地址空间中,因此对共享内存的访问必须同步,sync 包提供了有用的原语。
Channels
Channels是一种类型化的管道,可以通过它使用通道运算符 '<-' 发送和接收值。
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
与map和slice一样,通道必须在使用前创建:
ch := make(chan int)
默认情况下,发送端和接收端会阻塞直到另一端准备好。 这允许goroutine在没有显式锁或条件变量的情况下进行同步。
如下的示例代码对切片中的数字求和,在两个goroutine之间分配工作。 一旦两个goroutine都完成了它们的计算,它就会计算最终结果。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to 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 // receive from c
fmt.Println(x, y, x+y)
}
通道可以被缓冲(Buffered Channels)。 第二个参数作为初始化缓冲通道时缓冲区的长度
ch := make(chan int, 100)
当缓冲区满时发送到缓冲通道块,当缓冲区为空时接收块。
Range and Close
发送方可以关闭通道以指示不再发送值。接收方可以通过为接收表达式分配第二个参数来测试通道是否已关闭
v, ok := <-ch
如果没有更多要接收的值并且通道已关闭,则ok为假。
for i := range c
循环重复地从通道接收值,直到它关闭。注意:只有发送方应该关闭通道,而接收方不可以。
注意:通道与文件不同,通常不需要关闭它们。 仅当必须告诉接收方没有更多值时才需要关闭,例如终止range循环。
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)
}
}
3.2 Select语句
select语句让goroutine等待多个通信操作。select会阻塞直到它的一个case可以运行,然后它会执行那个case。 如果有多个准备好,它会随机选择一个。
func fibonacci(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
}()
fibonacci(c, quit)
}
如果没有其他case准备好,则运行选择中的默认case。使用默认情case试在不阻塞的情况下发送或接收
select {
case i := <-c:
// use i
default:
// receiving from c would block
}
例如
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!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
3.3 sync.Mutex
通道对于goroutines之间的通信非常有用,但如果只想确保一次只有一个goroutine可以访问一个变量以避免冲突,这个概念称为互斥(Mutex)。
Go的标准库通过sync.Mutex及其两种方法(lock, unlock)提供互斥功能。
可以通过调用Lock和Unlock来定义要互斥执行的代码块,如下面的Inc方法所示。还可以使用defer来确保互斥锁将像Value方法一样被解锁。
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map 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"))
}