day1 Go的初体验 | 青训营笔记

248 阅读2分钟

这是我参与「第五届青训营」笔记创作活动的第1天

defer

  • defer 的执行顺序
func fun() {
    //defer在函数return后从下到上依次运行,通常可做资源释放等
    defer last()
    defer middle()
    defer first()

    //最先运行
    fmt.Println("hello world")
}

// go fun()   // 开启一个 coroutine
  • defer 的具体执行时间
package main

import "fmt"

func main() {
    var f = func() (i int) {
            defer func() { i++ } ()
            defer func() { fmt.Println(i) } () // 1

            return 1
    }

    fmt.Println(f()) // 2
}

输出为

1
2

defer 在所在函数块返回后,caller逻辑执行前执行

sync.WaitGroup

协调多个执行流同步

协程,用户级子线程。和普通线程有很多相似之处。

开启子线程(协程)后,主协程可能需要等待子协程处理完毕才能继续运行,或者要接受子协程的输出结果,主协程才能结束执行。

朴素的方法:使用time.Sleep(ns)阻塞主协程一段合理的时间,给子协程足够的时间完成执行。

正确的方法:

类似Cpthread库的pthread_join()Java中的CyclicBarrierCountDownLatch方法

Go提供了sync.WaitGroup来(等待)同步多个协程的运行完成。本质是基于计数。

var wg sync.WaitGroup
wg.Add(等待任务个数)

for i = 1; i <= 任务个数; i++

其中WaitGroupAdd方法表示增加需要等待的任务的个数。Down方法使任务计数减1,表示完成一个任务。Wait方法阻塞当前协程,等待WaitGroup中设定的任务数减到0(都执行完毕)。

WaitGroup的实现原理[1]

loop variable captured by func literal

使用循环变量开启多个协程的常见问题

func main() {
    arr := []int{1, 2, 3, 4}

    for _, i := range arr {
            go func() { fmt.Println(i) }()
    }

    time.Sleep(time.Second)
}

可能会输出4\n3\n4\n44\n4\n4\n4等,因为匿名函数里使用的i绑定的是外层循环变量i本身,并没有对i进行拷贝,协程运行时访问i的值时,i已经随循环改变。这就是loop variable captured by func literal

简单的解决办法,对i进行拷贝,为func添加参数i,并在调用时传参 或手动拷贝

for _, i := range arr {
    i := i // 循环体新变量i,循环变量i的范围大于循环体,起始于for
    go func() { fmt.Println(i) }()
}

Notice

go的语法设计

  • channel <- var

将数据 var 写入通道 channel,但代码写起来是反着的,通道 被写入 数据,有种强行被动语态的赶脚

  • make

make明明可以很好地伪装成一个函数,提供一个统一的视觉/使用体验

s := make([]string, 3)

// 但在 Channel 这里
out = make(chan int)
buf = make(chan int, 3)

// 这样是不是更统一呢
out = make(chan, int, 3)

难道上面突出了make不同之处的语法更好解析吗

两个工具

网络请求代码生成 Convert curl to Go (curlconverter.com)

JSON转struct类型 JSON转Golang Struct - 在线工具 - OKTools

Reference

[1]Go语言 WaitGroup 详解