notes
  • notes
  • codes
    • 安卓脚本
    • redis入门指南
    • js 原型链
    • 如何发布npm
    • go字符串
    • redis
    • this指向
    • go1.13
    • go by example
    • hook
    • go指南 - 官网
    • git基本操作
  • vim
  • training
    • 9月19日 ubc讲座
    • zby
      • DAY 3 :硬拉 | ZBY
      • DAY 2:卧推 | ZBY
      • DAY 1:深蹲 | ZBY
      • DAY 4 :计划 | ZBY
    • 汉唐
    • 周旋康复课
  • book notes
    • 思考致富
    • 邓普顿教你逆向投资
    • 阮琦
    • 魔鬼经济学
    • 网络是怎样连接的
    • 好奇心
    • 魔鬼约会学5.0
    • 股票作手回忆录
    • 漫步华尔街
    • 码农翻身
    • 十分钟速成课 - 哲学
    • 魔鬼答疑
    • 大话数据结构
    • 魔鬼约会学笔记
    • 算法图解
  • 狼人杀
  • 图书馆
  • typora
  • imovie
Powered by GitBook
On this page
  • 打印:
  • 变量
  • for循环
  • if-else
  • switch
  • 数组
  • slice
  • map
  • For-range
  • function
  • 闭包Closures
  • 递归recursion
  • go还能操作指针
  • 结构体struct
  • 方法
  • 接口
  • 错误处理
  • 协程Goroutines
  • 通道
  • 通道缓冲
  • 通道同步
  • 通道方向
  • 通道选择器select
  • 超时
  • 非阻塞通道操作
  • 通道的关闭
  • for-range遍历通道
  • 定时器
  • 打点器tickers
  • 工作池
  • 速率限制
  • 原子计数器
  • 互斥锁mutexes

Was this helpful?

  1. codes

go by example

Previousgo1.13Nexthook

Last updated 5 years ago

Was this helpful?

go和js有啥区别呢?

打印:

import “fmt" 
...
fmt.Println("go" + "lang”)

变量

var a string = “initial" 

f := “short”      //  :=是var的简写。

var e int  // 0

变量类型可以不写,会自动推断。

const s string = "constant"

const定义了就是常量

for循环

循环条件不用写圆括号,不写条件就死循环。

和while功能合并

for i <= 3 { 
    fmt.Println(i)
    i = i + 1 
}

for j := 7; j <= 9; j++ { 
    fmt.Println(j)
}

if-else

没有三目运算符

if num := 9; num < 0 {           // 这个num在if上面定义会死啊。。非要挤在一起。。
    fmt.Println(num, "is negative") 
} else if num < 10 { 
    fmt.Println(num, "has 1 digit") 
} else { 
    fmt.Println(num, "has multiple digits") 
}

switch

i := 2
switch i {
case 1:   // 如果 i == 1
    fmt.Println("one")
case 2:
    fmt.Println("two")
case 3 , 4:   // 逗号表达 或
    fmt.Println("three or four")
default:
    fmt.Println("default")
}

// 用来判断类型,只能在switch里面用.(type)
switch t := i.(type) {
case bool:
    fmt.Println("I'm a bool")
case int:
    fmt.Println("I'm an int")
default:
    fmt.Printf("Don't know type %T\n", t)
}

数组

// 创建数组
var a [5]int   // [0 0 0 0 0]
b := [5]int{1, 2, 3, 4, 5}   // [1 2 3 4 5]

// 取值与赋值
a[4] = 100

// 数组长度
len(a)

// 多维数组(元素是数组的数组)
// 似乎不能通过:=创建,只能先定义一个,然后for循环填满
var twoD [2][3]int
for i := 0; i < 2; i++ {
    for j := 0; j < 3; j++ {
        twoD[i][j] = i + j
    }
}
fmt.Println("2d: ", twoD)

slice

类似于数组的升级版,有很多自带的api。感觉slice才是真正的数组。

// 用make来新建,规定内容和大小
s := make([]string, 3)
// 或者这样也行。和数组的区别在于,不规定长度
t := []string{"g", "h", "i"}


// 获取值和长度的方法和数组一样

// slice增加一个元素,返回一个新的slice
s = append(s, "d")
// 如果直接用s[3]来赋值,会报错。数组也是。

// copy一个slice
c := make([]string, len(s)) // 先要准备好长度和类型一样的位置,才能复制
copy(c, s)

// 切片操作。和python差不多
l := s[2:5]    // 包括前面不包括后面

map

go里面的哈希表,或称为字典

// 创建一个map
m := make(map[string]int)
n := map[string]int{"foo": 1, "bar": 2}

// 不限大小,增加就是赋值
m["k1"] = 7

// 删除
delete(m, "k1")

// 打印
fmt.Println("map:", m) // map: map[k1:7 k2:13]

// 长度
len(m)

// 空白标识符(blank identifier)
_, prs := m["k2"]   // 没有k2对应的值,返回false
// 本来没有值是返回的0或者""。用_为了统一变成false
// 相当于: prs || false  = m["k2"]
// 为了防一手没有值的情况

For-range

// 新建一个slice
nums := []int{2, 3, 4}

// 循环一下
for i, num := range nums {    // 第一个参数可以写_,表示不需要用这个索引
    if num == 3 {
        fmt.Println("index:", i)
    }
}

// 循环map
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {         // 第一个是key,第二个是value
    fmt.Printf("%s -> %s\n", k, v)
}
// 只有一个值,那就是遍历key
for k := range kvs {
    fmt.Println("key:", k)
}

// 循环字符串,返回的是unicode
for i, c := range "go" {   // i是索引,c是unicode
    fmt.Println(i, c)
}

function

// 函数定义:
func plus(a int, b int) int {  // 函数名(参数,参数的数据类型) 返回值的数据类型
    return a + b
}
// 输入参数都是一种类型,可以统一写在最后
func plusPlus(a, b, c int) int { 
    return a + b + c
}
// 多返回值可以加个括号
func vals() (int, int) {
    return 3, 7
}

// 调用
res = plusPlus(1, 2, 3)
_, c := vals()   // 不需要用到的返回值,可以用下划线代替

// 可变参数
func sum(nums ...int) { // 数据类型前面加上...
  // 函数内容
}
// 调用
sum(1, 2) // 普通地传入
// 装在slice里面传入
nums := []int{1, 2, 3, 4}
sum(nums...)  // ...写在后面

闭包Closures

func intSeq() func() int {  // 返回是一个函数,函数的返回是int
    i := 0
    return func() int {  // 返回的函数
        i++
        return i
    }
}

// 返回值放到nextInt之中
nextInt := intSeq()
fmt.Println(nextInt())  // 每次调用,输出都会+1

// 重新赋值返回的函数,就会获得一个新的i
newInts := intSeq()

递归recursion

func fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * fact(n-1)
}
// 感觉这种做法也不是最好,不是尾递归

go还能操作指针

总感觉没控制好就会玩脱的权限,改bug都可能不知道在哪里改。

主要目的是,如果指的东西非常大,就可以避免运行函数的时候还要花时间复制一份。直接拿过来就能用了。

【js好像引用类型的,直接用指针来处理了,就是取不到地址,把地址这一层都包裹起来了】

// 把值传进一个函数。即使改变了这个参数,也不会影响外面的值。
// 感觉传递参数,本质上是把这个变量复制了一份。变量的作用域就成了这个函数,只能在这里面用。
func zeroval(ival int) {
    ival = 0             // 把参数变成0
}

func main() {
    i := 1
    zeroval(i)
    fmt.Println("zeroval:", i)   // 还是1,没有变化
}


// 直接操作指针
func zeroptr(iptr *int) {     // 定义的时候需要多加一个*,估计是为了防止误操作
    *iptr = 0                                    // 修改的变量,也要加一个*
}
func main() {
    i := 1
    // 通过 `&i` 语法来取得 `i` 的内存地址,即指向 `i` 的指针。
    zeroptr(&i)               // 调用修改指针,要加一个&,估计也是为了防止误操作
                              // 被*定义了的函数,也就不能再接收普通的i了,一定要加&才能用
    fmt.Println("zeroptr:", i)  // 0,i的指针指向了0,所以获取的值就是0

    // 指针也是可以被打印的。
    fmt.Println("pointer:", &i) // 0xc000084000,这是内存地址
}

结构体struct

有点像写好了interface的object

// 这里的 `person` 结构体包含了 `name` 和 `age` 两个字段。
type person struct {
    name string
    age  int
}

func main() {
    // 使用这个语法创建新的结构体元素。
    fmt.Println(person{"Bob", 20})   // 结构体的名字要在前面补全

    // 你可以在初始化一个结构体元素时指定字段名字。
    fmt.Println(person{name: "Alice", age: 30})  // 最好这样写吧,看得清楚

    // 省略的字段将被初始化为零值。
    fmt.Println(person{name: "Fred"})

    // `&` 前缀生成一个结构体指针。
    fmt.Println(&person{name: "Ann", age: 40})

    // 使用`.`来访问结构体字段。
    s := person{name: "Sean", age: 50}
    fmt.Println(s.name)

    // 也可以对结构体指针使用`.` - 指针会被自动解引用。
    sp := &s
    fmt.Println(sp.age)

    // 结构体是可变(mutable)的。
    sp.age = 51
    fmt.Println(sp.age)
}

方法

类似于prototype,这是在结构体下面添加一个方法。

type rect struct {
    width, height int
}

func add(r rect) int {         // 普通的函数长这样
    return r.width + r.height
}

// 可以为值类型或者指针类型的接收器定义方法。这里是一个
// 【值类型】接收器的例子。
func (r rect) perim() int {      // 类似于在rect下面添加一个方法
    return 2*r.width + 2*r.height
}

// 这里的 `area` 方法有一个_接收器(receiver)类型_ `rect`。
// 【指针类型】,接受的是地址
func (r *rect) area() int {
    return r.width * r.height
}


func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("add: ", add(r))
    fmt.Println("add: ", add(rect{width:10, height:5}))
    // 要加一个rect才是结构体

    // 这里我们调用上面为结构体定义的两个方法。
    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())

    // Go 自动处理方法调用时的值和指针之间的转化。你可以使
    // 用指针来调用方法来避免在方法调用时产生一个拷贝,或者
    // 让方法能够改变接受的结构体。
    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
}

接口

感觉接口就是规定了进入的结构体,下面都有哪些方法。

更高级别的结构体?

// 规定结构体只能有这俩方法
type geometry interface {
    area() float64
    perim() float64
}

// 这里定义了rect和circle,然后每个方法下面都有area()和perim()两个方法
...

func measure(g geometry) {    // 在这里定义输入的g参数
    fmt.Println("g", g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    // 结构体类型 `circle` 和 `rect` 都实现了 `geometry`
    // 接口,所以我们可以使用它们的实例作为 `measure` 的参数。
    measure(r)
    measure(c)
}

错误处理

// 按照惯例,错误通常是【最后一个返回值】并且是 `error` 类
// 型,一个内建的接口。
func f1(arg int) (int, error) {  // 返回的第一个参数是int,第二个是error类型
    if arg == 42 {
        // `errors.New` 构造一个使用给定的错误信息的基本
        // `error` 值。
        return -1, errors.New("can't work with 42") // 创建一个新的error类型
    }
    // 返回错误值为 nil 代表没有错误。
    return arg + 3, nil
}

// 通过实现 `Error` 方法来自定义 `error` 类型是可以的。
// 这里使用自定义错误类型来表示上面例子中的参数错误。
type argError struct {
    arg  int
    prob string
}

func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.prob)  // 格式化输出,填写这俩位置【和python很像】
}
// 如果是fmt.Println,就会直接把"%d-%s"打印出来了。没法替换格式

func f2(arg int) (int, error) {
    if arg == 42 {
        // 在这个例子中,我们使用 `&argError` 语法来建立一个
        // 新的结构体,并提供了 `arg` 和 `prob` 这两个字段
        // 的值。
        return -1, &argError{arg, "can't work with it"}  // 加个&表明用其地址
    }
    return arg + 3, nil
}

func main() {
    // 下面的两个循环测试了各个返回错误的函数。注意在 `if`
    // 行内的错误检查代码,在 Go 中是一个普遍的用法。
    for v, i := range []int{7, 42} {    // [7, 42],虽然是range,其实就是两个元素
        fmt.Println("v", v)            // 第一个参数是index
        fmt.Println("i", i)            // 第二个参数是其值
      if r, e := f1(i); e != nil {             // ;前,是运行f1()并获取两个返回值,第二个是判断
            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 {  // 这是一个断言,假定e是argError,然后就能用其中的方法了。
        fmt.Println(ae.arg)
        fmt.Println(ae.prob)
    }
}

协程Goroutines

func f(from string) {
    for i := 0; i < 3; i++ {
        fmt.Println(from, ":", i)
    }
}

func main() {
    // 同步(synchronously)调用
    f("direct")

    // 使用 `go f(s)` 在一个 Go 协程中调用这个函数。
    // 这个新的 Go 协程将会并发(concurrently)执行这个函数。
    go f("goroutine")
        // 简言之,用协程就是在前面加一个go

    // 你也可以为匿名函数启动一个 Go 协程。
    go func(msg string) {
        fmt.Println(msg)
    }("going")

    // 现在这两个 Go 协程在独立的 Go 协程中异步(asynchronously)运行,所以
    // 程序直接运行到这一行。这里的 `Scanln` 代码需要我们
    // 在程序退出前按下任意键结束。
    fmt.Scanln()
    fmt.Println("done")
}

通道

不同协程之间如何通信?用通道【类似于一个共享的内存】

// 使用 `make(chan val-type)` 创建一个新的通道。
// 通道类型就是他们需要传递值的类型。
messages := make(chan string)

// 使用 `channel <-` 语法 _发送(send)_ 一个新的值到通道中。这里
// 我们在一个新的 Go 协程中发送 `"ping"` 到上面创建的
// `messages` 通道中。
go func() { messages <- "ping" }()     // 把"ping"发送到messages这个channel里面

// 使用 `<-channel` 语法从通道中 _接收(receives)_ 一个值。这里
// 将接收我们在上面发送的 `"ping"` 消息并打印出来。
msg := <-messages    // 从messages里面获取信息
fmt.Println(msg)  // ping

写入之后会堵塞这个channel,直到有人将其取出去;读取也会造成堵塞,直到有人写进去。

channel实现了类似锁的功能,保证了所有goroutine完成后main()才返回。

关闭不再使用的channel:

close(messages)

应该在生产者的地方关闭channel

通道缓冲

一般是,只有接收方准备好了,才能进行发送。

缓冲,能在没有对应接收方的情况下,缓存一定数量的值

// 这里我们创建了一个字符串通道,最多允许缓存 2 个值。
messages := make(chan string, 2)    // make的第二个参数就是可以缓存的值

// 由于此通道是缓冲的,因此我们可以将这些值发送到通道中
// 而不需要相应的并发接收。
messages <- "buffered"       // 可以发两个东西进去
messages <- "channel"

// 然后我们可以像前面一样接收这两个值。
fmt.Println(<-messages)
fmt.Println(<-messages)

channel的值都是一次性的,一对一的,拿出来的就销毁掉了。不存在一段信息发给多个人的情况。

通道同步

同步 Go 协程间的执行状态,使用阻塞的接受方式来等待一个 Go 协程的运行结束

【不就是变回了同步了吗。。】

// 这是一个我们将要在 Go 协程中运行的函数。`done` 通道
// 将被用于通知其他 Go 协程这个函数已经工作完毕。
func worker(done chan bool) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    // 发送一个值来通知我们已经完工啦。
    done <- true
}

func main() {
    // 运行一个 worker Go协程,并给予用于通知的通道。
    done := make(chan bool, 1)
    go worker(done)

    // 程序将在接收到通道中 worker 发出的通知前一直阻塞。
    <-done                 // 感觉就是一个await
}

通道方向

可以指定通道,只用来接收或者发送值。提高安全性。

【感觉就是在channel上面包了一层函数,只能用来接收或者发送,channel还是那个channel】

// `ping` 函数定义了一个只允许发送数据的通道。尝试使用这个通
// 道来接收数据将会得到一个编译时错误。
func ping(pings chan<- string, msg string) {  //  只能用来发送
    pings <- msg
}

// `pong` 函数允许通道(`pings`)来接收数据,另一通道
// (`pongs`)来发送数据。
func pong(pings <-chan string, pongs chan<- string) { // ping只能用来接收,pong是发送
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

通道选择器select

可以同时等待多个通道操作

【感觉就是专门用来等通道的switch】

// 在我们的例子中,我们将从两个通道中选择。
c1 := make(chan string)
c2 := make(chan string)

// 各个通道将在若干时间后接收一个值,这个用来模拟例如
// 并行的 Go 协程中阻塞的 RPC 操作
go func() {
    time.Sleep(time.Second * 1)
    c1 <- "one"
}()
go func() {
    time.Sleep(time.Second * 2)
    c2 <- "two"
}()

// 我们使用 `select` 关键字来同时等待这两个值,并打
// 印各自接收到的值。
for i := 0; i < 2; i++ {
    select {
        case msg1 := <-c1:                         // 如果c1成功读取到数据
        fmt.Println("received", msg1)
        case msg2 := <-c2:                            // 如果c2成功读取到数据
        fmt.Println("received", msg2)
    }
}

select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

超时

用select,增加一个超时的case

c1 := make(chan string, 1)
go func() {
    time.Sleep(time.Second * 2)   // 等待2秒
    c1 <- "result 1"
}()

// 这里是使用 `select` 实现一个超时操作。
// `res := <- c1` 等待结果,`<-Time.After` 等待超时
// 时间 1 秒x后发送的值。由于 `select` 默认处理第一个
// 已准备好的接收操作,如果这个操作超过了允许的 1 秒
// 的话,将会执行超时 case。
select {
  case res := <-c1:
      fmt.Println(res)
  case <-time.After(time.Second * 1):   // 超过一秒还没获取c1的值,就触发
      fmt.Println("timeout 1")
}

非阻塞通道操作

用default来实现非阻塞,如果跑的时候没有发送或接收,就跑default,不会阻塞进程

messages := make(chan string)

// 这里是一个非阻塞接收的例子。如果在 `messages` 中
// 存在,然后 `select` 将这个值带入 `<-messages` `case`
// 中。如果不是,就直接到 `default` 分支中。
select {
case msg := <-messages:
    fmt.Println("received message", msg)
default:
    fmt.Println("no message received")
}

// 一个非阻塞发送的实现方法和上面一样。
msg := "hi"
select {
case messages <- msg:
    fmt.Println("sent message", msg)
default:
    fmt.Println("no message sent")
}

// 我们可以在 `default` 前使用多个 `case` 子句来实现
// 一个多路的非阻塞的选择器。这里我们试图在 `messages`
// 和 `signals` 上同时使用非阻塞的接收操作。
select {
case msg := <-messages:
    fmt.Println("received message", msg)
case sig := <-signals:
    fmt.Println("received signal", sig)
default:
    fmt.Println("no activity")
}

通道的关闭

关闭通道,意味着无法再传入值。可以给这个通道的接收方传达工作已经完成的信息。

// 在这个例子中,我们将使用一个 `jobs` 通道来传递 `main()` 中 Go
// 协程任务执行的结束信息到一个工作 Go 协程中。当我们没有多余的
// 任务给这个工作 Go 协程时,我们将 `close` 这个 `jobs` 通道。
func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    // 这是工作 Go 协程。使用 `j, more := <- jobs` 循环的从
    // `jobs` 接收数据。在接收的这个特殊的二值形式的值中,
    // 如果 `jobs` 已经关闭了,并且通道中所有的值都已经接收
    // 完毕,那么 `more` 的值将是 `false`。当我们完成所有
    // 的任务时,将使用这个特性通过 `done` 通道去进行通知。
    go func() {
        for {
            j, more := <-jobs          // 如果没有j了,more就是false
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    // 这里使用 `jobs` 发送 3 个任务到工作函数中,然后
    // 关闭 `jobs`。
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    // 我们使用前面学到的[通道同步](../channel-synchronization/)
    // 方法等待任务结束。
    <-done
}

换言之数据接收方可以接受两个数据,一个是传过来的值,另一个是传值的状态,有没有传递完成。

【大概通道的第二个参数,创建时就是true,当close(jobs)运行,就是给通道的第二个参数设为false】

【怎么感觉这个channel有点像state】

for-range遍历通道

// 我们将遍历在 `queue` 通道中的两个值。
queue := make(chan string, 2)  // 创建queue通道
queue <- "one"
queue <- "two"
close(queue)

// 这个 `range` 迭代从 `queue` 中得到的每个值。因为我们
// 在前面 `close` 了这个通道,这个迭代会在接收完 2 个值
// 之后结束。如果我们没有 `close` 它,我们将在这个循环中
// 继续阻塞执行,等待接收第三个值
for elem := range queue {      // 阻塞,或者第二个参数为false则停止循环
    fmt.Println(elem)
}

非空的通道也是可以关闭的。我猜这个关闭,只是向通道内传递“要关闭”(第二个参数为false)并不是真正的回收,而是等到没有在指向的时候才回收。

定时器

类似于setTimeout

timer1 := time.NewTimer(time.Second * 2)  // 创建一个timer1,附带一个channel是.C,2秒后在.C发送消息
ans := <-timer1.C     // C估计是Channel的意思,定时器创建时自带的channel
// 2019-07-22 17:26:28.279435 +0800 CST m=+2.001325896
// 返回的内容是运行结束的时间

stop := timer1.Stop()   // 可以在其结束前,结束这个定时器
stop := timer2.Stop()
if stop {
    fmt.Println("Timer 2 stopped")
}

time.After
time.Sleep    // 如果只是想要单纯等待一段时间
time.Second    // 一秒钟
time.Millisecond  // 一毫秒

打点器tickers

类似于setInterval,固定间隔重复执行。

ticker := time.NewTicker(time.Millisecond * 500) // 500毫秒一次循环的定时器
go func() {
    for t := range ticker.C {      // 自带的频道同样在C,返回值是触发的时间
        fmt.Println("Tick at", t)
    }
}()

ticker.Stop()   // 需要手动停止定时器,不然会一直间隔打点下去

工作池

// 这是我们将要在多个并发实例中支持的任务了。这些执行者
// 将从 `jobs` 通道接收任务,并且通过 `results` 发送对应
// 的结果。我们将让每个任务间隔 1s 来模仿一个耗时的任务。
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "processing job", j) // 不同的协程对应的worker
        time.Sleep(time.Second) 
        results <- j * 2
    }
}

func main() {
    // 为了使用 worker 工作池并且收集他们的结果,我们需要
    // 2 个通道。
    jobs := make(chan int, 100)  // 创建通道,缓存100
    results := make(chan int, 100)

    // 这里启动了 3 个 worker,初始是阻塞的,因为 
    // 还没有传递任务。
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // 这里我们发送 9 个 `jobs`,然后 `close` 这些通道
    // 来表示这些就是所有的任务了。
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)

    // 最后,我们收集所有这些任务的返回值。
    for a := 1; a <= 9; a++ {
        fmt.Println("<-results", <-results)
    }
}

【问题是,不知道是哪个worker的那个job的返回值,都是一股脑传回来的】

速率限制

简言之就是要限制请求的频率。

方法是用time.Tick(),比如每200ms接受一个值。

// 创建通道,往里面传1到5
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
    requests <- i
}
close(requests)  // 关闭

// 这个 `limiter` 通道将每 200ms 接收一个值。这个是
// 速率限制任务中的管理器。
limiter := time.Tick(time.Millisecond * 200)  // Tick 会返回一个通道,源码中用make创建了

// 通过在每次请求前阻塞 `limiter` 通道的一个接收,我们限制
// 自己每 200ms 执行一次请求。
for req := range requests {
    <-limiter              // 阻塞,只有当limiter有了返回值了之后,才能继续下面的操作
    fmt.Println("request", req, time.Now())
}

这种做法是定义全局的速率限制,也可以只定义部分的速率

burstyLimiter := make(chan time.Time, 3)   // 通道长度只有3

for t := range time.Tick(time.Millisecond * 200) {
    burstyLimiter <- t
}

...

for req := range requests {
    <-burstyLimiter                                       // 只能影响前三个的速率
    fmt.Println("request new /n", req, time.Now())
}

原子计数器

go中的状态管理,主要通过通道来完成。也有一些其他方法,比如sync/atomic

// 我们将使用一个无符号整型数来表示(永远是正整数)这个*计数器*。
var ops uint64 = 0

// 为了模拟并发更新,我们启动 50 个 Go 协程
for i := 0; i < 50; i++ {
    go func() {
        for {
            // 使用 `AddUint64` 来让计数器自动增加,使用
            // `&` 语法来给出 `ops` 的内存地址。
            atomic.AddUint64(&ops, 1)    // 在计数器地址,自动+1

            // 允许其它 Go 协程的执行
            runtime.Gosched()     // 应该是为了让出执行堆栈
        }
    }()
}

// 等待一秒,让 ops 的自加操作执行一会。
time.Sleep(time.Second)

// 为了在计数器还在被其它 Go 协程更新时,安全的使用它,
// 我们通过 `LoadUint64` 将当前值的拷贝提取到 `opsFinal`
// 中。和上面一样,我们需要给这个函数所取值的内存地址 `&ops`
opsFinal := atomic.LoadUint64(&ops)      // 安全使用计数器
fmt.Println("ops:", opsFinal)

互斥锁mutexes

复杂情况的状态管理方法,能在 Go 协程间安全的访问数据。

比如对map的读写,同一时间只能有一个人在读写。如果同时多个线程在读写一个map,就会报错。

因此可以在读写之前,上一把锁。

// 定义map
var state = make(map[int]int)
// 定义锁
var mutex = &sync.Mutex{}


// 取值
mutex.Lock()                // 上锁才能操作map,一次只能有一个人在操作
total += state[key]
mutex.Unlock()

// 赋值
mutex.Lock()
state[key] = val
mutex.Unlock()

// 打印值也要上锁
mutex.Lock()
fmt.Println("state:", state)
mutex.Unlock()

https://books.studygolang.com/gobyexample/
参考