Go 之旅五: 并发

作者: 好刚编程 | 来源:发表于2017-02-28 19:14 被阅读0次

    原文链接 http://ironxu.com/713

    本文是学习 A Tour of Go (中文参考 Go 之旅中文 ) 整理的笔记。介绍Go 语言线程,信道以及互斥锁的概念和使用方法。

    1. Go 线程

    $GOPATH/src/go_note/gotour/concurrency/goroutine/goroutine.go 源码如下

    /**
     * go 语言线程
     */
    package main
    import (
        "fmt"
    )
    
    func say(s string) {
        for i := 0; i < 2; i++ {
            fmt.Println(i, s)
        }
    }
    
    
    func main() {
        go say("world")
        say("hello")
    }
    

    Go 线程(goroutine)是由Go 运行时管理的轻量级线程。

    go f(x, y, z)
    

    会启动一个新的 Go 线程程并执行

    f(x, y, z)
    

    其中 fxyz 的求值在当前的 Go 线程中进行,而 f 的执行发生在新的 Go 线程中。

    Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步,这可以通过使用sync 包或信道实现。

    2. 信道

    $GOPATH/src/go_note/gotour/concurrency/channel/channel.go 源码如下

    /**
     * go 语言信道
     */
    package main
    
    import "fmt"
    
    func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
            sum += v
        }
        c <- sum
    }
    
    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() {
        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)
    
        // range 与 close
        f := make(chan int, 10) // 创建带有缓冲区的管道
        go fibonacci(cap(f), f)
        for i := range f {
            fmt.Println(i)
        }
    }
    

    信道是带有类型的管道,通过信道操作符 <- 来从信道发送或者接收值。

    ch <- v    // 将 v 发送至信道 ch。
    v := <-ch  // 从 ch 接收值并赋予 v。
    

    “箭头”就是数据流的方向。

    信道在使用前必须创建:

    ch := make(chan int)
    

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

    2.1 带缓冲的信道

    信道可以是带缓冲的, 将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:

    ch := make(chan int, 100)
    

    仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

    2.2 range 和 close

    发送者可通过 close 关闭一个信道来表示没有需要发送的值。接收者可以通过为接收表达式的第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,该值为 false

    v, ok := <-ch
    // ok == false
    

    循环 for i := range c 会不断从信道接收值,直到它被关闭。

    注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序错误。信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有值需要发送的时候才有必要关闭,如终止一个 range 循环。

    3. select 语句

    $GOPATH/src/go_note/gotour/concurrency/select/select.go 源码如下:

    /**
     * go 语言 select
     */
    package main
    import (
        "fmt"
    )
    
    // go 线程设置c 管道
    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
            default:
                fmt.Println("    .")
            }
        }
    }
    
    
    func main() {
        c := make(chan int)
        quit := make(chan int)
        // go 线程读取c 管道
        go func() {
            for i:= 0; i < 5; i++{
                fmt.Println(<-c)
            }
            quit<-0
        }()
        fibonacci(c, quit)
    }
    

    select 语句使一个 Go 线程可以等待多个通信操作。

    select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。

    select 中的其它分支都没有准备好时,default 分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default 分支

    4. sync.Mutex

    $GOPATH/src/go_note/gotour/concurrency/mutex/mutex.go 源码如下:

    /**
     * go 互斥锁
     */
    
    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    // 并发安全
    type SafeCounter struct {
        v   map[string]int
        mux sync.Mutex
    }
    
    // 增加计数器值
    func (c *SafeCounter) Inc(key string) {
        c.mux.Lock()
        c.v[key]++
        c.mux.Unlock()
    }
    
    // 返回当前计数器值
    func (c *SafeCounter) Value(key string) int {
        c.mux.Lock()
        defer c.mux.Unlock() // 在Value 函数返回时解锁
        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")) //1000
    }
    

    互斥(mutual exclusion),指一次只有一个 Go 线程能够访问一个共享的变量。一般使用互斥锁(Mutex)来提供这种机制。

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

    Lock()
    Unlock()
    

    通过在代码前调用 Lock() 方法,在代码后调用 Unlock() 方法来保证一段代码的互斥执行。另外可以用 defer 语句来保证互斥锁一定会被解锁。

    参考

    可以关注我的微博了解更多信息:

    @刚刚小码农

    相关文章

      网友评论

        本文标题:Go 之旅五: 并发

        本文链接:https://www.haomeiwen.com/subject/kbqfgttx.html