Golang之闭包-《GO开发知识笔记》

admin 2025-11-04 01:21:11 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 1 closure定义
  • 2 closure与引用
  • 3 closure与context
  • 6 总结

    1 closure定义


    关于closure的定义,可以参照golang官方示例(参考文档1)中的一句话:

    Go supports anonymous functions, which can form closures. Anonymous functions are useful when you want to define a function inline without having to name it.

    从上面这句话可以看出看出,closure首先是匿名函数,其次是在另一个函数里面实现。很多语言都有closure,其实都是一种语法糖,它与其定义时所在的函数共享同一个函数栈,能够使用其所在函数的内存空间,其访问的内存空间的对象(可称之为closure context)会被runtime放在堆空间上,编译器编译closure后会被inline成所在函数的一部分语句块(golang中是Escape Analysis技术)以提高运行速度。其实可以这么定义:closure = anonymous function + closure conetxt。关于closure的汇编层面解释,详见最下面列出的参考文档2。下面列述最近遇到的几个比较典型的golang clousure code example。

    2 closure与引用


    golang中通过传递变量值能够起到引用效果的变量类型有slice & map & channel,其本质是这三种var type不是那种类似于int等可以让CPU直接访问的原子变量类型,而是一种C中的类似于struct的复合数据结构,其结构体中存储的值又指向的更大的一块内存地址,这个大内存区域才是真正的“值域”,结构体本身类似域大内存域的proxy。如果能够理解C++的shared_ptr的实现,就能够理解这种变量类型的本质。因为closure与其所在的函数共享函数栈,所以也能实现类似于引用的效果。如下程序:Go // output: 5 func main() { var v int = 3 func() { v = 5 }() println(v) }上面的例子中,main函数内部的closure修改了变量v的值,因为是函数内部调用,其结论可能不能为人信服,又有如下示例:Go // output: 5func test() (func(), func()) {var v int = 3return func() { v = 5 }, func() { println(“v:”, v) }}

    1. func main() {
    2. f1, f2 := test()
    3. f1()
    4. f2()
    5. }

    代码示例中f1和f2访问的变量v,其实v在使用时被runtime定义在了heap上。参考文档1的代码示例也比较经典,一并补录如下:Go func intSeq() func() int { i := 0 return func() int { i += 1 return i } }

    1. func main() {
    2. nextInt := intSeq()
    3. println(nextInt()) // 1
    4. println(nextInt()) // 2
    5. println(nextInt()) // 3
    6. newInts := intSeq()
    7. println(newInts()) // 1
    8. }

    注意上面示例中最后一行的输出,当closure所在函数重新调用时,其closure是新的,其context引用的变量也是重新在heap定义过的。

    3 closure与context


    context是我见过的golang标准库(go1.7)中最优雅的库之一,对context的分析详见参考文档3,其cancel相关代码如下:Go type CancelFunc func()

    1. // WithCancel方法返回一个继承自parent的Context对象,同时返回的cancel方法可以用来关闭返回的Context当中的Done channel
    2. // 其将新建立的节点挂载在最近的可以被cancel的父节点下(向下方向)
    3. // 如果传入的parent是不可被cancel的节点,则直接只保留向上关系
    4. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    5. c := newCancelCtx(parent)
    6. propagateCancel(parent, &c)
    7. return &c, func() { c.cancel(true, Canceled) }
    8. }
    9. func newCancelCtx(parent Context) cancelCtx {
    10. return cancelCtx{
    11. Context: parent,
    12. done: make(chan struct{}),
    13. }
    14. }

    从上可见cancel context也用到了closure,WithCancel返回了一个context对象和一个closure。cancel context的使用示例(参考文档4)如下:Go // 模拟一个最小执行时间的阻塞函数 func inc(a int) int { res := a + 1 // 虽然我只做了一次简单的 +1 的运算, time.Sleep(1 * time.Second) // 但是由于我的机器指令集中没有这条指令, // 所以在我执行了 1000000000 条机器指令, 续了 1s 之后, 我才终于得到结果。B) return res }

    1. // 向外部提供的阻塞接口
    2. // 计算 a + b, 注意 a, b 均不能为负
    3. // 如果计算被中断, 则返回 -1
    4. func Add(ctx context.Context, a, b int) int {
    5. res := 0
    6. for i := 0; i < a; i++ {
    7. res = inc(res)
    8. select {
    9. case <-ctx.Done():
    10. return -1
    11. default:
    12. }
    13. }
    14. for i := 0; i < b; i++ {
    15. res = inc(res)
    16. select {
    17. case <-ctx.Done():
    18. return -1
    19. default:
    20. }
    21. }
    22. return res
    23. }
    24. // output:
    25. // Compute: 1+2, result: -1
    26. // Compute: 1+2, result: -1
    27. func main() {
    28. // 手动取消
    29. a := 1
    30. b := 2
    31. ctx, cancel := context.WithCancel(context.Background())
    32. go func() {
    33. time.Sleep(2 * time.Second)
    34. cancel() // 在调用处主动取消
    35. }()
    36. res := Add(ctx, 1, 2)
    37. }
    1. <a name="toc_4"></a>
    2. ### 4 closure与error
    3. ---
    4. golang中错误处理是一件令人头疼的事情:需要不断的写"if err != nil {}"这样的代码^_^。<br />golang官方的《Errors are values》(参考文档5)一文中给出了如下一段错误处理示例:<br />`Go _, err = fd.Write(p0[a:b]) if err != nil { return err } _, err = fd.Write(p1[c:d]) if err != nil { return err } _, err = fd.Write(p2[e:f]) if err != nil { return err } // and so on`这段代码示例的机巧之处在于:三个错误处理针对同一个函数fd.Write,这便能通过closure上下其手了,官方给出的第一个改进就是:<br />`Go var err error write := func(buf []byte) { if err != nil { return } _, err = w.Write(buf) } write(p0[a:b]) write(p1[c:d]) write(p2[e:f]) // and so on if err != nil { return err }`上面write closure虽然没有减少代码量,但使得代码优雅了不少。后面官方又给出了第二个优化:<br />Go type errWriter struct { w io.Writer err error }
    5. ```go
    6. func (ew *errWriter) write(buf []byte) {
    7. if ew.err != nil {
    8. return
    9. }
    10. _, ew.err = ew.w.Write(buf)
    11. }
    12. ew := &errWriter{w: fd}
    13. ew.write(p0[a:b])
    14. ew.write(p1[c:d])
    15. ew.write(p2[e:f])
    16. // and so on
    17. if ew.err != nil {
    18. return ew.err
    19. }

    ``这个代码示例把closure中的error放入了struct errWriter之中,使得代码更加精妙。<br />上面代码段中这个技巧被用到了bufio.Writer的实现上,所以调用(bufio.Writer)Write函数时候,不用不断检查其返回值error,其代码示例如下:<br />Go b := bufio.NewWriter(fd) b.Write(p0[a:b]) b.Write(p1[c:d]) b.Write(p2[e:f]) // and so on if b.Flush() != nil { return b.Flush() }`本节的技巧只有在同一个函数接口以及同一个处理对象error这样的情况下才可使用。

    6 总结


    本文总结了closure的本质以及其一些使用场景,囿于个人golang知识范围低下,暂时只能写这么多了。以后随着个人能力提升,我会逐渐补加此文。此记。

    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  9