Skip to content

Concurrency-语法篇 #12

@oufeng

Description

@oufeng
  • goroutine

    goroutine是轻量级的线程,在相同的内存空间中运行;关键字是go

    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")
    }
    
  • Channels

    Channels可以在goroutine传递数据;应用上与nodeawait相似

    Channels必须receive (<- chan)sends (chan <-)共同存在才有意义

    By default channels are unbuffered, which states that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) which are ready to receive the sent value. This allows goroutines to synchronize without explicit locks or condition variables.

    package main
    
    import "fmt"
    
    func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
            fmt.Println(v)
            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

    设置缓冲区,允许多次sends (chan <-)再多次receive (<- chan)

    package main
    
    import "fmt"
    
    func main() {
        ch := make(chan int, 2)
        ch <- 1
        ch <- 2
        fmt.Println(<-ch)
        fmt.Println(<-ch)
    }
    
  • Range and Close

    sender可以调用close告知channel没有数据要发送了

    Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression

    v, ok := <-ch
    

    The loop for i := range c receives values from the channel repeatedly until it is closed.

    package main
    
    import (
        "fmt"
    )
    
    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)
        }
    }
    

    只有sender可以关闭通道,receiver不可以关闭;在已经关闭的通道发送数据会引发panic

    Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

  • Select

    select用来监听和channel有关的IO操作,当IO操作发生时,触发相应的动作

    监听cquit两个channel

    package main
    
    import "fmt"
    
    func fibonacci(c, quit chan int) {
        x, y := 0, 1
        for {
            select {
            // send x to c
            case c <- x:
                x, y = y, x+y
            // receive from quit
            case <-quit:
                fmt.Println("quit")
                return
            }
        }
    }
    
    func main() {
        c := make(chan int)
        quit := make(chan int)
        go func() {
            for i := 0; i < 10; i++ {
                // receive from c
                fmt.Println(<-c)
            }
            // send 0 to quit
            quit <- 0
        }()
        fibonacci(c, quit)
    }
    
  • Default Selection

    The default case in a select is run if no other case is ready

    package main
    
    import (
        "fmt"
        "time"
    )
    
    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)
            }
        }
    }
    
  • Exercise: Equivalent Binary Trees

    package main
    
    import "fmt"
    import "code.google.com/p/go-tour/tree"
    
    // Walk walks the tree t sending all values
    // from the tree to the channel ch.
    func Walk(t *tree.Tree, ch chan int) {
        var walker func(t *tree.Tree)
        walker = func (t *tree.Tree) {
            if (t == nil) {
                return
            }
            walker(t.Left)
            ch <- t.Value
            walker(t.Right)
        }
        walker(t)
        close(ch)
    }
    
    // Same determines whether the trees
    // t1 and t2 contain the same values.
    func Same(t1, t2 *tree.Tree) bool {
        ch1, ch2 := make(chan int), make(chan int)
    
        go Walk(t1, ch1)
        go Walk(t2, ch2)
    
        for {
            v1, ok1 := <-ch1
            v2, ok2 := <-ch2
    
            if !ok1 && !ok2 {  // no more nodes in trees
                break
            }
            if ok1 != ok2 {    // trees with different number of nodes
                return false
            }
            if v1 != v2 {
                return false
            }
        }
    
        return true
    }
    
    func main() {
        fmt.Println("1 and 1 same: ", Same(tree.New(1), tree.New(1)))
        fmt.Println("1 and 2 same: ", Same(tree.New(1), tree.New(2)))
    
    }
    
  • sync.Mutex

    What if we just want to make sure only one goroutine can access a variable at a time to avoid conflicts?

    如下,变量a的值并不会依次递增输出。因为goroutine共享内存

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        var a = 0
        for i := 0; i < 1000; i++ {
            go func(idx int) {
                a++
                fmt.Println(a)
            }(i)
        }
        time.Sleep(time.Second)
    }
    

    加上锁就能确保依次递增输出

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        var a = 0
        var lock sync.Mutex
        for i := 0; i < 1000; i++ {
            go func(idx int) {
                lock.Lock()
                defer lock.Unlock()
                a++
                fmt.Println(a)
            }(i)
        }
        time.Sleep(time.Second)
    }
    

    更优雅方案。换用channel方案,同时不用Sleep

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a = 0
        ch := make(chan int)
        for i := 0; i < 1000; i++ {
            go func(idx int, ch chan int) {
                a++
                ch <- a
            }(i, ch)
            fmt.Println(<-ch)
        }
    }
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions