zoukankan      html  css  js  c++  java
  • golang学习笔记 ---匿名函数/闭包

    匿名函数

    var f = func(int) {}
    
    func main() {
    	f = func(i int) {
    		fmt.Println(i)
    	}
    	f(2)
    	f = func(i int) {
    		fmt.Println(i * i * i)
    	}
    	f(2)
    }
    /*
    输出:
    2
    8
    */
    接:https://blog.csdn.net/qq_35976351/article/details/81986496
    

      上述代码中,f可以被任何输入一个整型,无返回值的函数给赋值,这类似于C++中的函数指针。因此f可以看成是一个函数类型的变量。这样,可以动态的改变f的功能。匿名函数可以动态的创建,与之成对比的常规函数必须在包中编译前就定义完毕。匿名函数可以随时改变功能。

    闭包

    闭包是匿名函数与匿名函数所引用环境的组合。匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。这就类似于常规函数直接使用全局变量一样,匿名函数和它引用的变量以及环境,类似常规函数引用全局变量处于一个包的环境。

    func main() {
    	n := 0
    	f := func() int {
    		n += 1
    		return n
    	}
    	fmt.Println(f())  // 别忘记括号,不加括号相当于地址
    	fmt.Println(f())
    }
    /*
    输出:
    1
    2
    */
    

      

    在上述代码中,

    n := 0
    f := func() int {
    	n += 1
    	return n
    }  

    就是一个闭包,类比于常规函数+全局变量+包。f不仅仅是存储了一个函数的返回值,它同时存储了一个闭包的状态。

    闭包作为函数返回值

    匿名函数作为返回值,不如理解理解为闭包作为函数的返回值,如下代码:

    func Increase() func() int {
    	n := 0
    	return func() int {
    		n++
    		return n
    	}
    }
    
    func main() {
    	in := Increase()
    	fmt.Println(in())
    	fmt.Println(in())
    }
    /*
    输出:
    1
    2
    */
    

      闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量in中,直到in被销毁,整个闭包也被销毁。

    Golang并发中的闭包

    Go语言的并发时,一定要处理好循环中的闭包引用的外部变量。如下代码:

    func main() {
    	runtime.GOMAXPROCS(runtime.NumCPU())
    
    	var wg sync.WaitGroup
    	for i := 0; i < 5; i++ {
    		wg.Add(1)
    		go func() {
    			fmt.Println(i)
    			wg.Done()
    		}()
    	}
    	wg.Wait()
    }

      输出: 5 5 5 5 5

    这种现象的原因在于闭包共享外部的变量i,注意到,每次调用go就会启动一个goroutine,这需要一定时间;但是,启动的goroutine与循环变量递增不是在同一个goroutine,可以把i认为处于主goroutine中。启动一个goroutine的速度远小于循环执行的速度,所以即使是第一个goroutine刚起启动时,外层的循环也执行到了最后一步了。由于所有的goroutine共享i,而且这个i会在最后一个使用它的goroutine结束后被销毁,所以最后的输出结果都是最后一步的i==5。

    我们可以使用循环的延时在验证上述说法:

    package main
    
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    	"time"
    )
    
    
    func main() {
    	runtime.GOMAXPROCS(runtime.NumCPU())
    
    
    	var wg sync.WaitGroup
    	for i := 0; i < 5; i++ {
    		wg.Add(1)
    		go func() {
    			fmt.Println(i)
    			wg.Done()
    		}()
    		time.Sleep(1 * time.Second)
    	}
    	wg.Wait()
    }
    
    
    /* 输出结果: 0 1 2 3 4 */

      

    每一步循环至少间隔一秒,而这一秒的时间足够启动一个goroutine了,因此这样可以输出正确的结果。

    在实际的工程中,不可能进行延时,这样就没有并发的优势,一般采取下面两种方法:

    共享的环境变量作为函数参数传递:
    package main

    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    func main() {
    	runtime.GOMAXPROCS(runtime.NumCPU())
    
    	var wg sync.WaitGroup
    	for i := 0; i < 5; i++ {
    		wg.Add(1)
    		go func(i int) {
    			fmt.Println(i)
    			wg.Done()
    		}(i)
    
    	}
    	wg.Wait()
    }
    /* 输出: 4 2 3 0 1
    */

     输出结果不一定按照顺序,这取决于每个goroutine的实际情况,但是最后的结果是不变的。可以理解为,函数参数的传递是瞬时的,而且是在一个goroutine执行之前就完成,所以此时执行的闭包存储了当前i的状态。

    2.使用同名的变量保留当前的状态

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    func main() {
    	runtime.GOMAXPROCS(runtime.NumCPU())
    
    	var wg sync.WaitGroup
    	for i := 0; i < 5; i++ {
    		wg.Add(1)
    		i := i
    		go func() {
    			fmt.Println(i)
    			wg.Done()
    		}()
    
    	}
    	wg.Wait()
    }
    

      

    同名的变量i作为内部的局部变量,覆盖了原来循环中的i,此时闭包中的变量不在是共享外循环的i,而是都有各自的内部同名变量i,赋值过程发生于循环goroutine,因此保证了独立。

  • 相关阅读:
    body test
    menu
    Toolbutton test itk_component
    grid pack
    double inherit
    set font
    Activity test itk_component
    Activity test no class
    按键驱动调试总结
    system.img解包打包的方法
  • 原文地址:https://www.cnblogs.com/saryli/p/12170565.html
Copyright © 2011-2022 走看看