色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

什么是閉包的延遲綁定

馬哥Linux運維 ? 來源:馬哥Linux運維 ? 作者:馬哥Linux運維 ? 2022-09-02 10:39 ? 次閱讀

本文講介紹以下幾個內容:

引入用GoLang語言寫的幾個case;

介紹什么是閉包;

介紹什么是閉包的延遲綁定;

從閉包的延遲綁定講到GoLang的Go Routine的延遲綁定問題;

I. 幾個有趣的Case

開門見山,首先請各位看官們先看下面foo1()到foo7()一共7個函數,然后回答后面的問題。(一下子丟出7個函數,請見諒。不過,每個函數都非常簡短,而本文接下來將圍繞這7個函數展開,因此,請各位看官老爺們耐心且看看題,活動活動腦細胞~)

case 1:


func foo1(x *int) func() {  return func() {    *x = *x + 1    fmt.Printf("foo1 val = %d
", *x)  }}
case 2:

func foo2(x int) func() {  return func() {    x = x + 1    fmt.Printf("foo2 val = %d
", x)  }}
case 3:

func foo3() {  values := []int{1, 2, 3, 5}  for _, val := range values {    fmt.Printf("foo3 val = %d
", val)  }}
case 4:

func show(v interface{}) {  fmt.Printf("foo4 val = %v
", v)}func foo4() {  values := []int{1, 2, 3, 5}  for _, val := range values {    go show(val)  }}
case 5:

func foo5() {  values := []int{1, 2, 3, 5}  for _, val := range values {    go func() {      fmt.Printf("foo5 val = %v
", val)    }()  }}
case 6:

var foo6Chan = make(chan int, 10)func foo6() {  for val := range foo6Chan {    go func() {      fmt.Printf("foo6 val = %d
", val)    }()  }}
case 7:

func foo7(x int) []func() {  var fs []func()  values := []int{1, 2, 3, 5}  for _, val := range values {    fs = append(fs, func() {      fmt.Printf("foo7 val = %d
", x+val)    })  }  return fs}

Q1:

第一組實驗:假設現在有變量x=133,并創建變量f1和f2分別為foo1(&x)和foo2(x)的返回值,請問多次調用f1()和f2()會打印什么?

第二組實驗:重新賦值變量x=233,請問此時多次調用f1()和f2()會打印什么?

第三組實驗:如果直接調用foo1(&x)()和foo2(x)()多次,請問每次都會打印什么?

Q2:
請問分別調用函數foo3(),foo4()和foo5(),分別會打印什么?

Q3:

第一組實驗:如果“幾乎同時”往channelfoo6Chan里面塞入一組數據"1,2,3,5",foo6會打印什么?

第二組實驗:如果以間隔納秒(10^-9秒)的時間往channel里面塞入一組數據,此時foo6又會打印什么?

第三組實驗:如果是微秒(10^-6秒)呢?如果是毫秒(10^-3秒)呢?如果是秒呢?

Q4:
請問如果創建變量f7s=foo7(11),f7s是一個函數集合,遍歷f7s會打印什么?

接下來,我們逐一來看這些問題和對應的foo函數。

II. case1~2:值傳遞(by value) vs. 引用傳遞(by reference)

子標題好難起 >0<... ? 看到case1和case2的兩組函數foo1()和foo2(),相信各位看官就知道,其中一個知識點就是值傳遞和引用傳遞。 ? 其實呢,Go是沒有引用傳遞的,即使是foo1()在參數上加了*,內部實現機制仍舊是值傳遞,只不過傳遞的是指針的數值。但是為了稱呼方便,下文會成為“引用傳遞”(為了區分正確的引用傳遞,這里特意加了引號)。 ? 如下圖所示,我們的目的是傳遞X變量,于是我們創建了一個傳參地址(臨時地址變量),它存放了X變量的地址值,調用函數的時候給它這個傳參地址,函數呢,則會再創建一個入參地址,是傳參地址的一份拷貝。函數拿到了這個地址值,可以通過尋址拿到這個X變量,此時函數如果直接修改X變量可以認為是“本地修改”或者“永久修改”了這個變量的數值。 ? 「舉個生活中的例子,比如一個叫做“函數”的人想尋找一個叫做“X”的人,函數跑過來問知道X的我,我拿出地址簿,給他出示了X這個人的家庭地址,函數記性不太好,所以拿了一本本子把X的地址抄在了他自己的本子上。」 ? 這個例子中,我的那個記著X的家庭地址的地址簿,就是傳參地址;函數抄錄了X地址的本子,就是入參地址;X的家庭地址,就對應了X變量的地址值。(哎,為什么講的這么細節了?) 2e5dc4ec-2a04-11ed-ba43-dac502259ad0.jpg ? Golang的“引用傳遞” ? 話題似乎有點扯遠了,拉回來,我們再來看看foo1()和foo2()。 ? foo1()和foo2()的區別確實在于值傳遞和引用傳遞,但是這個并不是本文介紹的中心。本文要介紹的已經在標題上寫明了:閉包(closure)。 ?

閉包(closure)

什么是閉包呢?摘用Wikipedia上的一句定義:

aclosureis a record storinga functiontogether withan environment.
閉包是由函數和與其相關的引用環境組合而成的實體 。

因此閉包的核心就是:函數和環境。其實這里就已經可以回答本文題目的問題:閉包究竟包了什么?答案是:函數和環境。但是相信部分看官們到這里依然不清楚:什么函數?什么環境? 函數,指的是在閉包實際實現的時候,往往通過調用一個外部函數返回其內部函數來實現的。內部函數可能是內部實名函數、匿名函數或者一段lambda表達式。用戶得到一個閉包,也等同于得到了這個內部函數,每次執行這個閉包就等同于執行內部函數。 環境,Wikipedia上說是與其(函數)相關的引用環境,可以說解釋地很精準了。 具體地說,在實際中引用環境是指外部函數的環境,閉包保存/記錄了它產生時的外部函數的所有環境。但是這段話對于尚未理解閉包的同學來說依舊是不友好的,聽完還是懵懂的。這里嘗試做個更實用性的解釋:

如果外部函數的所有變量可見性都是local的,即生命周期在外部函數結束時也結束的,那么閉包的環境也是封閉的。

反之,那么閉包其實不再封閉,全局可見的變量的修改,也會對閉包內的這個變量造成影響。

跳回foo1()和foo2()的例子,正好來解釋閉包的函數和環境。


func foo1(x *int) func() {    return func() {        *x = *x + 1        fmt.Printf("foo1 val = %d
", *x)    }}func foo2(x int) func() {    return func() {        x = x + 1        fmt.Printf("foo1 val = %d
", x)    }}
// Q1第一組實驗x := 133f1 := foo1(&x)f2 := foo2(x)f1() f2()f1()f2()// Q1第二組x = 233f1()f2()f1()f2()// Q1第三組foo1(&x)()foo2(x)()foo1(&x)()foo2(x)()
定義了x=133之后,我們獲取得到了f1=foo1(&x)和f2=foo2(x)。這里f1f2就是閉包的函數,也就是foo1()foo2()的內部匿名函數;而閉包的環境即外部函數foo1()foo2()的變量x(因為內部匿名函數引用到的相關變量只有x,因此這里簡化為變量x)。 閉包的函數做的事情歸納為:1). 將環境的變量x自增1;2). 打印環境變量x。 閉包的環境則是其外部函數獲取到的變量x。 因此Q1第一組實驗的答案為:

f1() // foo1 val = 134f2() // foo2 val = 134f1() // foo1 val = 135f2() // foo2 val = 135
這是因為閉包f1f2都保存了x=133時的整個環境,每次調用閉包f1f2都會執行一次自增+打印的內部匿名函數。因此第一次輸出都是(133+1=)134,第二次輸出都是(134+1=)135。 那么Q1第二組實驗的答案呢?

f1() // foo1 val = 234f2() // foo2 val = 136f1() // foo1 val = 235f2() // foo2 val = 137
有趣的事情發生了!f1的值居然發生了顯著性的變化!通過這組實驗,能夠更好地解釋其(函數)相關的引用環境其實就是產生這個閉包的時候的外部函數的環境,因此變量x的可見性和作用域也與外部函數相同,又因為foo1是“引用傳遞”,變量x的作用域不局限在foo1()中,因此當x發生變化的時候,閉包f1內部也變化了。這個也正好是"反之,那么閉包其實不再封閉,全局可見的變量的修改,也會對閉包內的這個變量造成影響"的證明。 Q1的第三組實驗的答案:

foo1(&x)() // foo1 val = 236foo2(x)() // foo2 val = 237foo1(&x)() // foo1 val = 237foo2(x)() // foo2 val = 238foo2(x)() // foo2 val = 238
因為foo1()返回的閉包都會修改變量x的數值,因此調用foo1()()之后,變量x必然增加1。而foo2()返回的閉包僅僅修改其內部環境的變量x而對調用外部的變量x不影響,且每次調用foo2()返回的閉包是獨立的,和其他調用foo2()的閉包不相關,因此最后兩次的調用,打印的數值都是相同的;第一次調用和第二次調用foo2()發現打印出來的數值增加了1,是因為兩次調用之間傳入的x的數值分別是236和237,而不是說第二次在第一次基礎上增加了1,這點需要補充說明。

III. case7:閉包的延遲綁定

hhh,是不是以為我會接著講case3,居然先提到了case7,意不意外驚不驚喜! 廢話不多說,看官們來瞅瞅下面調用f7()的時候分別會打印什么?


func foo7(x int) []func() {    var fs []func()    values := []int{1, 2, 3, 5}    for _, val := range values {        fs = append(fs, func() {            fmt.Printf("foo7 val = %d
", x+val)        })    }    return fs}// Q4實驗:f7s := foo7(11)for _, f7 := range f7s {    f7()}
答案是:

foo7 val = 16foo7 val = 16foo7 val = 16foo7 val = 16
是的,你沒有看錯,會打印4行,且都是16!是不是很驚喜! 相信已經有很多同學在網上看到過類似的case,并且也早已知道結果了,不清楚的同學們現在也看到答案了。嗯,這就是大名鼎鼎的閉包延遲綁定問題。網上的解釋其實有很多了,這里嘗試用之前對于閉包的環境的定義來解釋這個現象: “ 閉包是一段函數和相關的引用環境的實體。case7的問題中,函數是打印變量val的值,引用環境是變量val。僅僅是這樣的話,遍歷到val=1的時候,記錄的不應該是val=1的環境嗎? 上文在閉包解釋最后,還有一句話:閉包保存/記錄了它產生時的外部函數的所有環境。如同普通變量/函數的定義和實際賦值/調用或者說執行,是兩個階段。閉包也是一樣,for-loop內部僅僅是聲明了一個閉包,foo7()返回的也僅僅是一段閉包的函數定義,只有在外部執行了f7()時才真正執行了閉包,此時才閉包內部的變量才會進行賦值的操作。哎,如果這么說的話,豈不是應該拋出異常嗎?因為val是一個比foo7()生命周期更短的變量啊? 這就是閉包的神奇之處,它會保存相關引用的環境,也就是說,val這個變量在閉包內的生命周期得到了保證。因此在執行這個閉包的時候,會去外部環境尋找最新的數值!你是不是不相信?來來來,我們馬上寫個臨時的case執行下分分鐘就明白了:

臨時的case:


func foo0() func() {    x := 1    f := func() {        fmt.Printf("foo0 val = %d
", x)    }    x = 11    return f}
foo0()() // 猜猜我會輸出什么?
既然我說會在執行的時候去外部環境尋找最新的數值,那x的最新數值就是11呀,果然,最后輸出的就是11。 以上就是我對于閉包的延遲綁定的通俗版本解釋。:)

IV. case3~6:Go Routine的延遲綁定

case3、case4和case5不是閉包,case3只是遍歷了內部的slice并且打印,case4是在遍歷時通過協程調用了打印函數打印,case5也是在遍歷slice時調用了內部匿名函數打印。 Q2的case3問題的答案先丟出來:


func foo3() {    values := []int{1, 2, 3, 5}    for _, val := range values {        fmt.Printf("foo3 val = %d
", val)    }}
foo3()//foo3 val = 1a//foo3 val = 2//foo3 val = 3//foo3 val = 5
中規中矩,遍歷輸出slice的內容:1,2,3,5。 Q2的case4問題的答案再丟出來:

func show(v interface{}) {    fmt.Printf("foo4 val = %v
", v)}func foo4() {    values := []int{1, 2, 3, 5}    for _, val := range values {        go show(val)    }}
foo4()//foo3 val = 2//foo3 val = 3//foo3 val = 1//foo3 val = 5
嗯,因為Go Routine的執行順序是隨機并行的,因此執行多次foo4()輸出的順序不一行相同,但是一定打印了“1,2,3,5”各個元素。 最后是Q2的case5問題的答案:

func foo5() {    values := []int{1, 2, 3, 5}    for _, val := range values {        go func() {            fmt.Printf("foo5 val = %v
", val)        }()    }}
foo5()//foo3 val = 5//foo3 val = 5//foo3 val = 5//foo3 val = 5
居然都打印了5,驚不驚喜,意不意外?!相信看過子標題的你,一定不意外了(捂臉)。是的,接下來就要講講Go Routine的延遲綁定: 其實這個問題的本質同閉包的延遲綁定,或者說,這段匿名函數的對象就是閉包。在我們調用go func() { xxx }()的時候,只要沒有真正開始執行這段代碼,那它還只是一段函數聲明。而在這段匿名函數被執行的時候,才是內部變量尋找真正賦值的時候。 在case5中,for-loop的遍歷幾乎是“瞬時”完成的,4個Go Routine真正被執行在其后。矛盾是不是產生了?這個時候for-loop結束了呀,val生命周期早已結束了,程序應該報錯才對呀? 回憶上一章,是不是一個相同的情境?是的,這個匿名函數可不就是一個閉包嗎?一切就解釋通了:閉包真正被執行的時候,for-loop結束了,但是val的生命周期在閉包內部被延長了且被賦值到最新的數值5。 不知道各位看官是否好奇,既然說Go Routine執行的時候比for-loop慢,那如果我在遍歷的時候增加sleep機制呢?于是設計了Q3實驗:

var foo6Chan = make(chan int, 10)func foo6() {    for val := range foo6Chan {        go func() {            fmt.Printf("foo6 val = %d
", val)        }()    }}// Q3第一組實驗go foo6()foo6Chan <- 1foo6Chan <- 2foo6Chan <- 3foo6Chan <- 5// Q3第二組實驗foo6Chan <- 11time.Sleep(time.Duration(1) * time.Nanosecond)foo6Chan <- 12time.Sleep(time.Duration(1) * time.Nanosecond)foo6Chan <- 13time.Sleep(time.Duration(1) * time.Nanosecond)foo6Chan <- 15// Q3第三組實驗// 微秒foo6Chan <- 21time.Sleep(time.Duration(1) * time.Microsecond)foo6Chan <- 22time.Sleep(time.Duration(1) * time.Microsecond)foo6Chan <- 23time.Sleep(time.Duration(1) * time.Microsecond)foo6Chan <- 25time.Sleep(time.Duration(10) * time.Second)// 毫秒foo6Chan <- 31time.Sleep(time.Duration(1) * time.Millisecond)foo6Chan <- 32time.Sleep(time.Duration(1) * time.Millisecond)foo6Chan <- 33time.Sleep(time.Duration(1) * time.Millisecond)foo6Chan <- 35time.Sleep(time.Duration(10) * time.Second)// 秒foo6Chan <- 41time.Sleep(time.Duration(1) * time.Second)foo6Chan <- 42time.Sleep(time.Duration(1) * time.Second)foo6Chan <- 43time.Sleep(time.Duration(1) * time.Second)foo6Chan <- 45time.Sleep(time.Duration(10) * time.Second)// 實驗完畢,最后記得關閉channelclose(foo6Chan)
嘗試執行了多次,第一組答案如下:

foo6 val = 5/3foo6 val = 5foo6 val = 5foo6 val = 5
絕大部分時候執行出來都是5。 第二組答案如下:

foo6 val = 15/13/11/12foo6 val = 15/13foo6 val = 15foo6 val = 15
絕大部分時候執行得到的都是15。 第三組答案如下:

// 微秒foo6 val = 23/21foo6 val = 23/22foo6 val = 25/23foo6 val = 25// 毫秒foo6 val = 31foo6 val = 32foo6 val = 33foo6 val = 35// 秒foo6 val = 41foo6 val = 42foo6 val = 43foo6 val = 45
毫秒和秒的兩組非常確定,順序輸出。但是微妙就不一定了,有時候是順序輸出,大部分時候是隨機輸出如“22,22,23,25”或者“21,22,25,25”之類的。 可見,Go Routine的匿名函數從定義到執行,耗時時間在微妙上下。于是又增加了一個臨時的case測試了其真正的耗時大約是多少。

又一個臨時的case:


func foo8() {    for i := 1; i < 10; i++ {        curTime := time.Now().UnixNano()        go func(t1 int64) {            t2 := time.Now().UnixNano()            fmt.Printf("foo8 ts = %d us 
", t2-t1)        }(curTime)    }}
foo8()
執行下來發現耗時在5微秒~60微秒之間不等。 但是,以上的實驗數據都是從我的iMac本子上得到的,該本子的CPU是i7-7700K 4.2GHz;我又放在筆記本上(CPU為i5-8250U 1.6GHz 1.8GHz)運行了下,發現居然耗時是0微秒!起初我懷疑是時間精度的問題,于是把t1和t2時間都打印出來,精度是可以達到納秒的。抱著仍舊不信的想法,重新運行了第三組實驗,每一個都是順序輸出的! 好吧,回頭再說我的iMac的問題。現在只需要記住一點:Go Routine的匿名函數的延遲綁定本質就是閉包,實際生成中注意下這種寫法~

寫在后面

最后,閉包是個常見的玩意兒,但是實際代碼中不太建議使用,一不小心寫了個內存泄漏查都查不到。特別是不要為了炫技故意寫個閉包,實在沒有必要。

審核編輯:彭靜
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據
    +關注

    關注

    8

    文章

    7018

    瀏覽量

    89013
  • 函數
    +關注

    關注

    3

    文章

    4331

    瀏覽量

    62595
  • 閉包
    +關注

    關注

    0

    文章

    4

    瀏覽量

    2052

原文標題:Golang:“閉包(closure)”到底包了什么?

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Linux內核驅動與單個PCI設備的綁定和解綁定

    在Linux內核2.6.13-rc3以前,驅動和設備之間的綁定和解綁只能通過insmod(modprobe)和rmmod來實現,但是這種實現方法有一個弊端,就是一旦綁定或者解綁定都是針對驅動與其
    的頭像 發表于 11-17 17:11 ?1668次閱讀
    Linux內核驅動與單個PCI設備的<b class='flag-5'>綁定</b>和解<b class='flag-5'>綁定</b>

    綁定”成功后,請問怎么通過綁定的設備向被綁定的設備發送控制命令?

    。抓都正常響應了響應的respone。但是,我不知道怎么通過綁定的設備向被綁定的設備發送控制命令。比如,一個開關和燈實現了綁定,這個綁定
    發表于 05-22 00:08

    詞法作用域和

    #hello,JS:14(詞法作用域)
    發表于 05-20 15:35

    ECMAScript的與匿名函數你分的清嗎?

    重拾ECMAScript基礎——與匿名函數
    發表于 11-08 09:39

    javascript是什么_javascript有什么作用

    首先從一個經典錯誤談起,頁面上有若干個div, 我們想給它們綁定一個onclick方法,于是有了下面的代碼很簡單的功能可是卻偏偏出錯了,每次alert出的值都是簡單的修改就好使了讓我們從一些基礎的知識談起,首先了解一下內部函數。
    發表于 12-05 10:40 ?1789次閱讀
    javascript<b class='flag-5'>閉</b><b class='flag-5'>包</b>是什么_javascript<b class='flag-5'>閉</b><b class='flag-5'>包</b>有什么作用

    javascript原理及應用

    官方對的解釋是:一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。作為一個函數變量的一個引用,當函數返回時,其處于激活狀態。
    發表于 12-05 10:55 ?1215次閱讀
    javascript<b class='flag-5'>閉</b><b class='flag-5'>包</b>原理及應用

    javascript的優缺點有哪些

    是javascript的一大難點,也是它的特色。很多高級應用都要依靠來實現。要理解
    發表于 12-05 11:10 ?9329次閱讀

    用最簡單的語言解釋Python的是什么?

    很藍瘦,你應該盡量理解一下它。
    的頭像 發表于 03-21 16:33 ?2112次閱讀

    詳細介紹go語言中的的實現

    什么是? 什么場景下會用 ? 本文對 go 語言中的做了詳細介紹。
    的頭像 發表于 10-20 16:18 ?1858次閱讀

    帶你了解go語言中的

    ? 【 導讀】什么是? 什么場景下會用 ? 本文對 go 語言中的做了詳細介紹。
    的頭像 發表于 11-02 15:27 ?2445次閱讀

    無縫綁定和非綁定支持

    自動獲取并同步來自任何應用程序的綁定數據。NET源代碼,包括交錯數組、任何實現IListSource或IList的對象,以及任何ADO。NET數據集、DataTable、DataView或DataViewManager對象。
    的頭像 發表于 01-30 15:26 ?463次閱讀

    教程 5:配對、綁定和安全

    教程 5:配對、綁定和安全
    發表于 03-15 19:39 ?0次下載
    教程 5:配對、<b class='flag-5'>綁定</b>和安全

    教程 5:配對、綁定和安全

    教程 5:配對、綁定和安全
    發表于 07-06 18:49 ?0次下載
    教程 5:配對、<b class='flag-5'>綁定</b>和安全

    Rust語言中的應用場景

    Rust語言的是一種可以捕獲外部變量并在需要時執行的匿名函數。包在Rust中是一等公民,它們可以像其他變量一樣傳遞、存儲和使用。
    的頭像 發表于 09-20 11:25 ?599次閱讀

    工業交換機的零延遲和零丟

    在現代工業自動化和網絡通信的快速發展中,工業交換機作為連接各類設備的核心元素,其性能和穩定性顯得尤為重要。零延遲和零丟的概念不僅是技術上的追求,更是推動工業智能化進程的重要保障。傳統網絡在數
    的頭像 發表于 09-24 15:52 ?232次閱讀
    主站蜘蛛池模板: 国产乱色伦影片在线观看| 3D动漫网站HOXXXxes| 少妇高潮A视频| 日本久久久WWW成人免费毛片丨| 久久青青热| 久久观看视频| 久久久久久久尹人综合网亚洲| 狠狠色综合久久丁香婷婷| 国产亚洲精品A久久777777| 国产乱对白精彩在线播放| 国产精品乱人无码伦AV在线A| 国产成人久久精品激情| 国产成人免费观看| 国产三级在线免费| 国色精品VA在线观看免费视频| 国精产品一区二区三区四区糖心| 好男人午夜www视频在线观看| 精品国产品在线18年| 精品一成人岛国片在线观看| 久久青草费线频观看国产| 久久永久影院免费| 男人插女人逼逼| 欧美亚洲韩日午夜| 桃花免费高清在线观看| 亚洲 综合 欧美在线 热| 亚洲专区区免费| 18日本人XXXXXX18| 白银谷在线观看| 国产精品一区二区AV交换| 后入内射国产一区二区| 快乐激情网| 日本亚欧热亚洲乱色视频| 微拍秒拍99福利精品小视频| 亚洲色噜噜狠狠站欲八| 91国在线产| 粗壮挺进邻居人妻无码| 国产欧美日韩亚洲第一页| 久久久国产精品免费A片3D| 欧美白人极品性喷潮| 色欲久久99精品久久久久久AV| 亚洲 自拍 清纯 综合图区|