UP | HOME

并发

Table of Contents

goroutine

goroutine 是由 Go 运行时环境管理的轻量级线程

f,x,y 和 z 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 f

//开启一个新的 goroutine 执行f(x, y, z)
go f(x, y, z)

goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}
// hello
// world
// world
// hello
// hello
// world
// world
// hello
// hello
func main() {
        go say("world")
        say("hello")
}

channel

channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值,箭头方向代表数据接收者

// 将 v 送入 channel ch
ch <- v
// 从 ch 接收,并且赋值给 v
v := <-ch

和 map 与 slice 一样,channel 使用前必须创建

ch := make(chan int)

默认情况下,在另一端准备好之前发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步

func sum(a []int, c chan int) {
        sum := 0
        for _, v := range a {
                sum += v
        }
        c <- sum // 将和送入 c
}

func main() {
        a := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(a[:len(a)/2], c)
        go sum(a[len(a)/2:], c)
        x, y := <-c, <-c // 从 c 中获取
        //-5 17 12
        fmt.Println(x, y, x+y)
}

缓冲

channel 可以是带缓冲的

为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel

ch := make(chan int, 100)

向带缓冲的 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞,而当缓冲区为空的时候接收操作会阻塞。

ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)

如果buff满了,会抛出“fatal error: all goroutines are asleep - deadlock!”

range和close

发送者可以 close 一个 channel 来表示再没有值会被发送了

接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭

//当没有值可以接收并且 channel 已经被关闭,那么ok会被设置为 false。
v, ok := <-ch
//循环会不断从 channel 接收值,直到它被关闭
for i := range c
  • 只有发送者才能关闭 channel,而不是接收者!向一个已经关闭的 channel 发送数据会引起 panic!
  • channel 与文件不同,通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭!例如中断一个 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)
}
// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        for i := range c {
                fmt.Println(i)
        }
}

select

select 语句使得一个 goroutine 在多个通讯操作上等待

select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个

func fibonacci(c, quit chan int) {
        x, y := 0, 1
        for {
                //从c 和 quit两个channel分别读取数据,哪一个有数据就执行哪段代码
                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)
}

默认select

当 select 中的其他条件分支都没有准备好的时候,default 分支会被执行

//为了非阻塞的发送或者接收,可使用 default 分支
select {
case i := <-c:
        // 使用 i
default:
        // 从 c 读取会阻塞
}
//     .
//     .
// tick.
//     .
//     .
// tick.
//     .
//     .
// tick.
//     .
//     .
// tick.
//     .
//     .
// tick.
// BOOM!
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)
        }
}

互斥锁

Go 标准库中提供了 sync.Mutex 类型及其两个方法: Lock Unlock

可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
        v   map[string]int
        mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
        c.mux.Lock()
        // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
        c.v[key]++
        c.mux.Unlock()
}

可以用 defer 语句来保证互斥锁一定会被解锁

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
        c.mux.Lock()
        // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
        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)
        //1000
        fmt.Println(c.Value("somekey"))
}

Previous:接口和方法

Home:目录