热门问题
时间线
聊天
视角
Future與promise
异步处理 来自维基百科,自由的百科全书
Remove ads
在電腦科學中,future、promise、delay和deferred,是在某些並行程式語言中,指稱用於同步程式執行的一種構造。由於某些計算尚未結束,故而需要一個對象來代理這個未知的結果。這種構造起源於函數式程式設計和相關範式如邏輯編程,其目的是將值與其運算過程解耦,從而允許更靈活地進行計算,特別是通過將其並列化來進行。後來它在分散式計算中得到了應用,用來減少網路通訊往返的延遲。再後來async/await語法機制使其變得更有用,籍此允許以直接風格編寫非同步程式,而不再採用續體傳遞風格。
術語
在1976年Daniel P. Friedman和David Wise提出了術語「promise」[1],同年Peter Hibbard稱之為「eventual」[2]。1977年Henry Baker和Carl Hewitt在一篇論文中介紹了一個類似的概念「future」[3]。
術語「future」、「promise」、「delay」和「deferred」通常用來稱謂同樣一種機制,在特定實現中可能只選用其中之一。在這種情況下,值與其運算過程是一起建立並且相互關聯的:future若指稱推遲設定的一個值[4],設定它的函式就可稱謂為promise;promise若指稱推遲設定的一個值[5],設定它的函式就可稱謂為resolver;promise若指稱一個非同步函式[1],它的結果值所設定的就可稱謂為future。設定一個推遲值也稱為「決定/解決」(resolve)、「履行/實現」(fulfil)或「繫結」(bind)它。
推遲值及其設定者也存在區分稱謂而同時使用的情況,二者的這種用法在不同實現中的差異將在專門章節中討論。具體來說,future指稱一個「唯讀」的變數的預留位置視圖,而promise指稱一個可寫的單賦值容器,使用其「成功」方法來設定這個future的值[6]。尤其是在定義future之時,無須指定設定其值的promise;並且可能有不同的promise,設定同一個future的值;但是對於一個給定future,僅可以進行一次設定。
Remove ads
歷史
future及/或promise構造,首先實現於程式語言例如MultiLisp[4]和Act 1之中。在並行邏輯編程語言中,使用非常類似於future的邏輯變數進行通訊[7]。這種語言開始於1982年的「Prolog with Freeze」和「IC Prolog」,並且在後來的語言中變成了真正的並行原語,比如Concurrent Prolog、Parlog、守衛霍恩子句(GHC)、Strand、Janus、Oz(Mozart)和Alice ML[8][9]。叫做「I-變數」的單賦值變數,非常類似於並行邏輯變數,它起源於資料流程編程語言Id的「I-結構」元件[10],並且包含在Reppy的Concurrent ML中[11]。
在1988年Barbara Liskov和Liuba Shrira,發明了promise管線技術來克服傳輸延遲[5];Liskov和Shrira在論文中使用了術語「promise」,但是他們採用了現在少見的名稱「call-stream」來提及管線機制。在大約1989年Mark S. Miller、Dean Tribble和Rob Jellinghaus,於Xanadu專案中也獨立發明了此技術[12]。
Liskov和Shrira的論文中描述的設計,以及Xanadu中的promise管線的實現,都有一個限制,即promise值不是頭等的:call或send的參數或返回值,不能直接是promise。在Liskov和Shrira論文中使用的程式語言Argus,直至大約1988年停止開發,似乎都未曾在任何公開發布中實現promise和call-stream[13][14]。Xanadu實現的promise管線,僅在1999年Udanax Gold的原始碼發布時才公開發布[15],並且在任何已發布的文件中都沒有解釋過[16]。
一些早期的演員語言,包括Act系列[17][18],支援並列訊息傳遞和管線式訊息處理,但不支援promise管線。Joule和E語言的後續實現,支援完全頭等的promise和resolver,還有promise管線。
2000年之後,由於訊息模式的請求-回應模型,在使用者介面回應力和Web開發中的應用,future和promise重新引起了人們的興趣。現在一些主流語言對future和promise有了語言支援,最著名的是2004年發行的Java 5中的FutureTask
[19],以及2012年發行的.NET框架 4.5中的async
/await
結構[20][21],它在很大程度上受到可追溯到2007年的「F#非同步編程模型」的啟發[22][23]。async/await隨後被其他語言採用,特別是2014年的Dart 1.9[24]、2014年發行的Hack(HHVM)、2015年的Python 3.5[25]、ECMAScript 2017、2019年的Rust 1.39和C++20等。
Remove ads
實現列表
下面列出支援future、promise和並行邏輯變數、資料流程變數或I-變數的語言,包括直接在語言中支援還有在標準庫中支援:
- ABCL/f[26]
- Alice ML,支援future[9][27]
- AmbientTalk,包含頭等的resolver和唯讀promise
- C++,從C++11開始有std::future和std::promise
- Concurrent ML,僅限I-變數和M-變數[11]
- Crystal
- Dart,使用Future/Completer類[28]以及關鍵字
async
/await
[24] - Elm,通過Task模組[29]
- Glasgow Haskell,僅限同步可變變數「MVars」[30]
- Id,僅限I-結構和M-結構[10]
- Io[31]
- Java,通過Future[32]或CompletableFuture[33]
- JavaScript,始於ECMAScript 2015提供
Promise
對象[34],並且自從ECMAScript 2017可通過關鍵字async
/await
來使用[35] - Lucid,僅限作為串流的變數
- 一些Lisp
- .NET,自.NET框架 4.0起通過TPL(任務並列庫)[38]
- C#,自.NET框架 4.5起[20],通過關鍵字
async
/await
[21] - Visual Basic,自從版本11,通過關鍵字
Async
/Await
[21]
- C#,自.NET框架 4.5起[20],通過關鍵字
- Kotlin,但是
kotlin.native.concurrent.Future
通常只在書寫本機執行代碼時使用[39] - Nim,使用
async
/await
語法 - Oxygene
- Oz版本3 [40]
- Python,自從2011年版本3.2提供concurrent.futures模組[41][42],這是由PEP 3148所提議[43],而2015年版本3.5增加了基於低層future的
asyncio
模組,它採用高層async
/await
語法[44] - R,延遲計算的promise,仍然是單執行緒
- Raku[45]
- Rust,通常經由
.await
完成[46] - Scala,通過scala.concurrent包[47]
- Scheme,僅延遲求值
- Squeak Smalltalk
- Strand
還支援promise管線的語言套件括:
- E
- Joule
Remove ads
- 對於Common Lisp:
- 對於C++:
- 對於C#和其他.NET語言:
- Parallel Extensions庫
- 對於Groovy:
- GPars[58]
- 對於JavaScript:
- Promises/A+標準網站[59],有此標準的實現的列表[60]
- Cujo.js[61]的when.js[62],提供的promise符合Promises/A+ 1.1標準
- Dojo Toolkit,提供promise和Twisted風格Deferred[63]
- MochiKit[64],受Twisted的Deferred的啟發
- jQuery[65]的Deferred對象[66],基於了CommonJS Promises/A規定[67]
- AngularJS[68]
- Q[69],符合Promises/A+ 1.1標準
- RSVP.js[70],符合Promises/A+ 1.1標準
- Bluebird[71]
- Closure庫的promise包[72],符合Promises/A+標準
- 對於Java:
- 對於Objective-C:
- 對於OCaml:
- Lazy模組實現了懶惰的顯式future[82]
- 對於Perl:
- 對於PHP:
- React/Promise [86]
- 對於Python:
- 對於R:
- 對於Ruby:
- 對於Rust:
- futures-rs[94]
- 對於Scala:
- Twitter的util庫[95]
- 對於Swift:
- 對於Tcl:
- tcl-promise[102]
Remove ads
future可以用協程或生成器實現[25],從而具有相同的求值策略(例如協同多工或延遲求值)。
future還可以很容易地用通道實現:future是一個單元素的通道,而promise是一個傳送到通道,實現future的過程。這允許future在支援通道(如CSP和Go)的並行程式語言中實現[103]。由此產生的future是顯式的,因為它們必須通過從通道讀取而不是僅僅通過求值來取得。
程式語言典型範例
Python的concurrent.futures
模組,提供了非同步執行可呼叫對象的高層介面。非同步執行可以使用執行緒池執行器ThreadPoolExecutor
,通過多個執行緒來進行;或使用行程池執行器ProcessPoolExecutor
,通過分立的多個行程來進行。concurrent.futures.Future
類封裝了可呼叫對象的非同步執行,其實例由執行器抽象類Executor
的這兩個子類的submit()
方法建立[41]。
在下面的例子中,定義了load_url()
,用來檢索一個URL所指定的單一網頁並報告其內容。使用with
語句指定採用執行緒池執行器,這確保了執行緒由它及時清理。用這個執行器的submit()
方法啟動所有的裝載操作任務,並使用字典推導式為其建立的每個future標記上對應的URL。採用模組函式as_completed()
,在指定的Future
類的諸實例「已齊全」(completed)之時,建立在其上的迭代器。
import concurrent.futures
import urllib.request
URLS = [
'https://www.python.org/',
'https://pypi.org/search/',
'https://no-such-url'
]
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_url = {executor.submit(load_url, url, 60) : url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print(f'{url!r} generated an exception: {exc!s}')
else:
print(f'{url!r} page is {len(data):d} bytes')
在CPython中,由於全域直譯器鎖(GIL),保證一時只有一個執行緒可以執行Python位元組碼;一些擴充模組被設計為在進行計算密集任務時釋放GIL,還有在進行I/O時總是釋放GIL。執行緒池執行器的max_workers
參數的預設值是min(32, os.cpu_count() + 4)
,這個預設值為I/O踴躍任務保留至少5個worker執行緒,為釋放GIL的CPU踴躍任務利用最多32個CPU邏輯核心。想要更好利用多核心機器的計算資源,可以使用行程池執行器或多行程模組multiprocessing
。對於同時執行多個I/O踴躍任務,仍然適合採用執行緒池執行器或執行緒模組threading
。
Python的非同步I/O模組asyncio
,是採用async/await語法編寫並行代碼的庫[104]。asyncio
模組基於低層事件迴圈,提供的高層API包括了:並行執行Python協程並擁有在其執行上的完全控制,進行網路IO和IPC,控制子行程,通過佇列分布任務和同步並行代碼。asyncio
模組的同步原語,在設計上類似於threading
模組的同步原語,但與之相比有兩個重要差異:asyncio
模組的同步原語不是執行緒安全的,故而不能用於OS執行緒同步;這些同步原語的方法不接受逾時實際參數。
asyncio
模組提供的低層API中有asyncio.Future
對象,用來橋接低層基於回呼的代碼和高層採用async
/await
語法的代碼,它在設計上模仿了concurrent.futures.Future
對象。這兩個模組的Future
對象的set_result()
方法標記這個Future
對象為「已完畢」(done)並設定它的結果,而set_exception()
方法標記這個Future
對象為已完畢並設定一個例外;done()
方法在這個Future
對象已完畢時返回True
;result()
方法返回這個Future
對象所設定的結果,或者引發其所設定的例外。
二者的關鍵差異包括了:asyncio.Future
是可期待(awaitable)對象,而concurrent.futures.Future
對象不可以被期待(awaited)。asyncio.Future.result()
不接受逾時實際參數,如果此刻這個Future
對象的結果仍不可獲得,它引發一個InvalidStateError
例外;而concurrent.futures.Future.result()
,可接受逾時(timeout
)實際參數,如果此刻結果仍未「完全」(completed),它等待指定時間後若仍未完全則引發TimeoutError
例外,如果逾時未指定或指定為None
,則對等待時間沒有限制。
Remove ads
在JavaScript中,Promise
對象表示一個非同步運算的最終完成或失敗,及其結果值或失敗理由。Promise
對象是對一個值的代理(proxy),這個值在建立這個promise之時不必需已知。promise允許為非同步行動的最終成功值或失敗理由關聯上處理器,這使得非同步方法像同步方法那樣返回值:並非立即返回最終值,非同步方法返回一個promise來在將來的某一點上提供這個值。
在JavaScript生態系統中,Promise
對象在成為語言本身的一部份之前很久就有了多種實現[105]。儘管各有不同的內部表示,至少幾乎所有類似Promise
的對象都實現了「可接續」(thenable)介面[59]。可接續對象實現了.then()
方法,Promise
對象就是可接續的。
一個promise被稱為已落實(settled),如果它要麼已履行(fulfilled)要麼已拒絕(rejected),而不再待定(pending)。Promise
實例的.then()
方法,接受一到二個實際參數,它們是作為處理器非同步執行的回呼函式,分別針對了這個promise的已履行和已拒絕情況;此方法返回一個新的promise,它決定(resolve)出其所呼叫處理器的返回值,或者在原來這個promise未被處理的情況下,仍決定出其已落實的值。Promise
實例的.catch()
方法,實際上就是置空針對已履行情況的回呼函式,而只有針對已拒絕情況的回呼函式的.then()
方法。
Promise()
構造子建立Promise
對象,它主要用於包裝仍基於回呼的API所提供的非同步運算,要注意它只能通過new
算子來構造:new Promise(executor)
。Promise()
構造子的實際參數叫做「執行器」(executor),它是由這個構造子同步執行的一個函式,它有可自行指定名字的兩個函式形式參數,比如指定為resolveFunc
和rejectFunc
,這兩個函式接受任何類型的單一實際參數。
Promise()
構造子返回的Promise
對象,在要麼resolveFunc
函式要麼rejectFunc
函式被呼叫之時,它就成為「已決定」(resolved)的。要注意如果在呼叫這兩個函式之一的時候,將另一個Promise
對象作為實際參數傳遞給它,則這個Promise
對象可以稱為已決定但仍未落實。在執行器中有任何錯誤丟擲,都會導致這個promise成為已拒絕狀態,而返回值會被忽略。
Promise
類提供四個靜態方法來實施非同步任務並行,其中的Promise.allSettled()
方法,接受有多個promise的可迭代對象作為輸入,當所有輸入的promise都已落實之時,返回一個單一的已履行的Promise
對象,它決定出描述每個輸入promise的結果的一個對象陣列;其中每個對象都有status
屬性,它是字串"fulfilled"
或者"rejected"
;還有在status
為"fulfilled"
時出現的屬性value
,或者在status
為"rejected"
時出現的屬性reason
。
在下面例子中,全域fetch()
方法,啟動從網路上取回一個資源的過程,並返回一旦回應可獲得就履行的一個promise;這個promise決定出表示對這個請求回應的一個Response
對象,它只在遇到網路錯誤之時拒絕。Response.text()
方法返回一個promise,它決定出這個回應的主體的一個文字表示。
const urls = [
'https://developer.mozilla.org/',
'https://javascript.info/promise-api',
'https://no-such-url'
];
const fetchPromises = urls.map((url) => fetch(url));
Promise.allSettled(fetchPromises)
.then((results) => {
results.forEach((result, num) => {
if (result.status == "fulfilled") {
if (result.value.ok) {
result.value.text()
.then((data) => {
console.log(`${urls[num]}: ${data.length} bytes`);
})
.catch((err) => {
console.error(err);
});
}
else {
console.log(`${urls[num]}: ${result.value.status}`);
}
}
else if (result.status == "rejected") {
console.error(`${urls[num]}: ${result.reason}`);
}
});
});
JavaScript是天然的單執行緒的,所以在給定時刻只有一個任務在執行,但控制可以在不同的promise之間轉移,使得多個promise表現為並行執行。在JavaScript中並列執行只能通過分立的worker後台執行緒來完成[106]。
同基於promise的代碼合作的一種更簡單的方式,是使用async/await關鍵字。在函式開始處增加async
,使其成為非同步函式,非同步函式總是返回一個promise。在非同步函式中,可以在對返回一個promise的函式的呼叫之前,使用await
關鍵字;這使得代碼在這一點上等待直到這個promise已落實下來,然後在這一點上promise的已履行的值被當作返回值,而已拒絕的理由被作為例外丟擲。可以使用try...catch
塊進行例外處理,就如同這個代碼是同步的一樣。
Remove ads
唯讀視圖
在某些程式語言(如Oz、E和AmbientTalk)中,可以獲得推遲值的「唯讀視圖」,允許在解決(resolve)出這個值之後通過它來讀取,但不允許通過它來解決這個值:
- 在Oz語言中,
!!
運算子用於獲得唯讀視圖。 - 在E語言和AmbientTalk中,推遲值由一對稱為「promise/resolver對」的值表示。promise表示唯讀視圖,需要resolver來設定推遲值。
- 在C++11中,
std::future
提供了一個唯讀視圖。該值通過使用std::promise
直接設定,或使用std::packaged_task
或std::async
設定為函式呼叫的結果。 - 在Dojo Toolkit的1.5版本的Deferred API中,「僅限consumer的promise對象」表示唯讀視圖。[107]
- 在Alice ML中,future提供「唯讀視圖」[27],而promise包含future和解決future的能力[108]。
- 在.NET 4.0中,
System.Threading.Tasks.Task<T>
表示唯讀視圖。解決值可以通過System.Threading.Tasks.TaskCompletionSource<T>
來完成。
對唯讀視圖的支援符合最小特權原則,因為它使得設定值的能力僅限於需要設定該值的主體。在同樣支援管線的系統中,非同步訊息(具有結果)的傳送方接收結果的唯讀promise,訊息的目標接收resolver。
Remove ads
有關結構
「future」是事件同步原語的特例,它只能完成一次。通常,事件可以重設為初始的空狀態,因此可以根據需要多次完成。[109]
「I-var」是具有下面定義的阻塞語意的future。它起源於Id語言中包含I-var的「I-structure」資料結構。可以使用不同值多次設定的有關同步構造稱為「M-var」。M-var支援take
(採取)或put
(放置)當前值的原子性操作,這裡採取這個值還將M-var設定回其初始的「空」狀態。[110]
「並行邏輯變數」與future類似,但是通過合一更新,與邏輯編程中的「邏輯變數」相同。因此,它可以多次繫結到可合一的值,但不能設定回到空或未解決狀態。Oz的資料流變數充當並行邏輯變數,並且還具有上面提到的阻塞語意。
「並行約束變數」是並行邏輯變數的一般化,以支援約束邏輯編程:約束可以多次「縮小」,表示可能值的較小集合。通常,有一種方法可以指定每當約束進一步縮小時應該執行的thunk;這是支援「約束傳播」所必需的。
Remove ads
隱式與顯式future
對future的使用可以是「隱式」的,任何對future的使用都會自動獲得它的值,它就像是普通的參照一樣;也可以是「顯式」的,使用者必須呼叫函式來取得值,例如Java中的Future[32]或CompletableFuture[33]的get
方法。獲得一個顯式的future的值可以稱為「刺激」(stinging)或「強迫」(forcing)。顯式future可以作為庫來實現,而隱式future則通常作為語言的一部分來實現。
最初的Baker和Hewitt論文描述了隱式future,它們在演員模型和純物件導向程式設計語言(如Smalltalk)中自然得到支援。Friedman和Wise的論文只描述了顯式的future,可能反映了在老舊硬體上有效實施隱式future的困難。難點在於老舊硬體不能處理原始資料類型(如整數)的future。例如,add指令不會處理3 + future factorial(100000)
。在純演員模型或物件導向語言中,這個問題可以通過向future factorial(100000)
傳送訊息+[3]
來解決,它要求future自己加3
並返回結果。請注意,無論factorial(100000)
何時完成計算,訊息傳遞方法都可以工作,而且不需要任何「刺激」或「強迫」。
阻塞與非阻塞語意
如果future的值是非同步訪問的,例如通過向它傳送訊息,或者通過使用類似於E語言中的when
的構造顯式地等待它,那麼在訊息可以被接收或等待完成之前,推遲直到future得到解決(resolve)是沒有任何困難的。這是在純非同步系統(如純演員語言)中唯一需要考慮的情況。
然而,在某些系統中,還可能嘗試「立即」或「同步」訪問future的值。這樣的話就需要做出一個設計選擇:
- 訪問可能會阻塞當前執行緒或行程,直到future得到解決(可以具有逾時)。這是Oz語言中「資料流變數」的語意。
- 嘗試的同步訪問總是會引發訊號指示錯誤,例如丟擲異常。這是E語言中遠端promise的語意。[111]
- 潛在的,如果future已經解決,則訪問可能成功,但如果未解決,則發出訊號指示錯誤。這樣做的缺點是引入了不確定性和潛在的競爭條件,這似乎是一種不常見的設計選擇。
作為第一種可能性的範例,在C++11中 ,需要future值的執行緒可以通過呼叫wait()
或get()
成員函式來阻塞,直到它可獲得為止。還可以使用wait_for()
或wait_until()
成員函式指定等待逾時,以避免無限期阻塞。如果future對std::async
的呼叫,那麼阻塞等待(沒有逾時)可能導致函式的同步呼叫以計算等待執行緒上的結果。
promise管線
在分散式系統中使用推遲值可以顯著地減少傳輸延遲。例如,指稱推遲值的promise,成就了「promise管線」[112][113],就像在E語言和Joule語言中實現的那樣,它在Argus語言中稱為「call-stream」[5]。
考慮一個涉及常規遠端程序呼叫的表達式,例如:
t3 := (x.a()).c(y.b())
可以展開為
t1 := x.a(); t2 := y.b(); t3 := t1.c(t2);
每個語句需要傳送一條訊息,並在下一個語句可以繼續之前收到一個答覆。例如,假設x
、y
、t1
和t2
都位於同一台遠端機器上。在這種情況下,在開始執行第三條語句之前,必須對該機器進行兩次完整的網路往返。然後,第三條語句將引起另一個到同一個遠端機器的往返。
上面的表達式可以使用E語言的語法寫為:
t3 := (x <- a()) <- c(y <- b())
其中x <- a()
表示將訊息a()
非同步傳送給x
。它可以展開為:
t1 := x <- a(); t2 := y <- b(); t3 := t1 <- c(t2);
所有三個變數都會立即為其結果分配promise,執行過程將繼續進行到後面的語句。之後嘗試解決t3
的值可能會導致傳輸延遲;但是,管線操作可以減少所需的往返次數。如果與前面的範例一樣,x
、y
、t1
和t2
都位於相同的遠端機器上,則管線實現可以用一次往返來計算t3
,不必用三次。由於所有三條訊息都指向同一遠端電腦上的對象,因此只需要傳送一個請求,只需要接收一個包含結果的回應。另請注意,即使t1
和t2
位於不同機器上,或者位於與x
或y
不同的機器上,傳送t1 <- c(t2)
也不會阻塞。
promise管線應與並列非同步訊息傳遞區分開來。在支援並列訊息傳遞但不支援管線操作的系統中,上面範例中的訊息傳送x <- a()
和y <- b()
可以並列進行,但傳送t1 <- c(t2)
將不得不等到t1
和t2
都被接收,即使x
、y
、t1
和t2
在同一個遠端機器上。在涉及許多訊息的更複雜情況下,管線的相對傳輸延遲優勢變得更大。
promise管線操作也不應與演員系統中的管線式訊息處理相混淆,在這種系統中,演員可以在完成當前訊息的處理之前,指定並開始執行下一個訊息的行為。
有特定執行緒的future
某些語言比如Alice ML,定義的future可以關聯著計算這個future值的特定執行緒[27]。這種計算可以通過spawn exp
,在建立future時及早地開始;或者通過lazy exp
,在首次需要其值時懶惰地開始。在延遲計算的意義上,懶惰的future類似於thunk 。
Alice ML還支援可由任何執行緒解決的future,並稱謂它們為「promised future」[108]。這裡的promise是給future的顯式把柄(handle),它通過多型庫函式Promise.promise
來建立,所有promise都關聯的一個future,建立一個新promise也就建立了一個新鮮的future;這個future通過Promise.future
來提取,並且通過顯式的應用Promise.fulfill
於對應的promise來消除,即在全域中將這個future替代為一個值。Alice ML不支援promise管線,轉而對於future,包括關聯著promise的future,管線是自然而然地發生的。
在沒有特定執行緒的future(如Alice ML所提供的)中,通過在建立這個future的同時建立一個計算這個值的執行緒,可以直接實現及早求值的有特定執行緒的future。在這種情況下,最好將唯讀視圖返回給客戶,以便僅讓新建立的執行緒能夠解決這個future。
要在沒有特定執行緒的future中,實現隱式惰性的有特定執行緒的future,需要一種機制來確定何時首次需要future的值(例如,Oz中的WaitNeeded
構造[114] )。
如果所有值都是對象,那麼有實現透明轉發對象的能力就足夠了,因為傳送給轉發器的首條訊息表明需要future的值。
假定系統支援訊息傳遞,在有特定執行緒的future中,通過讓解決執行緒向future自己的執行緒傳送訊息,可以實現沒有特定執行緒的future。然而這可能被視為不必要的複雜性。在基於執行緒的程式語言中,最具表現力的方法似乎是提供一種混合:沒有特定執行緒的future、唯讀視圖、以及要麼有WaitNeeded
構造要麼支援透明轉發。
傳future呼叫
就求值策略而言,「傳future呼叫」是非確定性的:future的值將在建立future和使用其值之間的某個時間進行求值,但確切的時間不確定的,一次執行和另一次執行的求值時間會不一樣。計算可以在建立future時開始(及早求值),或者僅在實際需要值時開始(懶惰求值),並且可以在中途暫停,或在一次執行中執行。一旦future被賦值,它就不會在訪問future的時候重新計算;這就像傳需求呼叫時使用的記憶化。
「懶惰」future是確定性的具有惰性求值語意的future:future值的計算在首次需要時開始,與傳需要呼叫一樣。懶惰future使用在求值策略預設不是懶惰求值的語言中。例如,在C++11中,可以通過將std::launch::deferred
啟動策略傳遞給std::async
以及計算值的函式來建立這種惰性future。
演員模型中的future語意
在演員模型中,形式為future <Expression>
的表達式,以它對具有環境E和客戶C的Eval
訊息的回應方式來定義:future表達式通過向客戶C傳送新建立的演員F(計算<Expression>
的回應的代理)作為返回值來回應Eval
訊息,與之並行的向<Expression>
傳送具有環境E和客戶F的Eval
訊息。F的預設行為如下:
- 當F收到請求R時,它會檢查是否已經收到來自求值
<Expression>
的回應(可以是返回值或丟擲異常),處理過程如下所示:- 如果它已經有了回應V,那麼
- 如果V是返回值,那麼向它傳送請求R。
- 如果V是一個異常,那麼就把這個異常拋給請求R的客戶。
- 如果它還沒有回應,那麼將R儲存在F內的請求佇列中。
- 如果它已經有了回應V,那麼
- 當F接收到來自求值
<Expression>
的回應V時,那麼將V儲存在F中,並且- 如果V是返回值,那麼將所有排隊的請求傳送到V。
- 如果V是一個異常,那麼就會把這個異常丟擲給每個排隊請求的客戶。
但是,一些future可以通過特殊方式處理請求以提供更大的並列性。例如,表達式1 + future factorial(n)
可以建立一個新的future,其行為類似於數字1+factorial(n)
。這個技巧並不總是有效。例如,以下條件表達式:
if m>future factorial(n) then print("bigger") else print("smaller")
會掛起,直到factorial(n)
這個future已回應詢問m
是否大於其自身的請求。
參見
參照
外部連結
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads