อ่าน 14 นาที
อนาคตและคำสัญญา
ในวิทยาการ คอมพิวเตอร์ ฟิ วเจอร์ ส พรอมีส ดีเลย์ และดี เฟอร์ส เป็นโครงสร้างที่ใช้ในการ ซิงโครไนซ์ การทำงาน ของโปรแกรมใน ภาษาโปรแกรมแบบขนาน บาง ภาษา...
อนาคตและคำสัญญา
ในวิทยาการคอมพิวเตอร์ฟิวเจอร์ส พรอมีสดีเลย์และดีเฟอร์สเป็นโครงสร้างที่ใช้ในการซิงโครไนซ์การทำงานของโปรแกรมในภาษาโปรแกรมแบบขนาน บาง ภาษา แต่ละอย่างเป็นวัตถุที่ทำหน้าที่เป็นตัวแทนของผลลัพธ์ที่ไม่ทราบค่าในตอนแรก โดยปกติเป็นเพราะการคำนวณค่าของมันยังไม่เสร็จสมบูรณ์
คำว่า"สัญญา"ได้รับการเสนอในปี พ.ศ. 2519 โดยDaniel P. Friedmanและ David Wise [ 1 ] และ Peter Hibbard เรียกมันว่า" ในที่สุด " [ 2 ] แนวคิดที่ค่อนข้างคล้ายกันอย่าง " อนาคต"ได้รับการแนะนำในปี พ.ศ. 2520 ในบทความโดยHenry BakerและCarl Hewitt [ 3 ]
คำว่าfuture , promise , delayและdeferred มักถูกใช้สลับกันไปมา แม้ว่าการใช้งานระหว่าง futureและpromiseอาจมีความแตกต่างกันบ้างโดยเฉพาะอย่างยิ่ง เมื่อแยกความแตกต่างในการใช้งาน future คือ มุมมองตัวแทนแบบ อ่านอย่างเดียวของตัวแปร ในขณะที่ promise คือคอนเทนเนอร์ที่เขียนได้และ กำหนดค่าได้ เพียงครั้งเดียวซึ่งจะกำหนดค่าของ future ที่สำคัญคือ สามารถกำหนด future ได้โดยไม่ต้องระบุว่า promise ใดจะกำหนดค่า และ promise ที่แตกต่างกันอาจกำหนดค่าของ future ได้ แต่สามารถทำได้เพียงครั้งเดียวสำหรับ future นั้นๆ ในบางกรณี future และ promise จะถูกสร้างขึ้นพร้อมกันและเชื่อมโยงกัน: future คือค่า และ promise คือฟังก์ชันที่กำหนดค่า – โดยพื้นฐานแล้วคือค่าส่งคืน (future) ของฟังก์ชันแบบอะซิงโครนัส (promise) การกำหนดค่าของ future ยังเรียกว่าการแก้ไข (resolving ) , การทำให้สำเร็จ (fulfilling ) หรือการผูก (binding )
แอปพลิเคชัน
แนวคิด เรื่อง Futures และ Promises มีต้นกำเนิดมาจากการเขียนโปรแกรมเชิงฟังก์ชันและแนวคิดที่เกี่ยวข้อง (เช่นการเขียนโปรแกรมเชิงตรรกะ ) เพื่อแยกค่า (Future) ออกจากวิธีการคำนวณ (Promise) ทำให้การคำนวณมีความยืดหยุ่นมากขึ้น โดยเฉพาะอย่างยิ่งโดยการประมวลผลแบบขนาน ต่อมาได้มีการนำไปใช้ในการประมวลผลแบบกระจายเพื่อลดความหน่วงจากรอบการสื่อสาร และในเวลาต่อมาก็ได้รับความนิยมมากขึ้นโดยอนุญาตให้เขียนโปรแกรมแบบอะซิงโครนัสในรูปแบบโดยตรงแทนที่จะเป็นรูปแบบการส่งผ่านความต่อเนื่อง
โดยนัยเทียบกับโดยชัดแจ้ง
การใช้งานฟิวเจอร์อาจเป็นแบบโดยปริยาย (การใช้งานฟิวเจอร์ใดๆ จะได้รับค่าของมันโดยอัตโนมัติ เหมือนกับการอ้างอิง ทั่วไป ) หรือ แบบ โดยชัดแจ้ง (ผู้ใช้ต้องเรียกฟังก์ชันเพื่อรับค่า เช่นgetเมธอด `of` java.util.concurrent.FutureในJava ) การรับค่าของฟิวเจอร์แบบโดยชัดแจ้งเรียกว่าการบังคับ (stingingหรือforcing ) ฟิวเจอร์แบบโดยชัดแจ้งสามารถนำไปใช้ในรูปแบบของไลบรารีได้ ในขณะที่ฟิวเจอร์แบบโดยปริยายมักจะถูกนำไปใช้เป็นส่วนหนึ่งของภาษา
บทความต้นฉบับของ Baker และ Hewitt อธิบายถึงฟิวเจอร์แบบแฝง ซึ่งได้รับการสนับสนุนโดยธรรมชาติในโมเดลการคำนวณแบบแอคเตอร์และภาษาการเขียนโปรแกรมเชิงวัตถุ บริสุทธิ์ เช่น Smalltalkส่วนบทความของ Friedman และ Wise อธิบายเฉพาะฟิวเจอร์แบบชัดแจ้งเท่านั้น ซึ่งอาจสะท้อนถึงความยากลำบากในการใช้งานฟิวเจอร์แบบแฝงอย่างมีประสิทธิภาพบนฮาร์ดแวร์มาตรฐาน ความยากลำบากก็คือ ฮาร์ดแวร์มาตรฐานไม่สามารถจัดการกับฟิวเจอร์สำหรับชนิดข้อมูลพื้นฐาน เช่น จำนวนเต็มได้ ตัวอย่างเช่น คำสั่ง add ไม่รู้วิธีจัดการกับจำนวนเต็มในภาษาแอคเตอร์หรือภาษาเชิงวัตถุบริสุทธิ์ ปัญหานี้สามารถแก้ไขได้โดยการส่งข้อความซึ่งขอให้ฟิวเจอร์บวกกับตัวเองและส่งคืนผลลัพธ์ โปรดทราบว่า วิธี การส่งข้อความ นี้ ใช้งานได้ไม่ว่าการคำนวณจะเสร็จสิ้นเมื่อใด และไม่จำเป็นต้องใช้การบังคับหรือการกำหนดเงื่อนไขใดๆ 3 + future factorial(100000)future factorial(100000)+[3]3factorial(100000)
การส่งต่อคำมั่นสัญญา
การใช้ฟิวเจอร์สามารถลดความหน่วงในระบบกระจาย ได้อย่างมาก ตัวอย่างเช่น ฟิวเจอร์ช่วยให้สามารถ ทำไปป์ ไลน์ของคำสัญญาได้[ 4 ] [ 5 ]ดังที่ได้นำไปใช้ในภาษาEและJouleซึ่งเรียกอีกอย่างว่าสตรี มการเรียก[ 6 ]ในภาษาArgus
ลองพิจารณาตัวอย่างนิพจน์ที่เกี่ยวข้องกับการเรียกใช้ฟังก์ชันระยะไกล แบบดั้งเดิม เช่น:
t3 := ( xa() ).c( yb() )
ซึ่งสามารถขยายได้ถึง
t1 := xa(); t2 := yb(); t3 := t1.c(t2);
แต่ละคำสั่งจำเป็นต้องมีการส่งข้อความและรับข้อความตอบกลับก่อนที่คำสั่งถัดไปจะดำเนินการได้ สมมติว่า ตัวอย่างเช่นx, y, t1, และt2อยู่บนเครื่องระยะไกลเดียวกัน ในกรณีนี้ จะต้องมีการรับส่งข้อมูลผ่านเครือข่ายไปยังเครื่องนั้นสองรอบสมบูรณ์ก่อนที่คำสั่งที่สามจะเริ่มทำงาน จากนั้นคำสั่งที่สามจะทำให้เกิดการรับส่งข้อมูลผ่านเครือข่ายไปยังเครื่องระยะไกลเครื่องเดิมอีกครั้งหนึ่ง
หากใช้ฟิวเจอร์ส นิพจน์ข้างต้นสามารถเขียนได้ดังนี้
t3 := (x <- a()) <- c(y <- b())
ซึ่งสามารถขยายได้ถึง
t1 := x <- a(); t2 := y <- b(); t3 := t1 <- c(t2);
ไวยากรณ์ที่ใช้ในที่นี้คือไวยากรณ์ของภาษา E ซึ่งx <- a()หมายถึงการส่งข้อความa()แบบอะซิงโครนัสไปยังxตัวแปรทั้งสามตัวจะได้รับการกำหนดค่าฟิวเจอร์สำหรับผลลัพธ์ทันที และการดำเนินการจะดำเนินต่อไปยังคำสั่งถัดไป การพยายามหาค่าของ ในภายหลังt3อาจทำให้เกิดความล่าช้า อย่างไรก็ตาม การใช้ไปป์ไลน์สามารถลดจำนวนรอบการส่งข้อมูลที่จำเป็นได้ หากเช่นเดียวกับในตัวอย่างก่อนหน้านี้ , x, y, t1และt2อยู่บนเครื่องระยะไกลเดียวกัน การใช้งานแบบไปป์ไลน์สามารถคำนวณได้t3ด้วยการส่งข้อมูลเพียงรอบเดียวแทนที่จะเป็นสามรอบ เนื่องจากข้อความทั้งสามข้อความมีจุดหมายปลายทางไปยังวัตถุที่อยู่บนเครื่องระยะไกลเดียวกัน จึงจำเป็นต้องส่งคำขอเพียงครั้งเดียวและรับการตอบกลับเพียงครั้งเดียวที่มีผลลัพธ์ การส่งt1 <- c(t2)จะไม่บล็อกแม้ว่าt1และt2จะอยู่บนเครื่องที่แตกต่างกัน หรือกับxหรือyก็ตาม
ควรแยกแยะความแตกต่างระหว่างการส่งข้อความแบบไปป์ไลน์กับการส่งข้อความแบบอะซิงโครนัสแบบขนาน ในระบบที่รองรับการส่งข้อความแบบขนานแต่ไม่รองรับไปป์ไลน์ การส่งข้อความx <- a()ในy <- b()ตัวอย่างข้างต้นอาจดำเนินการแบบขนานได้ แต่การส่งข้อความt1 <- c(t2)จะต้องรอจนกว่าทั้งข้อความแรกt1และ ข้อความที่สอง t2จะได้รับการรับ แม้ว่าข้อความแรก ข้อความที่สองxและyข้อความt1ที่สามt2จะอยู่บนเครื่องระยะไกลเดียวกันก็ตาม ข้อได้เปรียบด้านความหน่วงแฝงสัมพัทธ์ของการส่งข้อความแบบไปป์ไลน์จะยิ่งมากขึ้นในสถานการณ์ที่ซับซ้อนกว่าซึ่งเกี่ยวข้องกับข้อความจำนวนมาก
ไม่ควรสับสนระหว่าง Promise pipelining กับการประมวลผลข้อความแบบ pipelinedในระบบ actor ซึ่ง actor สามารถระบุและเริ่มดำเนินการพฤติกรรมสำหรับข้อความถัดไปได้ก่อนที่จะประมวลผลข้อความปัจจุบันเสร็จสมบูรณ์
มุมมองแบบอ่านอย่างเดียว
ในบางภาษาโปรแกรม เช่นOz , EและAmbientTalkสามารถรับมุมมองแบบอ่านอย่างเดียวของฟิวเจอร์ได้ ซึ่งอนุญาตให้อ่านค่าของมันเมื่อได้รับการแก้ไขแล้ว แต่ไม่อนุญาตให้แก้ไขฟิวเจอร์นั้น
- ใน Oz
!!ตัวดำเนินการนี้ใช้เพื่อให้ได้มุมมองแบบอ่านอย่างเดียว - ในภาษา E และ AmbientTalk นั้น ฟิวเจอร์จะถูกแทนด้วยคู่ค่าที่เรียกว่าคู่คำสัญญา/ตัวแก้ไข (promise/resolver pair ) โดยคำสัญญาจะแทนมุมมองแบบอ่านอย่างเดียว และตัวแก้ไขจำเป็นสำหรับการกำหนดค่าให้กับฟิวเจอร์
- ในภาษา C++ (ตั้งแต่C++11 )
std::futureตัวแปร `a` ให้มุมมองแบบอ่านอย่างเดียว ค่าจะถูกกำหนดโดยตรงโดยใช้ตัวแปร `a`std::promiseหรือกำหนดเป็นผลลัพธ์จากการเรียกใช้ฟังก์ชันโดยใช้ `a`std::packaged_taskหรือ `std::asynca` - ใน API Deferred ของ Dojo Toolkitเวอร์ชัน 1.5 อ็อบเจ็กต์ Promise เฉพาะผู้บริโภคจะแสดงมุมมองแบบอ่านอย่างเดียว[ 7 ]
- ในAlice MLฟิวเจอร์จะให้มุมมองแบบอ่านอย่างเดียวในขณะที่คำสัญญาประกอบด้วยทั้งฟิวเจอร์และความสามารถในการแก้ไขฟิวเจอร์[ 8 ] [ 9 ]
- ใน.NET
System.Threading.Tasks.Task<T>นั้น แสดงถึงมุมมองแบบอ่านอย่างเดียว การหาค่าสามารถทำได้ผ่านทางSystem.Threading.Tasks.TaskCompletionSource<T>.
การสนับสนุนมุมมองแบบอ่านอย่างเดียวสอดคล้องกับหลักการสิทธิ์ขั้นต่ำสุดเนื่องจากช่วยให้สามารถจำกัดการกำหนดค่าไว้เฉพาะผู้ที่จำเป็นต้องกำหนดค่านั้นได้ ในระบบที่รองรับการประมวลผลแบบไปป์ไลน์ด้วย ผู้ส่งข้อความแบบอะซิงโครนัส (พร้อมผลลัพธ์) จะได้รับคำสัญญาแบบอ่านอย่างเดียวสำหรับผลลัพธ์ และผู้รับข้อความจะได้รับตัวแก้ไข
ฟิวเจอร์เฉพาะเธรด
บางภาษา เช่นAlice MLกำหนดฟิวเจอร์ที่เชื่อมโยงกับเธรดเฉพาะที่คำนวณค่าของฟิวเจอร์[ 9 ]การคำนวณนี้สามารถเริ่มต้นได้ ทันที เมื่อสร้างฟิวเจอร์ หรือเริ่มต้นอย่างเฉื่อยชาเมื่อต้องการค่าของฟิวเจอร์เป็นครั้งแรก ฟิวเจอร์แบบเฉื่อยชาคล้ายกับธังค์ในแง่ของการคำนวณที่ล่าช้า
Alice ML ยังรองรับฟิวเจอร์ที่สามารถแก้ไขได้โดยเธรดใดก็ได้ และเรียกสิ่งเหล่านี้ว่าสัญญา[ 8 ] การใช้สัญญา ในลักษณะนี้ แตกต่างจากการใช้ใน E ตามที่อธิบายไว้ข้างต้นใน Alice สัญญาไม่ใช่มุมมองแบบอ่านอย่างเดียว และไม่รองรับการส่งต่อสัญญา แต่การส่งต่อจะเกิดขึ้นเองตามธรรมชาติสำหรับฟิวเจอร์ รวมถึงฟิวเจอร์ที่เกี่ยวข้องกับสัญญาด้วย
ความหมายของการบล็อกและการไม่บล็อก
หากเข้าถึงค่าของอนาคตแบบอะซิงโครนัส เช่น โดยการส่งข้อความไปยังอนาคตนั้น หรือโดยการรออย่างชัดเจนโดยใช้โครงสร้างเช่นwhenในภาษา E ก็จะไม่มีปัญหาในการหน่วงเวลาจนกว่าอนาคตจะได้รับการแก้ไขก่อนที่จะรับข้อความหรือรอให้การรอเสร็จสมบูรณ์ นี่เป็นกรณีเดียวที่ต้องพิจารณาในระบบอะซิงโครนัสโดยสมบูรณ์ เช่น ภาษาแอคเตอร์บริสุทธิ์
อย่างไรก็ตาม ในบางระบบ อาจเป็นไปได้ที่จะพยายาม เข้าถึงค่าในอนาคต โดยทันทีหรือพร้อมกันในกรณีนี้จะต้องมีการตัดสินใจเลือกวิธีการออกแบบ:
- การเข้าถึงดัง กล่าวอาจบล็อกเธรดหรือกระบวนการปัจจุบันจนกว่าจะแก้ไขปัญหาในอนาคตได้ (อาจกำหนดเวลาหมดอายุ) นี่คือความหมายของตัวแปรการไหลของข้อมูลในภาษาOz
- การพยายามเข้าถึงแบบซิงโครนัสอาจส่งสัญญาณข้อผิดพลาดได้เสมอ เช่น การโยนข้อยกเว้นนี่คือความหมายของสัญญาระยะไกลใน E [ 10 ]
- โดยทั่วไป การเข้าถึงอาจสำเร็จหากอนาคตได้รับการแก้ไขแล้ว แต่จะส่งสัญญาณข้อผิดพลาดหากยังไม่ได้รับการแก้ไข วิธีนี้มีข้อเสียคือทำให้เกิดความไม่แน่นอนและอาจก่อให้เกิดปัญหาการแข่งขัน (race condition ) และดูเหมือนจะเป็นทางเลือกในการออกแบบที่ไม่ค่อยพบเห็น
ตัวอย่างของความเป็นไปได้แรก ในC++11เธรดที่ต้องการค่าของ future สามารถบล็อกจนกว่าค่าจะพร้อมใช้งานได้โดยการเรียก ใช้ฟังก์ชันสมาชิก wait()`wait` หรือget()`wait` นอกจากนี้ยังสามารถระบุค่าหมดเวลาในการรอได้โดยใช้ ฟังก์ชันสมาชิก `wait` wait_for()หรือ ` wait` wait_until()เพื่อหลีกเลี่ยงการบล็อกอย่างไม่มีกำหนด หาก future เกิดจากการเรียกใช้ ` std::asyncwait` การรอแบบบล็อก (โดยไม่มีค่าหมดเวลา) อาจทำให้เกิดการเรียกใช้ฟังก์ชันแบบซิงโครนัสเพื่อคำนวณผลลัพธ์บนเธรดที่รออยู่
โครงสร้างที่เกี่ยวข้อง
ฟิวเจอร์เป็นกรณีพิเศษของเหตุการณ์ การซิงโครไน ซ์ขั้นพื้นฐานซึ่งสามารถดำเนินการให้เสร็จสมบูรณ์ได้เพียงครั้งเดียว โดยทั่วไป เหตุการณ์สามารถรีเซ็ตเป็นสถานะว่างเปล่าเริ่มต้นได้ และด้วยเหตุนี้จึงสามารถดำเนินการให้เสร็จสมบูรณ์ได้หลายครั้งตามต้องการ[ 11 ]
I -var (เช่นเดียวกับ Idในภาษา) เป็นอนาคตที่มีความหมายเชิงบล็อกตามที่กำหนดไว้ข้างต้นI-structureเป็นโครงสร้างข้อมูลที่ประกอบด้วย I-var โครงสร้างการซิงโครไนซ์ที่เกี่ยวข้องซึ่งสามารถตั้งค่าได้หลายครั้งด้วยค่าที่แตกต่างกันเรียกว่าM-var M-var รองรับการดำเนินการอะตอมิกเพื่อรับหรือใส่ค่าปัจจุบัน โดยการรับค่าจะทำให้ M-var กลับไปเป็นสถานะว่างเปล่า เริ่มต้น [ 12 ]
ตัวแปรตรรกะแบบขนาน (Concurrent Logic Variable)คล้ายกับตัวแปรอนาคต (Future Variable) แต่จะได้รับการอัปเดตโดยการรวม (Unification ) ในลักษณะเดียวกับตัวแปรตรรกะในการเขียนโปรแกรมเชิงตรรกะดังนั้นจึงสามารถผูกกับค่าที่รวมได้มากกว่าหนึ่งครั้ง แต่ไม่สามารถตั้งค่ากลับไปเป็นสถานะว่างหรือสถานะที่ยังไม่ได้รับการแก้ไขได้ ตัวแปรการไหลของข้อมูล (Dataflow Variables) ของ Oz ทำหน้าที่เป็นตัวแปรตรรกะแบบขนาน และมีลักษณะการทำงานแบบบล็อก (Blocking Semantics) ดังที่กล่าวไว้ข้างต้น
ตัวแปรข้อจำกัดแบบขนาน (Concurrent Constraint Variable)เป็นการขยายแนวคิดของตัวแปรตรรกะแบบขนาน (Concurrent Logic Variables) เพื่อรองรับการเขียนโปรแกรมตรรกะแบบมีข้อจำกัด (Constraint Logic Programming) กล่าวคือ ข้อจำกัดอาจถูกจำกัดให้แคบลงได้หลายครั้ง ซึ่งบ่งชี้ถึงชุดค่าที่เป็นไปได้ที่เล็กลง โดยทั่วไปจะมีวิธีระบุ thunk ที่ควรทำงานทุกครั้งที่ข้อจำกัดถูกจำกัดให้แคบลงไปอีก ซึ่งจำเป็นต่อการรองรับการแพร่กระจายข้อจำกัด (Constraint Propagation )
ความสัมพันธ์ระหว่างการแสดงออกของรูปแบบต่างๆ ของอนาคต
ฟิวเจอร์แบบเฉพาะเธรดที่ต้องการความแม่นยำสูง สามารถนำไปใช้กับฟิวเจอร์ที่ไม่เฉพาะเธรดได้โดยตรง โดยการสร้างเธรดเพื่อคำนวณค่าไปพร้อมๆ กับการสร้างฟิวเจอร์ ในกรณีนี้ ควรส่งมุมมองแบบอ่านอย่างเดียวกลับไปยังไคลเอ็นต์ เพื่อให้เฉพาะเธรดที่สร้างขึ้นใหม่เท่านั้นที่สามารถแก้ไขฟิวเจอร์นี้ได้
เพื่อนำฟิวเจอร์เฉพาะเธรดแบบขี้เกียจโดยปริยายมาใช้ (เช่น ตามที่ Alice ML จัดให้) ในแง่ของฟิวเจอร์ที่ไม่เฉพาะเธรด จำเป็นต้องมีกลไกในการพิจารณาว่าเมื่อใดจึงจำเป็นต้องใช้ค่าของฟิวเจอร์เป็นครั้งแรก (ตัวอย่างเช่นWaitNeededโครงสร้างใน Oz [ 13 ] ) หากค่าทั้งหมดเป็นออบเจ็กต์ ความสามารถในการนำออบเจ็กต์การส่งต่อแบบโปร่งใสมาใช้ก็เพียงพอแล้ว เนื่องจากข้อความแรกที่ส่งไปยังผู้ส่งต่อจะบ่งชี้ว่าจำเป็นต้องใช้ค่าของฟิวเจอร์
ฟิวเจอร์ที่ไม่จำเพาะต่อเธรดสามารถนำไปใช้ในฟิวเจอร์ที่จำเพาะต่อเธรดได้ โดยสมมติว่าระบบรองรับการส่งข้อความ ด้วยการให้เธรดที่กำลังแก้ไขส่งข้อความไปยังเธรดของฟิวเจอร์นั้นเอง อย่างไรก็ตาม วิธีนี้อาจถูกมองว่าเป็นความซับซ้อนที่ไม่จำเป็น ในภาษาโปรแกรมที่ใช้เธรด วิธีการที่แสดงออกได้ดีที่สุดดูเหมือนจะเป็นการผสมผสานระหว่างฟิวเจอร์ที่ไม่จำเพาะต่อเธรด มุมมองแบบอ่านอย่างเดียว และ โครงสร้าง WaitNeededหรือการสนับสนุนการส่งต่อแบบโปร่งใส
กลยุทธ์การประเมินผล
กลยุทธ์การประเมินค่าของฟิวเจอร์ส ซึ่งอาจเรียกว่า " เรียกใช้โดยฟิวเจอร์ส" (call by future ) นั้นเป็นแบบไม่แน่นอน: ค่าของฟิวเจอร์สจะถูกประเมินในช่วงเวลาใดเวลาหนึ่งระหว่างเวลาที่ฟิวเจอร์สถูกสร้างขึ้นและเวลาที่ใช้ค่าของมัน แต่เวลาที่แน่นอนนั้นไม่ได้ถูกกำหนดไว้ล่วงหน้าและสามารถเปลี่ยนแปลงได้ในแต่ละครั้งที่ทำงาน การคำนวณสามารถเริ่มต้นได้ทันทีที่สร้างฟิวเจอร์ส ( การประเมินแบบกระตือรือร้น ) หรือเฉพาะเมื่อต้องการใช้ค่าจริง ๆ ( การประเมินแบบขี้เกียจ ) และอาจถูกระงับกลางคัน หรือดำเนินการเสร็จสิ้นในครั้งเดียว เมื่อกำหนดค่าให้กับฟิวเจอร์สแล้ว จะไม่คำนวณค่าใหม่เมื่อมีการเข้าถึงฟิวเจอร์สอีกครั้ง ซึ่งคล้ายกับการจดจำค่า (memoization ) ที่ใช้ใน"เรียกใช้โดยต้องการ" (call by need )
เอฟิวเจอร์แบบเลซี่ (Lazy Future)คือฟิวเจอร์ที่มีความหมายของการประเมินแบบเลซี่ (Lazy Evaluation) อย่างแน่นอน กล่าวคือ การคำนวณค่าของฟิวเจอร์จะเริ่มต้นเมื่อมีความต้องการใช้ค่านั้นเป็นครั้งแรก เช่นเดียวกับการเรียกใช้ตามความต้องการ (Call by Need) ฟิวเจอร์แบบเลซี่มีประโยชน์ในภาษาโปรแกรมที่กลยุทธ์การประเมินค่าเริ่มต้นไม่ใช่แบบเลซี่ ตัวอย่างเช่น ในC++11สามารถสร้างฟิวเจอร์แบบเลซี่ได้โดยการส่งstd::launch::deferredนโยบายการเริ่มต้น (launch policy) ไปยังstd::asyncเมธอด พร้อมกับฟังก์ชันที่จะคำนวณค่า
ความหมายของอนาคตในแบบจำลองนักแสดง
ในแบบจำลองแอคเตอร์ นิพจน์ในรูปแบบดังกล่าวfuture <Expression>ถูกกำหนดโดยวิธีการตอบสนองต่อEvalข้อความที่มีสภาพแวดล้อมEและลูกค้าCดังนี้: นิพจน์ในอนาคตจะตอบสนองต่อEvalข้อความโดยการส่ง แอคเตอร์ Fที่สร้างขึ้นใหม่(ตัวแทนสำหรับการตอบสนองของการประเมิน ) ไปยังลูกค้า C<Expression>เป็นค่าส่งคืนพร้อมกับการส่ง<Expression>ข้อความEvalที่มีสภาพแวดล้อมEและลูกค้าC ไปพร้อมกัน พฤติกรรมเริ่มต้นของFมีดังนี้:
- เมื่อFได้รับคำขอRแล้ว F จะตรวจสอบว่าได้รับคำตอบ (ซึ่งอาจเป็นค่าส่งคืนหรือข้อยกเว้นที่ถูกโยน) จากการประเมินแล้วหรือไม่
<Expression>โดยดำเนินการดังต่อไปนี้:- ถ้าหากมีคำตอบ V อยู่แล้ว ก็แสดงว่า
- ถ้าVเป็นค่าส่งคืน ระบบจะส่งคำขอR ไปยัง V
- ถ้าV เป็นข้อผิดพลาด ระบบจะส่งต่อ ข้อผิดพลาดนั้นไปยังลูกค้าที่ร้องขอR
- หากยังไม่มีการตอบกลับใดๆR จะถูกเก็บไว้ในคิวคำขอภายในF
- ถ้าหากมีคำตอบ V อยู่แล้ว ก็แสดงว่า
- เมื่อFได้รับการตอบสนองVจากการประเมินผล
<Expression>V จะถูกเก็บไว้ในFและ- ถ้าVเป็นค่าส่งคืน แสดงว่าคำขอทั้งหมดที่อยู่ในคิวจะถูกส่งไปยังV
- ถ้าVเป็นข้อยกเว้น ระบบจะส่งต่อข้อยกเว้นนั้นไปยังลูกค้าของคำขอแต่ละรายการที่อยู่ในคิว
อย่างไรก็ตาม ฟิวเจอร์บางแบบสามารถจัดการกับคำขอในรูปแบบพิเศษเพื่อให้เกิดความขนานกันมากขึ้น ตัวอย่างเช่น นิพจน์1 + future factorial(n)สามารถสร้างฟิวเจอร์ใหม่ที่จะทำงานเหมือนกับตัวเลขนั้นได้1+factorial(n)เทคนิคนี้ไม่ได้ผลเสมอไป ตัวอย่างเช่น นิพจน์เงื่อนไขต่อไปนี้:
if m>future factorial(n) then print("bigger") else print("smaller")
ระงับไว้จนกว่าอนาคตfactorial(n)จะตอบคำขอที่ถามว่าmมีค่ามากกว่าตัวมันเองหรือไม่
ประวัติศาสตร์
โครงสร้าง อนาคตและ/หรือคำสัญญาถูกนำมาใช้ครั้งแรกในภาษาโปรแกรม เช่นMultiLispและAct 1การใช้ตัวแปรตรรกะสำหรับการสื่อสารใน ภาษา โปรแกรมเชิงตรรกะแบบขนานนั้นคล้ายคลึงกับฟิวเจอร์มาก สิ่งเหล่านี้เริ่มต้นในProlog ด้วย FreezeและIC Prologและกลายเป็นองค์ประกอบพื้นฐานของการทำงานแบบขนานอย่างแท้จริงใน Relational Language, Concurrent Prolog , guarded Horn clauses (GHC), Parlog , Strand , Vulcan , Janus , Oz-Mozart , Flow JavaและAlice MLตัวแปรI-var ที่กำหนดค่าเพียงครั้งเดียว จาก ภาษา โปรแกรมการไหลของข้อมูลซึ่งมีต้นกำเนิดในId และรวมอยู่ใน Concurrent MLของ Reppy นั้นคล้ายกับตัวแปรตรรกะแบบขนานมาก
เทคนิคการส่งคำสัญญาแบบไปป์ไลน์ (โดยใช้ฟิวเจอร์เพื่อเอาชนะความล่าช้า) ถูกคิดค้นโดยBarbara LiskovและLiuba Shriraในปี 1988 [ 6 ]และโดยอิสระโดยMark S. Miller , Dean Tribble และ Rob Jellinghaus ในบริบทของProject Xanaduประมาณปี 1989 [ 14 ]
คำว่า"promise"นั้นบัญญัติโดย Liskov และ Shrira แม้ว่าพวกเขาจะเรียกกลไกการประมวลผลแบบไปป์ไลน์ว่า " call-stream"ซึ่งปัจจุบันแทบไม่ได้ใช้แล้ว
ทั้งการออกแบบที่อธิบายไว้ในเอกสารของ Liskov และ Shrira และการใช้งาน pipelining ของ promise ใน Xanadu มีข้อจำกัดที่ว่าค่าของ promise ไม่ใช่ค่าระดับเฟิร์สคลาส : อาร์กิวเมนต์หรือค่าที่ส่งคืนโดยการเรียกหรือการส่งไม่สามารถเป็น promise ได้โดยตรง (ดังนั้นตัวอย่างของ pipelining ของ promise ที่กล่าวถึงก่อนหน้านี้ ซึ่งใช้ promise สำหรับผลลัพธ์ของการส่งหนึ่งครั้งเป็นอาร์กิวเมนต์ของการส่งอีกครั้ง จะไม่สามารถแสดงออกมาโดยตรงในการออกแบบสตรีมการเรียกหรือในการใช้งาน Xanadu) ดูเหมือนว่า promise และสตรีมการเรียกจะไม่เคยถูกนำไปใช้ในเวอร์ชันสาธารณะใด ๆ ของ Argus [ 15 ]ซึ่งเป็นภาษาโปรแกรมที่ใช้ในเอกสารของ Liskov และ Shrira การพัฒนา Argus หยุดลงประมาณปี 1988 [ 16 ]การใช้งาน pipelining ของ promise ใน Xanadu เพิ่งเปิดให้สาธารณะใช้งานได้เมื่อมีการเผยแพร่ซอร์สโค้ดสำหรับ Udanax Gold [ 17 ]ในปี 1999 และไม่เคยมีการอธิบายในเอกสารที่เผยแพร่ใด ๆ[ 18 ]การใช้งานในภายหลังใน Joule และ E รองรับ promise และ resolver ระดับเฟิร์สคลาสอย่างสมบูรณ์
ภาษาแอคเตอร์ยุคแรกหลายภาษา รวมถึงซีรี่ส์ Act [ 19 ] [ 20 ]รองรับทั้งการส่งข้อความแบบขนานและการประมวลผลข้อความแบบไปป์ไลน์ แต่ไม่รองรับการไปป์ไลน์ของคำสัญญา (ถึงแม้ว่าในทางเทคนิคแล้วจะสามารถใช้งานคุณสมบัติสุดท้ายนี้ได้ในสองแบบแรก แต่ก็ไม่มีหลักฐานว่าภาษา Act ได้ทำเช่นนั้น)
หลังปี 2000 ความสนใจในสัญญาซื้อขายล่วงหน้าและคำสัญญาได้กลับมาเฟื่องฟูอีกครั้ง เนื่องจากมีการนำไปใช้ในการตอบสนองของส่วนติดต่อผู้ใช้ และในการพัฒนาเว็บไซต์อันเนื่องมาจากรูปแบบการส่งข้อความแบบขอและตอบปัจจุบันภาษาโปรแกรมหลักหลายภาษามีการรองรับ futures และ promises โดยเฉพาะอย่างยิ่งที่ได้รับความนิยมFutureTaskใน Java 5 (ประกาศในปี 2004) [ 21 ]และ โครงสร้าง async/awaitใน .NET 4.5 (ประกาศในปี 2010 วางจำหน่ายในปี 2012) [ 22 ] [ 23 ] ซึ่ง ได้รับแรงบันดาลใจอย่างมากจากเวิร์กโฟลว์แบบอะซิงโครนัสของ F# [ 24 ]ซึ่งมีมาตั้งแต่ปี 2007 [ 25 ]ต่อมาได้มีการนำไปใช้ในภาษาอื่นๆ เช่น Dart (2014) [ 26 ] Python (2015) [ 27 ] Hack (HHVM) และร่างของ ECMAScript 7 (JavaScript), Scala และ C++ (2011)
รายการการใช้งาน
ภาษาโปรแกรมบางภาษาสนับสนุนฟิวเจอร์ส พรอมิส ตัวแปรตรรกะแบบขนาน ตัวแปรการไหลของข้อมูล หรือ I-vars ไม่ว่าจะโดยการรองรับโดยตรงในภาษาหรือในไลบรารีมาตรฐาน
รายชื่อแนวคิดที่เกี่ยวข้องกับฟิวเจอร์สและสัญญาตามภาษาโปรแกรม
- ABCL/f [ 28 ]
- อลิซ เอ็มแอล
- AmbientTalk (รวมถึง resolvers ระดับสูงและ promises แบบอ่านอย่างเดียว)
- C++เริ่มต้นด้วยC++11ผ่านทาง
std::futureและstd::promise- C++ เชิงองค์ประกอบ
- คริสตัล
- Dart (พร้อมคลาส
Future/ [ 29 ]และคำหลักและ[ 26 ] )Completerawaitasync - Elmผ่านโมดูลงาน[ 30 ]
- Haskellผ่านไลบรารี
Control.Concurrent.Async(I-vars) และControl.Concurrent.MVar(M-vars) - รหัส (เฉพาะตัวแปร I และตัวแปร M เท่านั้น)
- ไอโอ[ 31 ]
- Javaผ่านทาง
java.util.concurrent.Futureหรือjava.util.concurrent.CompletableFuture - JavaScriptตามมาตรฐานECMAScript 2015 [ 32 ]และผ่านคีย์เวิร์ด
asyncและawaitตั้งแต่ ECMAScript 2017 [ 33 ] - ลูซิด (เฉพาะการไหลของข้อมูล)
- ลิสป์บางประเภท
- .NETผ่าน
System.Threading.Tasks.Task - Kotlinแม้ว่า
kotlin.native.concurrent.Futureจะมักใช้เฉพาะเมื่อเขียน Kotlin ที่ตั้งใจให้ทำงานแบบเนทีฟ[ 35 ] - นิม
- ออกซิเจน
- เวอร์ชัน Oz 3 [ 36 ]
- Python concurrent.futures [ 37 ]ตั้งแต่ 3.2 [ 38 ]ตามที่เสนอโดย PEP 3148 [ 39 ]และ Python 3.5 เพิ่ม
asyncและawait[ 40 ] - R (รับประกันการประเมินผลแบบเลซี่ แต่ยังคงทำงานแบบเธรดเดียว)
- แร็กเก็ต[ 41 ]
- ราคุ[ 42 ]
- Rust (อนาคตเป็น
std::future::Futureสัญญาที่บรรลุผลผ่าน.await) [ 43 ] - Scalaผ่านแพ็คเกจ scala.concurrent
- โครงการ
- บทสนทนาเล็กๆน้อย ๆ
- สาย
- Swift (ใช้งานได้เฉพาะผ่านไลบรารีของบุคคลที่สามเท่านั้น)
- Visual Basic 11 (ผ่านคีย์เวิร์ดAsyncและAwait ) [ 23 ]
ภาษาอื่นๆ ที่รองรับการส่งข้อมูลแบบ Promise Pipelining ได้แก่:
รายชื่อการใช้งานฟิวเจอร์สในรูปแบบไลบรารี
- สำหรับภาษา Common Lisp :
- สำหรับภาษา C++:
- สำหรับ ภาษา C#และ ภาษา .NET อื่นๆ : ไลบรารีParallel Extensions
- สำหรับGroovy : GPars [ 56 ]
- สำหรับJavaScript :
- Cujo.js [ 57 ] when.js [ 58 ]ให้คำสัญญาที่สอดคล้องกับข้อกำหนด Promises/A+ [ 59 ] 1.1
- ชุดเครื่องมือโดโจจัดหาคำสัญญา[ 60 ]และรูปแบบบิดเบี้ยวที่เลื่อน ออกไป
- MochiKit [ 61 ]ได้รับแรงบันดาลใจจากDeferreds ของ Twisted
- Deferred Object ของ jQueryนั้นอิงตามการออกแบบPromises/A ของ CommonJS
- AngularJS [ 62 ]
- node -promise [ 63 ]
- Q โดย Kris Kowal สอดคล้องกับ Promises/A+ 1.1 [ 64 ]
- RSVP.js สอดคล้องกับ Promises/A+ 1.1 [ 65 ]
- คลาส Promise ของ YUI [ 66 ] [ 67 ]สอดคล้องกับข้อกำหนด Promises/A+ 1.0
- Bluebird โดย Petka Antonov [ 68 ]
- แพ็ก เกจ PromiseของClosure Libraryเป็นไปตามข้อกำหนด Promises/A+
- ดู รายการ ของ Promise/A+สำหรับตัวอย่างการใช้งานเพิ่มเติมที่อิงตามการออกแบบ Promise/A+
- สำหรับภาษา Java :
- JDeferred ให้ API และพฤติกรรม deferred-promise ที่คล้ายกับอ็อบเจ็กต์jQuery .Deferred [ 69 ]
- ParSeq [ 70 ]ให้บริการ API สัญญางานที่เหมาะสำหรับการทำไปป์ไลน์แบบอะซิงโครนัสและการแยกสาขา ซึ่งดูแลโดยLinkedIn
- สำหรับภาษา Lua :
- โมดูล cqueues [1]ประกอบด้วย API Promise
- สำหรับObjective-C : MAFuture, [ 71 ] [ 72 ] RXPromise, [ 73 ] ObjC-CollapsingFutures, [ 74 ] PromiseKit, [ 75 ] objc-promise, [ 76 ] OAPromise, [ 77 ]
- สำหรับOCaml : โมดูล Lazy ใช้งานฟิวเจอร์แบบชัดเจนแบบ Lazy [ 78 ]
- สำหรับPerl : Future, [ 79 ] Promises, [ 80 ] Reflex, [ 81 ] Promise::ES6, [ 82 ]และ Promise::XS [ 83 ]
- สำหรับPHP : React/Promise [ 84 ]
- สำหรับPython :
- การใช้งานในตัว[ 85 ]
- pythonfutures [ 86 ]
- เลื่อนของ Twisted [ 87 ]
- สำหรับR :
- สำหรับRuby :
- สำหรับRust :
- ฟิวเจอร์ส-อาร์เอส[ 95 ]
- สำหรับScala :
- สำหรับSwift :
- เฟรมเวิร์กอะซิงโครนัส ใช้รูปแบบ C#
async/แบบไม่บล็อกawait[ 97 ] - FutureKit [ 98 ]ใช้งานเวอร์ชันสำหรับ Apple GCD [ 99 ]
- FutureLib ไลบรารี Swift 2 บริสุทธิ์ที่ใช้ฟิวเจอร์และสัญญาแบบ Scala พร้อมการยกเลิกแบบ TPL [ 100 ]
- Deferred ซึ่งเป็นไลบรารี Swift บริสุทธิ์ที่ได้รับแรงบันดาลใจจาก Deferred ของ OCaml [ 101 ]
- ไบรท์ฟิวเจอร์ส[ 102 ]
- SwiftCoroutine [ 103 ]
- เฟรมเวิร์กอะซิงโครนัส ใช้รูปแบบ C#
- สำหรับTcl : tcl-promise [ 104 ]
โครูทีน
อนาคตสามารถนำไปใช้ในโครูทีน[ 27 ]หรือตัวสร้าง [ 105 ] ซึ่งส่งผลให้กลยุทธ์การประเมินเหมือนกัน (เช่นมัลติทาสกิ้งแบบร่วมมือหรือการประเมินแบบขี้เกียจ)
ช่องต่างๆ
อนาคตสามารถนำไปใช้ในช่องทาง ได้อย่างง่ายดาย : อนาคตคือช่องทางที่มีองค์ประกอบเดียว และคำสัญญาคือกระบวนการที่ส่งไปยังช่องทางเพื่อเติมเต็มอนาคต[ 106 ] [ 107 ]ซึ่งทำให้สามารถนำอนาคตไปใช้ในภาษาการเขียนโปรแกรมแบบขนานที่รองรับช่องทางได้ เช่น CSP และGoอนาคตที่ได้นั้นมีความชัดเจน เนื่องจากต้องเข้าถึงโดยการอ่านจากช่องทาง แทนที่จะประเมินเพียงอย่างเดียว
ดูเพิ่มเติม
- อะซิงโครนัส/อะไวต์
- ไฟเบอร์ (วิทยาการคอมพิวเตอร์)
- ฟูเท็กซ์
- พีระมิดแห่งความหายนะ (การเขียนโปรแกรม)รูปแบบการออกแบบที่ไม่พึงประสงค์ซึ่งหลีกเลี่ยงได้ด้วยคำสัญญา
ลิงก์ภายนอก
- การนำเสนอรูปแบบการทำงานพร้อมกัน (Concurrency patterns)ในงาน ScaleConf
- การสร้าง มูลค่าในอนาคตและการวางแผนโครงการที่ คลังเก็บ แบบแผนพอร์ตแลนด์
- การใช้งานมัลติเธรดอย่างง่ายด้วย FuturesในPython
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ อนาคตและคำสัญญา
ในวิทยาการ คอมพิวเตอร์ ฟิ วเจอร์ ส พรอมีส ดีเลย์ และดี เฟอร์ส เป็นโครงสร้างที่ใช้ในการ ซิงโครไนซ์ การทำงาน ของโปรแกรมใน ภาษาโปรแกรมแบบขนาน บาง ภาษา...
แอปพลิเคชัน
แนวคิด เรื่อง Futures และ Promises มีต้นกำเนิดมาจาก การเขียนโปรแกรมเชิงฟังก์ชัน และแนวคิดที่เกี่ยวข้อง (เช่น การเขียนโปรแกรมเชิงตรรกะ ) เพื่อแยกค่า (Future) ออกจากวิธีการคำนวณ (Promise) ทำให้การคำนวณมีความยืดหยุ่นมากขึ้น โดยเฉพาะอย่างยิ่งโดยการประมวลผลแบบขนาน...
โดยนัยเทียบกับโดยชัดแจ้ง
การใช้งานฟิวเจอร์อาจเป็น แบบโดยปริยาย (การใช้งานฟิวเจอร์ใดๆ จะได้รับค่าของมันโดยอัตโนมัติ เหมือนกับ การอ้างอิง ทั่วไป ) หรือ แบบ โดยชัดแจ้ง (ผู้ใช้ต้องเรียกฟังก์ชันเพื่อรับค่า เช่น get เมธอด `of` java.util.concurrent.
การส่งต่อคำมั่นสัญญา
การใช้ฟิวเจอร์สามารถลด ความหน่วง ใน ระบบกระจาย ได้อย่างมาก ตัวอย่างเช่น ฟิวเจอร์ช่วยให้สามารถ ทำไปป์ ไลน์ ของคำสัญญาได้ [ 4 ] [ 5 ] ดังที่ได้นำไปใช้ในภาษา E และ Joule ซึ่งเรียกอีกอย่างว่าสตรี มการเรียก [ 6 ] ในภาษา Argus