Go 中的回调与闭包

回调函数:

将函数B作为另一个函数A的参数,可以使得函数A的通用性更强,可以随意定义函数B,只要满足规则,函数A都可以去处理,这比较适合于回调函数。

sort包中的SliceStable()就是一个比较典型的用回调函数的应用

定制一个对数字按字符大小的排序:

package main

import (
    "fmt"
    "sort"
    "strconv"
)

func main() {
    s1 := []int{112, 220, 52, 32, 42}
    sort.SliceStable(s1, func(i, j int) bool {
        // 将i和j对应的元素值转换成字符串
        bi := strconv.FormatInt(int64(s1[i]), 10)
        bj := strconv.FormatInt(int64(s1[j]), 10)
        // 按字符顺序降序排序
        return bi > bj
    })
    fmt.Println(s1)
}

闭包:函数A返回函数B

闭包通常与defer联合使用

闭包就是"一个函数+一个作用域环境"组成的特殊函数。这个函数可以访问不是它自己内部的变量,也就是这个变量在其它作用域内,且这个变量是未赋值的,而是等待我们去赋值的。(内部函数可以访问外部函数的值)

闭包的简单使用

例1:

package main

import "fmt"

func f(x int) func(int) int {
    g := func(y int) int {
        return x + y
    }
    // 返回闭包
    return g
}
func main() {
    // 将函数的返回结果"闭包"赋值给变量a
    a := f(3)//3是外部函数的值
    // 调用存储在变量中的闭包函数
    res := a(5)//5是内部函数的值
    fmt.Println(res)
    // 可以直接调用闭包
    // 因为闭包没有赋值给变量,所以它称为匿名闭包
    fmt.Println(f(3)(5))
}

例2:

package main

import "fmt"

func main() {
    // 自由变量x
    var x int
    // 闭包函数g
    g := func(i int) int {
        return x + i
    }
    x = 5
    // 调用闭包函数
    fmt.Println(g(5))//10
    x = 10
    // 调用闭包函数
    fmt.Println(g(3))//13
}

 观察以上两个例子观察到被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。

闭包对外部变量的引用:

例3:

func main() {
    v := 11
    defer func() {
        fmt.Println(v)
    }()
    v = 2
}
#v = 2

闭包对外部的引用为指针,可以对其进行修改

闭包与逃逸分析:

例4:

func closure() func() int {
    var a int
    return func() int {
        a++
        return a
    }
}

内函数对外函数的变量的修改,是对变量的引用。本身运行栈空间上分配的内存,由于闭包的关系,变量在函数的作用域之外,因此逃逸到堆上。 变量被引用后,它所在的函数结束,这变量也不会马上被销毁。相当于变相延长了函数的生命周期。

例5:

m := make(map[int]int, 10)
for i := 1; i<= 10; i++ {
    m[i] = i
}

for k, v := range(m) {
    go func() {
        fmt.Println("k ->", k, "v ->", v)
    }()
}
..

变量输出会乱序,因为它持有外部变量引用

实际项目中一个闭包的例子,主要解决rpc并行调用的问题

问题:外层服务调大量子服务时,前后子服务没有顺序关系,可以尝试并行调用,就必然使用groutine和Group,这样会导致主函数非常难看,因此用了闭包去封装groutine,把耗时操作放在PostJson当中,既可以实现并行调用,也可以使代码结构简洁易读

func (c *ReviewApi) GetReviewsServerNewSortFunc(param *proto.GetReviewsServerSortParam) func() (*proto.GetReviewsServerSortResp, error) {
    r := c.PostJson(CATEGORY_REVIEW, HTTPAPI_FLAG, "get_reviews_new_sort", param)
    return func() (*proto.GetReviewsServerSortResp, error) {
        ret := &proto.GetReviewsServerSortResp{}
        err := r.GetJson(ret)
        return ret, err
    }
}

在PostJson中去构造groutine,PostJson和GetJson的封装

func (r *replyImpl) GetJson(resp interface{}) error {
    r.wait()
    if r.body != nil {
        if r.body.err == nil {
            return json.Unmarshal(r.body.dat, resp)
        }
        return r.body.err
    }
    return errors.New("reply is nil")
}

func (p *ApiClient) PostJson(category string, model string, action string, param interface{}) xcproto.Reply {
    r := &replyImpl{
        body: nil,
        ch:   make(chan *replyBody, 1),
    }
    go func() {
        body, err := p.DoRaw(category, model, action, param)
        r.ch <- &replyBody{err, body}
        close(r.ch)
    }()
    return r
}

外层函数使用

func1 := GetReviewsServerNewSortFunc()

func2 := GetReviewsServerNewSortFunc()

func3 := GetReviewsServerNewSortFunc()

以上三个不会阻塞,最终的耗时是最长的一个,最终实现rpc调用

func1()

func2()

func3()

 

posted @ 2020-09-16 16:39  LeeJuly  阅读(186)  评论(0编辑  收藏  举报