1. 程式人生 > >spark閉包

spark閉包

定義

關於閉包有太多種解釋,但基本上都很難用一兩句解釋清楚,下面這句簡短的定義是我見過的最精煉且準確的解釋了:

A closure is a function that carries an implicit binding to all the variables referenced within it. In other words, the function (or method) encloses a context around the things it references.  

首先,閉包是一個函式,然後,也是最本質的地方:這個函式內部會引用(依賴)到一些變數,這些變數既不是全域性的也不是區域性的,而是在定義在上下文中的(這種變數被稱為“自由變數”,我們會在稍後的例子中看到這種變數),閉包的“神奇”之處是它可以“cache”或者說是持續的“trace”它所引用的這些變數。(從語言實現層面上解釋就是:這些變數以及它們引用的物件不會被GC釋放)。同樣是這件事情,換另一種說法就是:閉包是一個函式,但同時這個函式“背後”還自帶了一個“隱式”的上下文儲存了函式內部引用到的一些(自由)變數。

第一個例子

第二個例子

對兩個例子的補充

上述兩個例子的程式碼都在解釋閉包的概念,但是解釋的角度不太一樣,相對而言第二個例子揭示地更為深刻一些,它揭示閉包會隱式地持續trace(也就是不會被垃圾回收)它所使用的那些自由變數!

每當我們去呼叫一個閉包時,腦子裡一定要意識到:閉包不單單是定義它的那段程式碼,同時還有一個繫結在它“後面”(隱式的)的持續保持它所引用的所有自用變數的一個“上下文”(“環境”)!

更透徹地理解:閉包產生的根源

某種角度上,我們可以說閉包是函式字面量的一個“衍生品”。函式字面量的存在使得函式的定義與普通變數無異,也就是val 變數名=函式字面量,既然普通變數在賦值時可以引用另一個變數的值,那麼定義函式時,在函式字面量裡引用其他變數也變成非常自然的事情(而在傳統的函式體內是沒有辦法直接引用函式體外部的變數的),比如,像下面這樣定義普通變數是非常常見的:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">var</span> a=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">var</span> b=a+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

顯然變數b的賦值過程中引用了變數a. 同樣的,在函式程式語言裡,像下面這樣定義函式:

<code class="hljs coffeescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-reserved" style="box-sizing: border-box;">var</span> a=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>;
val <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-title" style="box-sizing: border-box;">b</span>=<span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">()</span>=></span>a+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

又有何不可呢?這時b成了就成了典型的閉包,它所引用的變數的a是定義在上下文的。

為什麼需要閉包

閉包被創造出來顯然是因為有場景需要的。一個最為普遍和典型的使用場合是:推遲執行。我們可以把一段程式碼封裝到閉包裡,你可以等到“時機”成熟時去執行它。比如:在Spark裡,針對RDD的計算任務都要分佈到每個節點(準確的說是executor)上並行處理,Spark就需要封裝一個閉包,把相關的操作(方法)和需要的變數引入到閉包中分發給節點執行。