• 跟着实例学Go语言(二)


    本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,既适合新人快速上手,也适合工作中遇到问题速查知识点。
    教程代码示例来自go by example,文字部分来自本人自己的理解。

    本文是教程系列的第二部分,共计20个例子、约1.2万字。

    系列文章快速跳转:
    跟着实例学Go语言(一)
    跟着实例学Go语言(二)
    跟着实例学Go语言(三)
    跟着实例学Go语言(四)

    21. Interfaces

    下面的例子展示了用interface关键字定义接口。接口是一组方法签名的集合,用于实现多态。Go不像其他语言那样需要通过extend关键字显式指定类型的继承关系。如果一个struct类型实现了interface中定义的所有方法,那么就认为这个struct类型属于interface所定义的类型。传统的面向对象语言逻辑是:A is B if it’s a child of B;而Go的逻辑是:A is B if it acts like B。

    package main
    
    import (
        "fmt"
        "math"
    )
    
    // 定义了interface:几何体,以及方法:计算面积、计算周长
    // 定义顺序:type、interface名、interface
    type geometry interface {
        area() float64
        perim() float64
    }
    
    // 定义了struct:矩形和圆形,分别实现了geometry定义的所有接口
    type rect struct {
        width, height float64
    }
    type circle struct {
        radius float64
    }
    
    func (r rect) area() float64 {
        return r.width * r.height
    }
    func (r rect) perim() float64 {
        return 2*r.width + 2*r.height
    }
    
    func (c circle) area() float64 {
        return math.Pi * c.radius * c.radius
    }
    func (c circle) perim() float64 {
        return 2 * math.Pi * c.radius
    }
    
    func measure(g geometry) {
        fmt.Println(g)
        fmt.Println(g.area())
        fmt.Println(g.perim())
    }
    
    func main() {
        r := rect{width: 3, height: 4}
        c := circle{radius: 5}
    
    	// 矩形和圆形都可以作为几何体类型的参数传递
        measure(r)
        measure(c)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    $ go run interfaces.go
    {3 4}
    12
    14
    {5}
    78.53981633974483
    31.41592653589793
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    22. Struct Embedding

    下面的例子展示了结构体嵌套的做法。Go允许struct或者interface的嵌套使用,从而组合成更复杂的结构。这有点像Java中的外部类和内部类,外部结构和内部结构可以互相访问对方的字段和方法。这也可以看做是Go通过组合的方式来实现其他语言中的继承。

    package main
    
    import "fmt"
    
    // 定义了基础类型
    type base struct {
        num int
    }
    
    func (b base) describe() string {
        return fmt.Sprintf("base with num=%v", b.num)
    }
    
    // 定义了包装类型
    type container struct {
    	// 包装类型中包含了基础类型
        base
        str string
    }
    
    func main() {
    
        co := container{
            base: base{
                num: 1,
            },
            str: "some name",
        }
    
    	// num变量可以被直接或间接访问
        fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)
    
        fmt.Println("also num:", co.base.num)
    
        fmt.Println("describe:", co.describe())
    
        type describer interface {
            describe() string
        }
    
        var d describer = co
        fmt.Println("describer:", d.describe())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    $ go run struct-embedding.go
    co={num: 1, str: some name}
    also num: 1
    describe: base with num=1
    describer: base with num=1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    23. Generics

    泛型是Go 1.18版本引入的有争议但很有用的特性。泛型可以看做是类型参数,经常与map、slice等数据结构伴随使用。泛型用中括号表示,而不是其他语言中常用的尖括号。

    package main
    
    import "fmt"
    
    // 泛型声明放在函数名后或者类型名后,可以为泛型参数指定要实现的接口名
    func MapKeys[K comparable, V any](m map[K]V) []K {
        r := make([]K, 0, len(m))
        for k := range m {
            r = append(r, k)
        }
        return r
    }
    
    type List[T any] struct {
        head, tail *element[T]
    }
    
    type element[T any] struct {
        next *element[T]
        val  T
    }
    
    func (lst *List[T]) Push(v T) {
        if lst.tail == nil {
            lst.head = &element[T]{val: v}
            lst.tail = lst.head
        } else {
            lst.tail.next = &element[T]{val: v}
            lst.tail = lst.tail.next
        }
    }
    
    func (lst *List[T]) GetAll() []T {
        var elems []T
        for e := lst.head; e != nil; e = e.next {
            elems = append(elems, e.val)
        }
        return elems
    }
    
    func main() {
        var m = map[int]string{1: "2", 2: "4", 4: "8"}
    
        fmt.Println("keys:", MapKeys(m))
    
        _ = MapKeys[int, string](m)
    
        lst := List[int]{}
        lst.Push(10)
        lst.Push(13)
        lst.Push(23)
        fmt.Println("list:", lst.GetAll())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    $ go run generics.go
    keys: [4 1 2]
    list: [10 13 23]
    
    • 1
    • 2
    • 3

    24. Errors

    Go提供了error和panic两种异常处理的方式。不同点在于,前者用于意料中的错误,通过函数返回值获取;后者用于意料外的严重错误,会中断程序执行,但可用recover捕捉恢复。Go建议对于一般错误,尽量使用error来处理。

    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func f1(arg int) (int, error) {
        if arg == 42 {
    		// 使用errors.New()生成新的error,并作为第二个值返回
            return -1, errors.New("can't work with 42")
    
        }
    
        return arg + 3, nil
    }
    
    type argError struct {
        arg  int
        prob string
    }
    
    func (e *argError) Error() string {
        return fmt.Sprintf("%d - %s", e.arg, e.prob)
    }
    
    func f2(arg int) (int, error) {
        if arg == 42 {
    
            return -1, &argError{arg, "can't work with it"}
        }
        return arg + 3, nil
    }
    
    func main() {
    
        for _, i := range []int{7, 42} {
            if r, e := f1(i); e != nil {
                fmt.Println("f1 failed:", e)
            } else {
                fmt.Println("f1 worked:", r)
            }
        }
        for _, i := range []int{7, 42} {
            if r, e := f2(i); e != nil {
                fmt.Println("f2 failed:", e)
            } else {
                fmt.Println("f2 worked:", r)
            }
        }
    
        _, e := f2(42)
        if ae, ok := e.(*argError); ok {
            fmt.Println(ae.arg)
            fmt.Println(ae.prob)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    $ go run errors.go
    f1 worked: 10
    f1 failed: can't work with 42
    f2 worked: 10
    f2 failed: 42 - can't work with it
    42
    can't work with it
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    25. Goroutines

    协程是Go的核心特性之一,它是一种轻量级的线程和任务调度机制,运行在操作系统级别的线程之上。它可以灵活地处理阻塞,从而减少大量异步代码的编写。我们可以开启大量的协程,而不用担心内存资源占用和线程上下文切换的代价。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func f(from string) {
        for i := 0; i < 3; i++ {
            fmt.Println(from, ":", i)
        }
    }
    
    func main() {
    
        f("direct")
    
    	// 可以通过go执行函数来启动协程
        go f("goroutine")
    
        go func(msg string) {
            fmt.Println(msg)
        }("going")
    
    	// 注意协程不会阻止主线程的结束,即使协程还未执行完。所以这里需要sleep 1秒
        time.Sleep(time.Second)
        fmt.Println("done")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    $ go run goroutines.go
    direct : 0
    direct : 1
    direct : 2
    goroutine : 0
    going
    goroutine : 1
    goroutine : 2
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    26. Channels

    通道可用于在多个goroutine之间通信。发送方发送消息到通道中,接收方再从通道中接收消息。当使用非缓存通道时,发送和接收消息是同步操作,若有一方未完成都会造成对方阻塞。

    package main
    
    import "fmt"
    
    func main() {
    	// channel是引用类型,需要通过make创建
        messages := make(chan string)
    	
    	// 发送ping消息,并阻塞等待接收方
        go func() { messages <- "ping" }()
    	// 接收ping消息,在接收到消息前阻塞
        msg := <-messages
        fmt.Println(msg)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    $ go run channels.go 
    ping
    
    • 1
    • 2

    27. Channel Buffering

    缓冲通道可以支持异步发送,无需等待接收方响应。在创建时可指定最大缓冲队列大小,若队列中消息长度超过最大值,则发送还是会被阻塞。接收方无论是哪种情况,只要没有消息可以接收,就一定会被阻塞。

    package main
    
    import "fmt"
    
    func main() {
    	// 指定缓冲队列最大长度为2
        messages := make(chan string, 2)
    	// 发送不再会被阻塞,因为有缓存队列
        messages <- "buffered"
        messages <- "channel"
    
        fmt.Println(<-messages)
        fmt.Println(<-messages)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    $ go run channel-buffering.go 
    buffered
    channel
    
    • 1
    • 2
    • 3

    28. Channel Synchronization

    Go鼓励通过通信的方式(CSP模型)实现同步,而非传统的共享内存。下面的例子展示了通过将主线程阻塞在接收端,从而保证goroutine先于主线程执行完。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(done chan bool) {
        fmt.Print("working...")
        time.Sleep(time.Second)
        fmt.Println("done")
    
        done <- true
    }
    
    func main() {
    
        done := make(chan bool, 1)
        go worker(done)
    	// 主线程阻塞在接收端,直至goroutine发送结束消息
        <-done
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    $ go run channel-synchronization.go      
    working...done  
    
    • 1
    • 2

    29. Channel Directions

    通道在作为参数传递给函数时,可以指定通道的方向,函数内部只能按指定的方向发送或者接收数据。下面的例子展示了通过pings、pongs两个通道实现乒乓消息(先发送后回收)的效果。

    package main
    
    import "fmt"
    
    // 先发送消息给pings
    func ping(pings chan<- string, msg string) {
        pings <- msg
    }
    
    // 再从pings中取消息,发送给pongs
    func pong(pings <-chan string, pongs chan<- string) {
        msg := <-pings
        pongs <- msg
    }
    
    func main() {
        pings := make(chan string, 1)
        pongs := make(chan string, 1)
        ping(pings, "passed message")
        pong(pings, pongs)
        // 最后从pongs中取消息
        fmt.Println(<-pongs)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    $ go run channel-directions.go
    passed message
    
    • 1
    • 2

    30. Select

    通过select可以实现在多个channel上等待。用法有点类似于网络通信中的select模型,只要有任意一个通道接收到数据,就可以从select中接触阻塞并读取这次到达的数据。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        c1 := make(chan string)
        c2 := make(chan string)
    
        go func() {
        	// sleep 1秒,模拟耗时操作
            time.Sleep(1 * time.Second)
            c1 <- "one"
        }()
        go func() {
            time.Sleep(2 * time.Second)
            c2 <- "two"
        }()
    	// 循环两次,保证从两个channel都读到数据
        for i := 0; i < 2; i++ {
            select {
            // 用select结合case实现多通道等待
            case msg1 := <-c1:
                fmt.Println("received", msg1)
            case msg2 := <-c2:
                fmt.Println("received", msg2)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    $ time go run select.go 
    received one
    received two
    
    real    0m2.245s
    
    • 1
    • 2
    • 3
    • 4
    • 5

    31. Timeouts

    超时可结合select使用,用于限制从channel获取消息的最大时间。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        c1 := make(chan string, 1)
        go func() {
            time.Sleep(2 * time.Second)
            c1 <- "result 1"
        }()
    
        select {
        case res := <-c1:
            fmt.Println(res)
        // 限制超时时间为1秒
        case <-time.After(1 * time.Second):
            fmt.Println("timeout 1")
        }
    
        c2 := make(chan string, 1)
        go func() {
            time.Sleep(2 * time.Second)
            c2 <- "result 2"
        }()
        select {
        case res := <-c2:
            fmt.Println(res)
        case <-time.After(3 * time.Second):
            fmt.Println("timeout 2")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    $ go run timeouts.go 
    timeout 1
    result 2
    
    • 1
    • 2
    • 3

    32. Non-Blocking Channel Operations

    通常通道的发送和接收都是阻塞的。但是我们可以使用select和default来实现非阻塞操作,发送和接收都不再阻塞,而是在尝试失败后立即走default定义的默认逻辑。

    package main
    
    import "fmt"
    
    func main() {
        messages := make(chan string)
        signals := make(chan bool)
    
        select {
        case msg := <-messages:
            fmt.Println("received message", msg)
        // 没有收到消息,不会阻塞,而是走default逻辑
        default:
            fmt.Println("no message received")
        }
    
        msg := "hi"
        select {
        case messages <- msg:
            fmt.Println("sent message", msg)
        // 没有接收方接收消息,不会阻塞,而是走default逻辑
        default:
            fmt.Println("no message sent")
        }
    
        select {
        case msg := <-messages:
            fmt.Println("received message", msg)
        case sig := <-signals:
            fmt.Println("received signal", sig)
        default:
            fmt.Println("no activity")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    $ go run non-blocking-channel-operations.go 
    no message received
    no message sent
    no activity
    
    • 1
    • 2
    • 3
    • 4

    33. Closing Channels

    关闭通道后,通道不再允许发送消息。这时接收方读取完通道中所有消息后,得到结束信号,做通信结束的后续操作。

    package main
    
    import "fmt"
    
    func main() {
        jobs := make(chan int, 5)
        done := make(chan bool)
    
        go func() {
            for {
            	// 关闭channel且读完通道中所有消息后,more取到false,结束通信
                j, more := <-jobs
                if more {
                    fmt.Println("received job", j)
                } else {
                    fmt.Println("received all jobs")
                    done <- true
                    return
                }
            }
        }()
    
        for j := 1; j <= 3; j++ {
            jobs <- j
            fmt.Println("sent job", j)
        }
        // 用close函数关闭channel
        close(jobs)
        fmt.Println("sent all jobs")
    
    	// 用消息阻塞保证goroutine先于主线程执行完 
        <-done
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    $ go run closing-channels.go 
    sent job 1
    received job 1
    sent job 2
    received job 2
    sent job 3
    received job 3
    sent all jobs
    received all jobs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    34. Range over Channels

    使用range也可用于从通道接收消息,包括在已关闭的通道上。

    package main
    
    import "fmt"
    
    func main() {
    
        queue := make(chan string, 2)
        queue <- "one"
        queue <- "two"
        close(queue)
    
    	// 即使通道已关闭,仍然可以用range接收消息
        for elem := range queue {
            fmt.Println(elem)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    $ go run range-over-channels.go
    one
    two
    
    • 1
    • 2
    • 3

    35. Timers

    定时器用于在将来的某个时间点执行代码。通过从通道接收到消息的方式表示达到触发时间点,接收到的消息为当前时间。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    	// 用time.NewTimer()定义2秒后的定时器
        timer1 := time.NewTimer(2 * time.Second)
    	// 阻塞在这里,直到到达触发时间点
        <-timer1.C
        fmt.Println("Timer 1 fired")
    
        timer2 := time.NewTimer(time.Second)
        // 用goroutine的方式避免阻塞主线程
        go func() {
            <-timer2.C
            fmt.Println("Timer 2 fired")
        }()
        // 用Stop()停止定时器
        stop2 := timer2.Stop()
        if stop2 {
            fmt.Println("Timer 2 stopped")
        }
    
        time.Sleep(2 * time.Second)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    $ go run timers.go
    Timer 1 fired
    Timer 2 stopped
    
    • 1
    • 2
    • 3

    36. Tickers

    周期定时器用于在将来周期性地执行某个任务,直至我们让它停下。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    	// 定义了一个周期为500毫秒的周期定时器
        ticker := time.NewTicker(500 * time.Millisecond)
        done := make(chan bool)
    
        go func() {
            for {
                select {
                case <-done:
                    return
                // 每间隔500毫秒从定时器通道接收到消息
                case t := <-ticker.C:
                    fmt.Println("Tick at", t)
                }
            }
        }()
    
    	// 1.6秒后停止定时器
        time.Sleep(1600 * time.Millisecond)
        ticker.Stop()
        done <- true
        fmt.Println("Ticker stopped")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    $ go run tickers.go
    Tick at 2012-09-23 11:29:56.487625 -0700 PDT
    Tick at 2012-09-23 11:29:56.988063 -0700 PDT
    Tick at 2012-09-23 11:29:57.488076 -0700 PDT
    Ticker stopped
    
    • 1
    • 2
    • 3
    • 4
    • 5

    37. Worker Pools

    工人池(协程池)用于使用固定数量的协程处理任务,防止协程数量太多。在部分特殊场景需要使用。协程池并非Go语言原生概念,而是基于已有语言特性搭建的开发模型。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    // worker从jobs通道获取任务,并将执行结果发送到results通道
    func worker(id int, jobs <-chan int, results chan<- int) {
        for j := range jobs {
            fmt.Println("worker", id, "started  job", j)
            time.Sleep(time.Second)
            fmt.Println("worker", id, "finished job", j)
            results <- j * 2
        }
    }
    
    func main() {
    
        const numJobs = 5
        jobs := make(chan int, numJobs)
        results := make(chan int, numJobs)
    	// 开启worker数量为3的协程池
        for w := 1; w <= 3; w++ {
            go worker(w, jobs, results)
        }
    	// 将新任务添加到jobs通道中
        for j := 1; j <= numJobs; j++ {
            jobs <- j
        }
        close(jobs)
    
        for a := 1; a <= numJobs; a++ {
            <-results
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    $ time go run worker-pools.go 
    worker 1 started  job 1
    worker 2 started  job 2
    worker 3 started  job 3
    worker 1 finished job 1
    worker 1 started  job 4
    worker 2 finished job 2
    worker 2 started  job 5
    worker 3 finished job 3
    worker 1 finished job 4
    worker 2 finished job 5
    
    	
    
    real    0m2.358s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    38. WaitGroups

    前面的例子展示了如何用通道实现主线程和单个goroutine之间的等待。下面将会展示如何使用WaitGroup实现对多个goroutine的等待。

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func worker(id int) {
        fmt.Printf("Worker %d starting\n", id)
    
        time.Sleep(time.Second)
        fmt.Printf("Worker %d done\n", id)
    }
    
    func main() {
    
        var wg sync.WaitGroup
    
        for i := 1; i <= 5; i++ {
        	// 对每个goroutine,等待计数加1
            wg.Add(1)
    
            i := i
            go func() {
            	// 延迟执行,完成工作后,将等待计数减1
                defer wg.Done()
                worker(i)
            }()
        }
    	// 当等待计数为0时,结束等待
        wg.Wait()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    $ go run waitgroups.go
    Worker 5 starting
    Worker 3 starting
    Worker 4 starting
    Worker 1 starting
    Worker 2 starting
    Worker 4 done
    Worker 1 done
    Worker 2 done
    Worker 5 done
    Worker 3 done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    39. Rate Limiting

    速率限制是互联网和软件工程中的概念,用于防止对资源的使用超过一定限度。Go通过周期定时器和通道可以方便地实现这一功能。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        requests := make(chan int, 5)
        for i := 1; i <= 5; i++ {
            requests <- i
        }
        close(requests)
    
        limiter := time.Tick(200 * time.Millisecond)
    
        for req := range requests {
        	// 每200毫秒结束阻塞,接收一次请求
            <-limiter
            fmt.Println("request", req, time.Now())
        }
    
        burstyLimiter := make(chan time.Time, 3)
    	// 先允许执行三次
        for i := 0; i < 3; i++ {
            burstyLimiter <- time.Now()
        }
    	// 然后每200毫秒允许执行一次
        go func() {
            for t := range time.Tick(200 * time.Millisecond) {
                burstyLimiter <- t
            }
        }()
    
        burstyRequests := make(chan int, 5)
        for i := 1; i <= 5; i++ {
            burstyRequests <- i
        }
        close(burstyRequests)
        for req := range burstyRequests {
            <-burstyLimiter
            fmt.Println("request", req, time.Now())
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    $ go run rate-limiting.go
    request 1 2012-10-19 00:38:18.687438 +0000 UTC
    request 2 2012-10-19 00:38:18.887471 +0000 UTC
    request 3 2012-10-19 00:38:19.087238 +0000 UTC
    request 4 2012-10-19 00:38:19.287338 +0000 UTC
    request 5 2012-10-19 00:38:19.487331 +0000 UTC
    
    request 1 2012-10-19 00:38:20.487578 +0000 UTC
    request 2 2012-10-19 00:38:20.487645 +0000 UTC
    request 3 2012-10-19 00:38:20.487676 +0000 UTC
    request 4 2012-10-19 00:38:20.687483 +0000 UTC
    request 5 2012-10-19 00:38:20.887542 +0000 UTC
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    40. Atomic Counters

    Go中的同步方式除了通过channel通信,还可以通过原子计数器。通过原子技术器访问或者修改数值变量,可以让这些操作成为原子性的。

    package main
    
    import (
        "fmt"
        "sync"
        "sync/atomic"
    )
    
    func main() {
    
        var ops uint64
    
        var wg sync.WaitGroup
    
        for i := 0; i < 50; i++ {
            wg.Add(1)
    
            go func() {
                for c := 0; c < 1000; c++ {
    				// 通过AddUnit64实现原子性增加,若是原子读取可用LoadUint64,注意这里需要传指针参数 
                    atomic.AddUint64(&ops, 1)
                }
                wg.Done()
            }()
        }
    
        wg.Wait()
    
        fmt.Println("ops:", ops)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    $ go run atomic-counters.go
    ops: 50000
    
    
    • 1
    • 2
    • 3
  • 相关阅读:
    基于.net的应用开发技术-作业六
    Python的安装
    每日刷题记录 (一)
    CentOS 7 安装Libevent
    Android 系统通过pid和vid固定usb声卡节点
    echart进阶
    “华数杯”建模学习思考(Matlab&Python代码实现)
    sparkctl x86/arm不同平台编译使用
    java计算机毕业设计航空机票预订系统MyBatis+系统+LW文档+源码+调试部署
    华清远见11.7
  • 原文地址:https://blog.csdn.net/needmorecode/article/details/128171848