Golang閉包


定義

函數可以嵌套定義(嵌套的函數一般為匿名函數),即在一個函數內部可以定義另一個函數。Go語言通過匿名函數支持閉包,C++不支持匿名函數,在C++11中通過Lambda表達式支持閉包。
閉包是由函數及其相關引用環境組合而成的實體(即:閉包=函數+引用環境)。

引用環境的定義:

在函數式語言中,當內嵌函數體內引用到體外的變量時,將會把定義時涉及到的引用環境和函數體打包成一個整體(閉包)返回。當每次調用包含閉包的函數時,都將返回一個新的閉包實例,這些實例之間是隔離的,分別包含調用時不同的引用環境現場。

不同於函數,閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例

關鍵知識點

  1. 閉包與逃逸分析

    閉包可能會導致變量逃逸到堆上來延長變量的生命周期,給GC帶來壓力

    func closure() func(int) int{
    	var x int
    	return func(a int) int {
    		x++
    		return a + x
    	}
    }
    func main() {
    	a := closure() //x逃逸到堆上,成為a閉包實例的一部分
    	fmt.Println(a(1)) //2
    	fmt.Println(a(1)) //3
    }
    
    
  2. 閉包與外部函數的生命周期

    注意:當closure函數被重新調用后,返回的是新的閉包實例,引用的變量也是重新在堆上定義的。

    func closure1(n int) func() {
    	n++
    	return func()  {
    		fmt.Println(n)
    	}
    }
    
    func closure2(n int) func() {
      return func(){
        n++
        fmt.Println(n)
      }
    }
    
    func main() {
    	a := closure1(3) //變量被匿名函數引用,closure函數結束,n變量也不會馬上銷毀,而是綁定到了匿名函數上
      b := closure2(3) //
    	a() //4
    	a() //4
      b() //4
      b() //5
    }
    
  3. 閉包在for循環中問題

    func main() {
    	s := []string{"a", "b", "c"}
    	for _, v := range s {
    		go func() {
    			fmt.Println(v)
    		}()
    	}
    	time.Sleep(time.Second * 1)
    }
    
    // c c c
    

    主協程執行完for之后,定義的子協程才開始執行,v最終是c,所以輸出了c c c。如果for過程中,子協程執行了,結果就可能不是c, c,c。輸出的結果依賴於子協程執行時的那一刻,v是什么

     func main() {        
          s := []string{"a", "b", "c"}
        for _, v := range s {
            go func() {
                fmt.Println(v)
            }()
            time.Sleep(time.Second * 3)
        }
        fmt.Println("main routine")
        time.Sleep(time.Second * 1)    // 阻塞模式
    }
    
    //a b c
    
    //還有一種辦法就是需要每次將變量v拷貝給函數即可,但此時就不是使用上下文環境中的變量了
    func main() {                
        s := []string{"a", "b", "c"}                             
        for _, v := range s { 
            go func(c string) {
                fmt.Println(c)
            }(v)   //每次將變量 v 的拷貝傳進函數                 
        }                        
        select {}                                                      
    }  
    
  4. 延遲調用與閉包

    defer中使用匿名函數依然是一個閉包

    package main
    
    import "fmt"
    
    func main() {
        x, y := 1, 2
    
        defer func(a int) { 
            fmt.Printf("x:%d,y:%d\n", a, y)  // y 為閉包引用
        }(x)      // 復制 x 的值
    
        x += 100
        y += 100
        fmt.Println(x, y)
    }
    
    /*
    101 102
    x:1 y:102
    */
    

    在defer定義時候已經將x的值1拷貝進了defer函數,defer執行時使用的是defer定義時x的拷貝,而不是當前環境中x的值

總結

其實閉包需要注意的關鍵點就是:當閉包引用外部變量時候,此變量的生命周期就不是它的作用域范圍了,而是被閉包實例捕獲,此時閉包不僅僅和外部函數共享此變量,更重要的是變量的生命周期因匿名函數也就是閉包的存在而延長


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM