อ่าน 15 นาที
โครูทีน
โครูทีน (Coroutines) คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของ ซับรูทีน (Subroutines ) เพื่อ การทำงาน...
โครูทีน
โครูทีน (Coroutines)คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของซับรูทีน (Subroutines ) เพื่อ การทำงาน แบบมัลติทาสกิ้งร่วมกันโครูทีนเหมาะอย่างยิ่งสำหรับการนำไปใช้ในการสร้างส่วนประกอบของโปรแกรมที่คุ้นเคย เช่นงานร่วมมือ (Cooperative tasks) , ข้อยกเว้น (Exceptions ) , ลูปเหตุการณ์ (Event loops) , ตัววนซ้ำ (Iterators) , รายการอนันต์ (Infinite lists ) และท่อส่งข้อมูล (Pipes )
ฟังก์ชันเหล่านี้ได้รับการอธิบายว่าเป็น "ฟังก์ชันที่คุณสามารถหยุดการทำงานชั่วคราวได้" [ 1 ]
เมลวิน คอนเวย์บัญญัติศัพท์คำว่าcoroutineในปี พ.ศ. 2501 เมื่อเขานำไปใช้ในการสร้างโปรแกรมแอสเซมบลี [ 2 ] คำอธิบายเกี่ยวกับ coroutine ที่ตีพิมพ์ครั้งแรกปรากฏขึ้นในภายหลังในปี พ.ศ. 2506 [ 3 ]
คำจำกัดความและประเภท
ไม่มีคำจำกัดความที่แน่นอนเพียงคำเดียวสำหรับโครูทีน ในปี พ.ศ. 2523 คริสโตเฟอร์ ดี. มาร์ลิน[ 4 ]ได้สรุปคุณลักษณะพื้นฐานสองประการของโครูทีนที่ได้รับการยอมรับอย่างกว้างขวางไว้ดังนี้:
- ค่าของข้อมูลเฉพาะที่อยู่ภายในโครูทีนจะคงอยู่ระหว่างการเรียกใช้งานครั้งต่อๆ ไป
- การทำงานของโครูทีนจะถูกระงับเมื่อการควบคุมออกจากโครูทีนนั้น และจะดำเนินการต่อจากจุดที่หยุดไว้เมื่อการควบคุมกลับเข้าสู่โครูทีนอีกครั้งในภายหลัง
นอกจากนั้น การใช้งานโครูทีนยังมีคุณสมบัติ 3 ประการดังนี้:
- กลไกการถ่ายโอนการควบคุม โดยปกติแล้ว โครูทีนแบบไม่สมมาตรจะมีคีย์เวิร์ด เช่น ` yield`
yieldและresume`yield` โปรแกรมเมอร์ไม่สามารถเลือกเฟรมที่จะส่งการทำงานไปได้อย่างอิสระ รันไทม์จะส่งการทำงานไปยังผู้เรียกที่ใกล้ที่สุดของโครูทีนปัจจุบันเท่านั้น ในทางกลับกัน ในโครูทีนแบบสมมาตรโปรแกรมเมอร์ต้องระบุปลายทางของการส่งการทำงาน - ไม่ว่าโครูทีนจะถูกจัดเตรียมไว้ในภาษาในฐานะอ็อบเจ็กต์ระดับเฟิร์สคลาสซึ่งโปรแกรมเมอร์สามารถจัดการได้อย่างอิสระ หรือในฐานะโครงสร้างที่มีข้อจำกัด
- โครูทีนสามารถระงับการทำงานภายในฟังก์ชันที่ซ้อนกันได้หรือไม่ โครูทีนแบบนั้นเรียกว่า โครูทีนแบบ มีสแต็ก (stackful coroutine ) ส่วน โครูทีนแบบไม่มีสแต็ก (stackless coroutine ) นั้น หากไม่ได้ระบุว่าเป็นโครูทีน ฟังก์ชันปกติจะไม่สามารถใช้คีย์เวิร์ด `coroutine`
yieldได้
บทความ "Revisiting Coroutines" [ 5 ]ที่ตีพิมพ์ในปี 2009 เสนอคำว่าfull coroutineเพื่อหมายถึง coroutine ที่รองรับ first-class coroutine และมี stackful Full Coroutines สมควรได้รับชื่อเฉพาะของตนเอง เนื่องจากมีพลังในการแสดงออก เช่นเดียว กับ one-shot continuationsและ delimited continuations Full coroutines อาจเป็นแบบสมมาตรหรือแบบไม่สมมาตร ที่สำคัญคือ ไม่ว่า coroutine จะเป็นแบบสมมาตรหรือไม่สมมาตรนั้นไม่มีผลต่อความสามารถในการแสดงออก ถึงแม้ว่า full coroutines จะแสดงออกได้มากกว่า non-full coroutines ก็ตาม ในขณะที่พลังในการแสดงออกนั้นเท่ากัน coroutines แบบไม่สมมาตรจะคล้ายกับโครงสร้างควบคุมแบบ routine มากกว่าในแง่ที่ว่าการควบคุมจะถูกส่งกลับไปยัง invoker เสมอ ซึ่งโปรแกรมเมอร์อาจคุ้นเคยมากกว่า
การเปรียบเทียบ
ซับรูทีน
ซับรูทีนเป็นกรณีพิเศษของโครูทีน[ 6 ]เมื่อมีการเรียกใช้ซับรูทีน การทำงานจะเริ่มต้นที่จุดเริ่มต้น และเมื่อซับรูทีนสิ้นสุดลง การทำงานก็จะสิ้นสุดลงเช่นกัน อินสแตนซ์ของซับรูทีนจะส่งคืนค่าเพียงครั้งเดียว และไม่เก็บสถานะระหว่างการเรียกใช้ ในทางตรงกันข้าม โครูทีนสามารถออกจากระบบได้โดยการเรียกโครูทีนอื่น ซึ่งอาจกลับไปยังจุดที่ถูกเรียกใช้ในโครูทีนเดิมในภายหลัง จากมุมมองของโครูทีน มันไม่ได้ออกจากระบบ แต่เป็นการเรียกโครูทีนอื่น[ 6 ]ดังนั้น อินสแตนซ์ของโครูทีนจึงเก็บสถานะและเปลี่ยนแปลงระหว่างการเรียกใช้ สามารถมีอินสแตนซ์ของโครูทีนที่กำหนดได้หลายตัวพร้อมกัน ความแตกต่างระหว่างการเรียกโครูทีนอื่นโดยวิธีการ"ยอมจำนน"กับการเรียกรูทีนอื่น (ซึ่งจะกลับไปยังจุดเดิมเช่นกัน) คือ ความสัมพันธ์ระหว่างโครูทีนสองตัวที่ยอมจำนนต่อกันนั้นไม่ใช่ความสัมพันธ์แบบผู้เรียก-ผู้ถูกเรียก แต่เป็นแบบสมมาตร
ซับรูทีนใดๆ ก็สามารถแปลงเป็นโครูทีนที่ไม่เรียกyieldได้[ 7 ]
นี่คือตัวอย่างง่ายๆ ของประโยชน์ของโครูทีน สมมติว่าคุณมีความสัมพันธ์ระหว่างผู้บริโภคและผู้ผลิต โดยที่รูทีนหนึ่งสร้างรายการและเพิ่มลงในคิว และอีกรูทีนหนึ่งนำรายการออกจากคิวและนำไปใช้ ด้วยเหตุผลด้านประสิทธิภาพ คุณต้องการเพิ่มและลบรายการหลายรายการพร้อมกัน โค้ดอาจมีลักษณะดังนี้:
q := คิวใหม่ < รายการ >โครูทีนสร้าง ลูปในขณะที่คิวไม่เต็ม สร้างสิ่งของใหม่บางรายการ เพิ่มรายการลงใน q ผลผลิตเพื่อการบริโภค โครูทีนบริโภค ลูปในขณะที่ q ไม่ว่างเปล่า ลบรายการบางรายการออกจากคิว ใช้สิ่งของเหล่านั้น ผลผลิตเพื่อการผลิต โทรผลิต
จากนั้นคิวจะถูกเติมจนเต็มหรือว่างเปล่าก่อนที่จะส่งการควบคุมไปยังโครูทีนอื่นโดยใช้ คำสั่ง yieldการเรียกโครูทีนเพิ่มเติมจะเริ่มต้นทันทีหลังจากคำสั่ง yieldในลูปของโครูทีนภายนอก
แม้ว่าตัวอย่างนี้มักถูกใช้เป็นบทนำเกี่ยวกับการทำงานแบบมัลติเธรดแต่จริงๆ แล้วไม่จำเป็นต้องใช้สองเธรด: คำสั่ง yieldสามารถนำไปใช้ได้โดยการกระโดดจากรูทีนหนึ่งไปยังอีกรูทีนหนึ่งโดยตรง
ด้าย
โครูทีนมีความคล้ายคลึงกับเธรด มาก อย่างไรก็ตาม โครูทีนเป็นการทำงานแบบมัลติทาสก์แบบร่วมมือกันในขณะที่เธรดโดยทั่วไปเป็นการ ทำงาน แบบมัลติทาสก์แบบแทรกแซงโครูทีนช่วยให้เกิดความพร้อมกัน (concurrency)เพราะอนุญาตให้ทำงานต่างๆ ได้โดยไม่เรียงลำดับ หรือในลำดับที่เปลี่ยนแปลงได้ โดยไม่เปลี่ยนแปลงผลลัพธ์โดยรวม แต่ไม่ช่วยให้เกิดความขนาน (parallelism ) เพราะไม่ได้ทำงานหลายอย่างพร้อมกัน ข้อดีของโครูทีนเหนือเธรดคือ สามารถใช้ใน บริบท แบบเรียลไทม์ที่เข้มงวดได้ ( การสลับระหว่างโครูทีนไม่เกี่ยวข้องกับการเรียกใช้ระบบหรือ การเรียกใช้ แบบบล็อกใดๆ เลย) ไม่จำเป็นต้องใช้กลไกการซิงโครไนซ์ เช่นมิวเท็กซ์เซมาฟอร์ ฯลฯ เพื่อป้องกันส่วนที่สำคัญและไม่จำเป็นต้องได้รับการสนับสนุนจากระบบปฏิบัติการ
เป็นไปได้ที่จะใช้งานโครูทีนโดยใช้เธรดที่กำหนดเวลาไว้ล่วงหน้าในลักษณะที่โปร่งใสต่อโค้ดที่เรียกใช้ แต่ข้อดีบางประการ (โดยเฉพาะความเหมาะสมสำหรับการทำงานแบบเรียลไทม์ที่เข้มงวดและความคุ้มค่าในการสลับระหว่างโครูทีน) จะหายไป
เครื่องกำเนิดไฟฟ้า
ตัวสร้าง หรือที่รู้จักกันในชื่อเซมิโครูทีน[ 8 ]เป็นส่วนย่อยของโครูทีน โดยเฉพาะอย่างยิ่ง ในขณะที่ทั้งสองสามารถหยุดการทำงานได้หลายครั้ง ทำให้การดำเนินการหยุดชะงักและอนุญาตให้กลับเข้ามาใหม่ได้ที่จุดเข้าหลายจุด แต่ทั้งสองแตกต่างกันตรงที่โครูทีนสามารถควบคุมได้ว่าการดำเนินการจะดำเนินต่อไปที่ใดทันทีหลังจากหยุด ในขณะที่ตัวสร้างไม่สามารถทำได้ แต่จะส่งการควบคุมกลับไปยังผู้เรียกตัวสร้างแทน[ 9 ]กล่าวคือ เนื่องจากตัวสร้างส่วนใหญ่ใช้เพื่อลดความซับซ้อนในการเขียนตัววนซ้ำคำyieldสั่งในตัวสร้างจึงไม่ได้ระบุโครูทีนที่จะกระโดดไป แต่จะส่งค่ากลับไปยังรูทีนแม่แทน
อย่างไรก็ตาม ยังคงสามารถใช้งานโครูทีนบนพื้นฐานของกลไกการสร้างข้อมูลได้ โดยอาศัยรูทีนตัวจัดการระดับบนสุด (ซึ่ง โดยพื้นฐานแล้วก็คือ แทรมโพลีน ) ที่ส่งการควบคุมไปยังตัวสร้างข้อมูลย่อยที่ระบุโดยโทเค็นที่ส่งกลับมาจากตัวสร้างข้อมูลเหล่านั้นโดยตรง:
q := คิวใหม่ < รายการ >เครื่องกำเนิดไฟฟ้าสร้าง ลูปในขณะที่ q ยังไม่เต็ม สร้างสิ่งของใหม่บางรายการ เพิ่มรายการลงใน q ผลผลิตตัวสร้างใช้ ลูปในขณะที่ q ไม่ว่างเปล่า ลบรายการบางรายการออกจากคิว ใช้สิ่งของเหล่านั้น ผลผลิตตัวจัดการซับรูทีน d := new Map < Generator , Iterator > d[ผลิต] := เริ่มบริโภค d[บริโภค] := เริ่มผลิต ปัจจุบัน := ผลิต เรียกลูปปัจจุบัน ปัจจุบัน := ถัดไป d[ปัจจุบัน] เจ้าหน้าที่ รับสาย
การใช้งาน coroutines จำนวนมากสำหรับภาษาที่มีการสนับสนุน generator แต่ไม่มี coroutines ดั้งเดิม (เช่น Python [ 10 ]ก่อน 2.5) ใช้โมเดลนี้หรือโมเดลที่คล้ายกัน
การเรียกซ้ำซึ่งกันและกัน
การใช้โครูทีนสำหรับเครื่องสถานะหรือการทำงานพร้อมกันนั้นคล้ายกับการใช้การเรียกซ้ำร่วมกันด้วย การ เรียกแบบหาง (tail call ) เนื่องจากในทั้งสองกรณี การควบคุมจะเปลี่ยนไปยังรูทีนอื่นในชุดของรูทีน อย่างไรก็ตาม โครูทีนมีความยืดหยุ่นมากกว่าและโดยทั่วไปมีประสิทธิภาพมากกว่า เนื่องจากโครูทีนจะส่งคืนค่า (yield) แทนที่จะส่งกลับ (return) แล้วจึงดำเนินการต่อแทนที่จะเริ่มต้นใหม่ตั้งแต่ต้น โครูทีนจึงสามารถเก็บสถานะได้ ทั้งตัวแปร (เช่นเดียวกับใน closure) และจุดการทำงาน และการส่งคืนค่าไม่ได้จำกัดเฉพาะในตำแหน่งหางเท่านั้น รูทีนย่อยที่เรียกซ้ำร่วมกันจะต้องใช้ตัวแปรที่ใช้ร่วมกันหรือส่งสถานะเป็นพารามิเตอร์ นอกจากนี้ การเรียกซ้ำร่วมกันแต่ละครั้งของรูทีนย่อยต้องใช้เฟรมสแต็กใหม่ (เว้นแต่ จะมี การกำจัด tail call ) ในขณะที่การส่งการควบคุมระหว่างโครูทีนจะใช้บริบทที่มีอยู่และสามารถทำได้ง่ายๆ โดยการกระโดด (jump)
การใช้งานทั่วไป
โครูทีนมีประโยชน์ในการนำไปใช้งานดังต่อไปนี้:
- เครื่องสถานะภายในซับรูทีนเดียว ซึ่งสถานะจะถูกกำหนดโดยจุดเริ่มต้น/จุดสิ้นสุดปัจจุบันของกระบวนการ อาจทำให้โค้ดอ่านง่ายขึ้นเมื่อเทียบกับการใช้gotoและยังสามารถนำไปใช้ผ่านการเรียกซ้ำแบบร่วมกันด้วยการ เรียก แบบ tail calls ได้อีกด้วย
- โมเดล Actorสำหรับการทำงานพร้อมกัน ตัวอย่างเช่น ในวิดีโอเกมแต่ละ Actor มีขั้นตอนการทำงานของตนเอง (ซึ่งเป็นการแยกโค้ดออกจากกันในเชิงตรรกะ) แต่พวกเขายินดีที่จะมอบการควบคุมให้กับตัวจัดตารางเวลาส่วนกลาง ซึ่งจะดำเนินการตามลำดับ (นี่คือรูปแบบหนึ่งของการทำงานหลายอย่างพร้อมกันแบบร่วมมือ )
- ตัวสร้าง (Generators)มีประโยชน์สำหรับสตรีม โดยเฉพาะอย่างยิ่งสตรีมอินพุต/เอาต์พุต และสำหรับการท่องไปในโครงสร้างข้อมูลทั่วไป
- กระบวนการสื่อสารแบบลำดับที่แต่ละกระบวนการย่อยเป็นโครูทีน การรับ/ส่งข้อมูลผ่านช่องทางและการดำเนินการแบบบล็อกจะสร้างโครูทีนขึ้นมา และตัวกำหนดตารางเวลาจะปลดบล็อกโครูทีนเหล่านั้นเมื่อเสร็จสิ้น หรืออีกทางหนึ่ง แต่ละกระบวนการย่อยอาจเป็นกระบวนการหลักของกระบวนการถัดไปในไปป์ไลน์ข้อมูล (หรือกระบวนการก่อนหน้า ซึ่งในกรณีนี้ รูปแบบสามารถแสดงได้ในรูปของตัวสร้างแบบซ้อนกัน)
- การสื่อสารแบบย้อนกลับ มักใช้ในซอฟต์แวร์ทางคณิตศาสตร์ ซึ่งกระบวนการต่างๆ เช่น ตัวแก้สมการ ตัวประเมินปริพันธ์ ฯลฯ ต้องการให้กระบวนการใช้งานทำการคำนวณ เช่น การประเมินสมการหรือตัวอินทิกรัล
การสนับสนุนแบบเนทีฟ
โครูทีนมีต้นกำเนิดมาจาก วิธีการ ในภาษาแอสเซมบลีแต่ได้รับการสนับสนุนในภาษาโปรแกรมระดับสูง บาง ภาษา
- ไอคิโด
- แองเจิลสคริปต์
- นักบัลเล่ต์
- บีซีพีแอล
- Pascal (Borland Turbo Pascal 7.0 พร้อมโมดูล uThreads)
- เบต้า
- บลิส
- C++ (ตั้งแต่ C++20 เป็นต้นไป)
- C# (ตั้งแต่เวอร์ชัน 2.0)
- โบสถ์
- ชัค
- ซีแอลยู
- ดี
- ไดนามิก ซี
- เออร์ลัง
- เอฟ#
- ปัจจัย
- สคริปต์ GameMonkey
- GDScript (ภาษาสคริปต์ของ Godot)
- ฮัสเคลล์[ 11 ] [ 12 ]
- การประกอบระดับสูง[ 13 ]
- ไอคอน
- ไอโอ
- JavaScript (ตั้งแต่เวอร์ชัน 1.7 ได้รับการกำหนดมาตรฐานใน ECMAScript 6) [ 14 ] ECMAScript 2017 ยังรวมถึงการสนับสนุนawait ด้วย
- จูเลีย[ 15 ]
- Kotlin (ตั้งแต่เวอร์ชัน 1.1) [ 16 ]
- ลิมโบ้
- ลัว[ 17 ]
- ลูซิด
- μC++
- โมดูลา-2
- เนเมอร์เล
- Perl 5 (โดยใช้โมดูล Coro )
- PHP (ตั้งแต่เวอร์ชัน 5.5)
- พิโคลิสป์
- บทนำ
- Python (ตั้งแต่เวอร์ชัน 2.5 [ 18 ]พร้อมการสนับสนุนที่ดีขึ้นตั้งแต่เวอร์ชัน 3.3 และไวยากรณ์ที่ชัดเจนตั้งแต่เวอร์ชัน 3.5 [ 19 ] )
- แร็กเก็ต
- ราคุ[ 20 ]
- ทับทิม
- ซาเธอร์
- โครงการ
- ตัวเอง
- Simula 67 [ 21 ]
- การสนทนาเล็กๆ น้อยๆ
- กระรอก
- ไพธอนไร้สแต็ก
- ซูเปอร์คอลไลเดอร์[ 22 ]
- Tcl (ตั้งแต่เวอร์ชัน 8.6)
- urbiscript
Javaไม่มีฟังก์ชันหรือไลบรารีที่รองรับ coroutine โดยตรง แต่สามารถเรียกใช้ coroutine ของ Kotlin ได้kotlinx.coroutines(ถึงแม้ว่าวิธีนี้จะไม่เหมาะสมนักและจะต้องมี Java wrapper มาครอบ Kotlin เพิ่มเติม)
เนื่องจากสามารถใช้ continuations ในการสร้าง coroutines ได้ ดังนั้นภาษาโปรแกรมที่รองรับ continuations จึงสามารถรองรับ coroutines ได้ค่อนข้างง่ายเช่นกัน
การนำไปใช้
ณ ปี 2003 ภาษาโปรแกรมยอดนิยมหลายภาษา รวมถึงภาษา C และภาษาที่พัฒนาต่อยอดจากภาษา C นั้น ยังไม่มีการรองรับโครูทีนในตัวภาษาหรือในไลบรารีมาตรฐาน สาเหตุหลักมาจากข้อจำกัดของ การใช้งานซับรูทีน แบบใช้สแต็ก อย่างไรก็ตาม มีข้อยกเว้นคือไลบรารี Boost.Context ในภาษา C++ ซึ่ง เป็นส่วนหนึ่งของBoostที่รองรับการสลับบริบทบน ARM, MIPS, PowerPC, SPARC และ x86 บน POSIX, Mac OS X และ Windows และสามารถสร้างโครูทีนบน Boost.Context ได้
ในสถานการณ์ที่โครูทีนจะเป็นการใช้งานกลไกที่เหมาะสมที่สุด แต่ไม่สามารถใช้งานได้ วิธีแก้ปัญหาทั่วไปคือการใช้โคลเชอ ร์ ซึ่งเป็นซับรูทีนที่มีตัวแปรสถานะ ( ตัวแปรสแตติกมักเป็นแฟล็กบูลีน) เพื่อรักษาสถานะภายในระหว่างการเรียกใช้ และเพื่อถ่ายโอนการควบคุมไปยังจุดที่ถูกต้อง เงื่อนไขภายในโค้ดจะส่งผลให้มีการเรียกใช้เส้นทางโค้ดที่แตกต่างกันในการเรียกใช้ครั้งต่อๆ ไป โดยขึ้นอยู่กับค่าของตัวแปรสถานะ อีกวิธีแก้ปัญหาทั่วไปคือการสร้างเครื่องสถานะที่ชัดเจนในรูปแบบของคำสั่ง switch ที่ใหญ่และซับซ้อน หรือผ่าน คำสั่ง gotoโดยเฉพาะอย่างยิ่งgoto ที่คำนวณได้การใช้งานในลักษณะนี้ถือว่าเข้าใจและบำรุงรักษาได้ยาก และเป็นแรงจูงใจให้มีการสนับสนุนโครูทีน
เธรด และ ไฟเบอร์ ( ในระดับที่น้อยกว่า) เป็นทางเลือกแทนโครูทีนในสภาพแวดล้อมการเขียนโปรแกรมกระแสหลักในปัจจุบัน เธรดช่วยจัดการการทำงานร่วมกันแบบเรียลไทม์ ของโค้ดที่ทำงาน พร้อมกันเธรดมีให้ใช้งานอย่างแพร่หลายในสภาพแวดล้อมที่รองรับภาษา C (และได้รับการสนับสนุนในภาษาโปรแกรมสมัยใหม่อื่นๆ อีกมากมาย) เป็นที่คุ้นเคยสำหรับโปรแกรมเมอร์หลายคน และโดยทั่วไปแล้วมีการใช้งานที่ดี มีเอกสารประกอบที่ดี และได้รับการสนับสนุนที่ดี อย่างไรก็ตาม เนื่องจากเธรดแก้ปัญหาที่ใหญ่และซับซ้อน จึงมีฟังก์ชันการทำงานที่ทรงพลังและซับซ้อนมากมาย และมีเส้นโค้งการเรียนรู้ที่ค่อนข้างยาก ดังนั้น เมื่อต้องการเพียงแค่โครูทีน การใช้เธรดอาจเป็นการใช้เกินความจำเป็น
ความแตกต่างที่สำคัญอย่างหนึ่งระหว่างเธรดและโครูทีนคือ เธรดมักจะถูกจัดตารางการทำงานล่วงหน้า ในขณะที่โครูทีนจะไม่เป็นเช่นนั้น เนื่องจากเธรดสามารถถูกจัดตารางใหม่ได้ตลอดเวลาและสามารถทำงานพร้อมกันได้ โปรแกรมที่ใช้เธรดจึงต้องระมัดระวังเรื่องการล็อกในทางตรงกันข้าม เนื่องจากโครูทีนสามารถถูกจัดตารางใหม่ได้เฉพาะในจุดที่กำหนดในโปรแกรมเท่านั้นและไม่ทำงานพร้อมกัน โปรแกรมที่ใช้โครูทีนจึงมักหลีกเลี่ยงการล็อกได้อย่างสิ้นเชิง คุณสมบัตินี้ยังถูกกล่าวถึงว่าเป็นข้อดีของ การเขียนโปรแกรม แบบขับเคลื่อนด้วยเหตุการณ์หรือแบบอะซิ งโครนัสอีกด้วย
เนื่องจากไฟเบอร์ได้รับการจัดกำหนดการแบบร่วมมือกัน จึงเป็นฐานที่เหมาะสมสำหรับการใช้งานโครูทีนข้างต้น[ 23 ]อย่างไรก็ตาม การสนับสนุนระบบสำหรับไฟเบอร์มักจะขาดหายไปเมื่อเทียบกับการสนับสนุนสำหรับเธรด
ซี
ในการใช้งานโครูทีนทั่วไป จำเป็นต้องได้รับ สแต็กการเรียก ตัวที่สอง ซึ่งเป็นคุณสมบัติที่ ภาษา C ไม่รองรับโดยตรง วิธีที่เชื่อถือได้ (แม้ว่าจะขึ้นอยู่กับแพลตฟอร์ม) ในการทำเช่นนี้คือการใช้ แอสเซมบลีแบบอินไลน์จำนวนเล็กน้อยเพื่อจัดการตัวชี้สแต็กอย่างชัดเจนในระหว่างการสร้างโครูทีนครั้งแรก นี่คือแนวทางที่Tom Duff แนะนำ ในการอภิปรายเกี่ยวกับข้อดีข้อเสียเมื่อเทียบกับวิธีการที่ใช้โดยProtothreads [ 24 ] บนแพลตฟอร์มที่ให้ การเรียกใช้ระบบ POSIX sigaltstackสามารถรับสแต็กการเรียกตัวที่สองได้โดยการเรียกฟังก์ชัน springboard จากภายในตัวจัดการสัญญาณ[ 25 ] [ 26 ]เพื่อให้บรรลุเป้าหมายเดียวกันใน C แบบพกพา โดยมีค่าใช้จ่ายเป็นความซับซ้อนเพิ่มเติม ไลบรารี C ที่สอดคล้องกับPOSIXหรือSingle Unix Specification (SUSv3) มีรูทีนเช่นgetcontext, setcontext, makecontext และ swapcontextแต่ฟังก์ชันเหล่านี้ถูกประกาศว่าล้าสมัยใน POSIX 1.2008 [ 27 ]
เมื่อได้สแต็กการเรียกครั้งที่สองด้วยวิธีใดวิธีหนึ่งที่ระบุไว้ข้างต้น แล้ว ฟังก์ชัน setjmp และ longjmpในไลบรารีมาตรฐานของภาษา Cสามารถนำมาใช้ในการสลับระหว่างโครูทีนได้ ฟังก์ชันเหล่านี้จะบันทึกและกู้คืนตัวชี้สแต็ก ตัว นับโปรแกรม รีจิ สเตอร์ ที่ฟังก์ชัน เรียกบันทึกไว้และสถานะภายในอื่นๆ ตามที่ABI กำหนด ตาม ลำดับ เพื่อให้การกลับไปยังโครูทีนหลังจากที่ได้ yield ไปแล้ว จะกู้คืนสถานะทั้งหมดที่จะถูกกู้คืนเมื่อกลับจากการเรียกฟังก์ชัน การใช้งานแบบเรียบง่าย ซึ่งไม่ได้อาศัยฟังก์ชัน setjmp และ longjmp อาจให้ผลลัพธ์เดียวกันได้โดยใช้บล็อกแอสเซมบลีแบบอินไลน์ ขนาดเล็ก ซึ่งจะสลับเฉพาะตัวชี้สแต็กและตัวนับโปรแกรม และเขียนทับรีจิสเตอร์อื่นๆ ทั้งหมด วิธีนี้อาจเร็วกว่ามาก เนื่องจาก setjmp และ longjmp ต้องจัดเก็บรีจิสเตอร์ทั้งหมดที่อาจใช้งานอยู่ตาม ABI อย่างระมัดระวัง ในขณะที่วิธีการเขียนทับจะอนุญาตให้คอมไพเลอร์จัดเก็บ (โดยการเขียนลงสแต็ก) เฉพาะสิ่งที่รู้ว่าใช้งานอยู่จริงเท่านั้น
เนื่องจากขาดการสนับสนุนภาษาโดยตรง ผู้เขียนหลายคนจึงเขียนไลบรารีของตนเองสำหรับโครูทีนซึ่งซ่อนรายละเอียดข้างต้น ไลบรารี libtask ของ Russ Cox [ 28 ]เป็นตัวอย่างที่ดีของประเภทนี้ โดยใช้ฟังก์ชันบริบทหากมีให้โดยไลบรารี C ดั้งเดิม มิฉะนั้นจะมีการใช้งานของตัวเองสำหรับ ARM, PowerPC, Sparc และ x86 การใช้งานที่น่าสนใจอื่นๆ ได้แก่ libpcl [ 29 ] coro [ 30 ] lthread [ 31 ] libCoroutine [ 32 ] libconcurrency [ 33 ] libcoro [ 34 ] ribs2 [ 35 ] libdill [ 36 ] libaco [ 37 ]และ libco [ 26 ]
นอกเหนือจากแนวทางทั่วไปข้างต้นแล้ว ยังมีความพยายามหลายครั้งในการประมาณค่าโครูทีนในภาษา C ด้วยการผสมผสานระหว่างซับรูทีนและมาโครผลงานของSimon Tatham [ 38 ]ซึ่งอิงตามอุปกรณ์ของ Duffเป็นตัวอย่างที่โดดเด่นของแนวทางนี้ และเป็นพื้นฐานสำหรับProtothreadsและการใช้งานที่คล้ายกัน[ 39 ]นอกจากข้อโต้แย้งของ Duff แล้ว[ 24 ]ความคิดเห็นของ Tatham เองยังให้การประเมินที่ตรงไปตรงมาเกี่ยวกับข้อจำกัดของแนวทางนี้ว่า "เท่าที่ผมรู้ นี่คือการเขียนโค้ด C ที่แย่ที่สุดเท่าที่เคยเห็นในโค้ดการผลิตที่จริงจัง" [ 38 ]ข้อบกพร่องหลักของการประมาณค่านี้คือ การที่ไม่รักษาเฟรมสแต็กแยกต่างหากสำหรับแต่ละโครูทีน ทำให้ตัวแปรโลคอลไม่ได้รับการเก็บรักษาไว้ระหว่างการออกจากฟังก์ชัน ไม่สามารถมีรายการหลายรายการในฟังก์ชันได้ และสามารถออกจากฟังก์ชันได้จากรูทีนระดับบนสุดเท่านั้น[ 24 ]
ซี++
C++20ได้แนะนำ coroutine มาตรฐานในรูปแบบฟังก์ชันไร้สแต็กที่สามารถระงับการทำงานกลางคันและกลับมาทำงานต่อได้ในภายหลัง สถานะการระงับของ coroutine จะถูกเก็บไว้ในฮีป[ 40 ]การนำมาตรฐานนี้ไปใช้ยังคงดำเนินต่อไป โดยคอมไพเลอร์ G++ และ MSVC ในปัจจุบันรองรับ coroutine มาตรฐานอย่างเต็มที่ในเวอร์ชันล่าสุด[ 41 ]คลาสgeneratorสำหรับช่วงที่ประเมินแบบซิงโครนัสและแบบ lazy std::generatorถูกเพิ่มเข้ามาใน C++23 [ 42 ]คลาส task ที่เหมาะสมstd::execution::taskถูกนำเสนอในC++26 [ 43 ] โดยจะถูกเรียกใช้โดยใช้std::execution::sync_wait()ซึ่งจะส่งคืนstd::optional<std::tuple<Ts...>>ค่า[ 44 ]
นี่คือตัวอย่างของ coroutine ใน C++20 ที่ใช้std::execution::taskคลาส C++26
import std ;using std :: optional ; using std :: tuple ; using std :: execution :: task ;task < int > add ( int a , int b ) noexcept { co_return a + b ; }task < int > test () { int ret = co_await add ( 1 , 2 ); std :: println ( "Return {}" , ret ); co_return ret ; }int main ( int argc , char * argv []) { optional < tuple < int >> result = std :: execution :: sync_wait ( test ()); std :: println ( "Result: {}" , std :: get < 0 > ( result ). value_or ( std :: make_tuple ( -1 )));ส่งคืนค่า0 ; }ซี#
C# 2.0เพิ่มฟังก์ชันเซมิโครูทีน ( ตัวสร้าง ) ผ่านรูปแบบตัววนซ้ำและyieldคำหลัก[ 45 ] [ 46 ] C# 5.0รวมการสนับสนุนไวยากรณ์ await
โคลจูร์
Cloroutineเป็นไลบรารีภายนอกที่ให้การสนับสนุน coroutine แบบไร้ stack ในClojureโดยถูกนำมาใช้ในรูปแบบของ macro ซึ่งจะแบ่งบล็อกโค้ดแบบคงที่ตามการเรียกใช้ตัวแปรใดๆ และสร้าง coroutine ออกมาเป็นฟังก์ชันที่มีสถานะ
ดี
Dใช้ coroutines เป็นคลาสไลบรารีมาตรฐานcore.thread.Fiberสำหรับไฟเบอร์ [ 47 ]ตัวสร้าง ( std.concurrency.Generator) [ 48 ]ทำให้การเปิดเผยฟังก์ชันไฟเบอร์เป็นช่วงอินพุต ( std.range.interfaces.InputRange) [ 49 ] เป็นเรื่องง่าย ทำให้ไฟเบอร์ใด ๆก็ตามเข้ากันได้กับอัลกอริธึมช่วงที่มีอยู่
ไป
Goมีแนวคิดในตัวของ " goroutines " ซึ่งเป็น เธรดสีเขียวประเภทหนึ่งที่มีน้ำหนักเบา เป็นกระบวนการอิสระที่จัดการโดยรันไทม์ของ Go สามารถเริ่มต้น goroutine ใหม่ได้โดยใช้คีย์เวิร์ด "go" แต่ละ goroutine มีสแต็กขนาดแปรผันได้ซึ่งสามารถขยายได้ตามต้องการ โดยทั่วไป goroutine จะสื่อสารกันโดยใช้ช่องทางในตัวของ Go [ 50 ] [ 51 ] [ 52 ] [ 53 ]อย่างไรก็ตาม goroutine ไม่ใช่ coroutines (ตัวอย่างเช่น ข้อมูลภายในจะไม่คงอยู่ระหว่างการเรียกใช้ที่ต่อเนื่องกัน) [ 54 ]
ชวา
มีการใช้งาน coroutines หลายแบบในJava แม้จะมีข้อจำกัดที่กำหนดโดยนามธรรมของ Java แต่ JVM ก็ไม่ได้ตัดความเป็นไปได้นี้ออกไป [ 55 ]มีวิธีการทั่วไปสี่วิธีที่ใช้ แต่สองวิธีนั้นทำให้ความสามารถในการพกพาของไบต์โค้ดระหว่าง JVM ที่สอดคล้องกับมาตรฐานนั้น เสียหาย
- JVM ที่ได้รับการดัดแปลง สามารถสร้าง JVM ที่ได้รับการแก้ไขเพื่อรองรับ coroutines ได้อย่างเป็นธรรมชาติมากขึ้นJVM ของ Da Vinciได้มีการสร้างแพทช์ขึ้น[ 56 ]
- ไบต์โค้ดที่แก้ไขแล้ว ฟังก์ชันการทำงานของโครูทีนนั้นเป็นไปได้โดยการเขียนไบต์โค้ด Java ปกติใหม่ ไม่ว่าจะแบบเรียลไทม์หรือในระหว่างการคอมไพล์ ชุดเครื่องมือที่เกี่ยวข้อง ได้แก่Javaflow , Java CoroutinesและCoroutines
- กลไก JNI เฉพาะแพลตฟอร์ม กลไกเหล่านี้ใช้วิธีการ JNI ที่ถูกนำมาใช้ในระบบปฏิบัติการหรือไลบรารี C เพื่อให้ฟังก์ชันการทำงานแก่ JVM
- แนวคิดเรื่องเธรด ไลบรารีโครูทีนที่ใช้เธรดอาจมีขนาดใหญ่ แต่ประสิทธิภาพจะแตกต่างกันไปตามการใช้งานเธรดของ JVM
- เราสามารถเรียกใช้ coroutine ของ Kotlin จาก Java ได้ แต่เนื่องจาก Java ไม่สามารถ "
suspendบล็อก" ได้ เราจึงต้องใช้kotlinx.coroutines.runBlocking`@Resolve`kotlinx.coroutines.CoroutineScopeหรือkotlinx.coroutines.JobAPI อื่นๆ แทน หรือ (ซึ่งเป็นวิธีที่นิยมใช้มากที่สุด) ส่งคืนค่า `@java.util.concurrent.CompletableFutureResolve`
สคริปต์
ตั้งแต่ECMAScript 2015 เป็นต้นมา JavaScript รองรับgeneratorซึ่งเป็นกรณีพิเศษของ coroutine [ 57 ]
โคทลิน
Kotlinมีการใช้งาน coroutines เป็นส่วนหนึ่งของไลบรารีที่พัฒนาโดยผู้พัฒนาเอง
import kotlinx.coroutines.*fun main () = runBlocking { launch { delay ( 1000L ) print ( "สวัสดีจากภายใน coroutine!" ) }พิมพ์( "สวัสดีโลก!" ) }ลัว
Luaรองรับ coroutine แบบ stackful asymmetric ระดับเฟิร์สคลาสมาตั้งแต่เวอร์ชัน 5.0 (2003) [ 58 ] ในไลบรารีมาตรฐานcoroutine [ 59 ] [ 60 ]
โมดูลา-2
Modula-2ตามที่Wirth นิยามไว้ ใช้โครูทีนเป็นส่วนหนึ่งของไลบรารี SYSTEM มาตรฐาน
ฟังก์ชัน NEWPROCESS() จะเติมบริบทโดยรับบล็อกโค้ดและพื้นที่สำหรับสแต็กเป็นพารามิเตอร์ และฟังก์ชัน TRANSFER() จะถ่ายโอนการควบคุมไปยังโครูทีนโดยรับบริบทของโครูทีนเป็นพารามิเตอร์
โมโน
Mono Common Language Runtime รองรับ continuations [ 61 ]ซึ่งสามารถสร้าง coroutines ได้
.NET Framework
ในระหว่างการพัฒนา . NET Framework 2.0 ไมโครซอฟต์ได้ขยายการออกแบบของCommon Language Runtime (CLR) Hosting API เพื่อจัดการการกำหนดเวลาแบบไฟเบอร์โดยคำนึงถึงการใช้งานในโหมดไฟเบอร์สำหรับ SQL Server [ 62 ]ก่อนการวางจำหน่าย การสนับสนุนสำหรับ hook การสลับงานICLRTask::SwitchOutถูกลบออกเนื่องจากข้อจำกัดด้านเวลา[ 63 ]ด้วยเหตุนี้ การใช้ API ไฟเบอร์เพื่อสลับงานจึงไม่ใช่ตัวเลือกที่เหมาะสมใน .NET Framework ในปัจจุบัน
โอแคมล์
OCaml รองรับ coroutines ผ่านThreadโมดูล[ 64 ] coroutines เหล่านี้ให้การทำงานพร้อมกันโดยไม่ต้องใช้การทำงานแบบขนาน และจะถูกกำหนดเวลาล่วงหน้าบนเธรดระบบปฏิบัติการเดียว ตั้งแต่ OCaml 5.0 เป็นต้นมาเธรดสีเขียวก็มีให้ใช้งานเช่นกัน โดยจัดเตรียมโดยโมดูลต่างๆ
เพิร์ล
- โคโร
โครูทีนได้รับการนำไปใช้งานในแบ็กเอนด์Raku ทั้งหมด [ 65 ]
พีพี
- ไฟเบอร์เป็นส่วนประกอบพื้นฐานตั้งแต่ PHP 8.1
- แอมพีเอชพี
- ขนแกะเปิด
- Coroutineถูกนำมาใช้ในลักษณะที่คล้ายกับ ฟังก์ชัน ในภาษา PythonและGo โดยมี ตัวอย่างมากมายที่แสดงโค้ดที่แปลงแล้วซึ่งมีจำนวนบรรทัดและพฤติกรรมเหมือนกัน
ไพธอน
Pythonรองรับ coroutines โดยใช้asyncio.create_task()ฟังก์ชัน[ 66 ]
- Python 2.5 นำเสนอการสนับสนุนที่ดีขึ้นสำหรับฟังก์ชันการทำงานที่คล้ายกับ coroutine โดยอิงจาก generator ที่ได้รับการขยาย ( PEP 342 )
- Python 3.3 ปรับปรุงความสามารถนี้โดยรองรับการมอบหมายงานให้กับซับเจเนอเรเตอร์ ( PEP 380 )
- Python 3.4 นำเสนอเฟรมเวิร์ก I/O แบบอะซิงโครนัสที่ครอบคลุมตามมาตรฐานPEP 3156ซึ่งรวมถึงโครูทีนที่ใช้ประโยชน์จากการมอบหมายซับเจเนอเรเตอร์
- Python 3.5 นำเสนอการรองรับอย่างชัดเจนสำหรับ coroutines ด้วยไวยากรณ์ async/ await ( PEP 0492 )
- ตั้งแต่Python 3.7 เป็นต้นมา async/await ได้กลายเป็นคำสงวน[ 67 ]
- อีเวนต์เล็ต
- กรีนเล็ต
- เกเวนต์
- ไพธอนไร้สแต็ก
import asyncio import time from asyncio import Taskasync def main () -> None : task1 : Task [ str ] = asyncio . create_task ( say_after ( 1 , "hello" )) task2 : Task [ str ] = asyncio . create_task ( say_after ( 2 , "world" ))print ( f "เริ่มเวลา{ time.strftime ( ' % X ' ) } " )# รอจนกว่าทั้งสองงานจะเสร็จสมบูรณ์ (ควรใช้เวลาประมาณ 2 วินาที) await task1 await task2print ( f "เสร็จสิ้นเวลา{ time.strftime ( ' % X ' ) } " )แร็กเก็ต
Racketมีฟังก์ชัน continuation ในตัว พร้อมด้วยการใช้งาน coroutine อย่างง่ายๆ ที่มีอยู่ในแคตตาล็อกแพ็กเกจอย่างเป็นทางการการพัฒนาโดย S. De Gabrielle
ทับทิม
- Ruby 1.9 รองรับ coroutines โดยตรงซึ่งถูกนำไปใช้เป็นfibersซึ่งเป็น semi-coroutines [ 68 ]
- การนำไปปฏิบัติโดย Marc De Scheemaecker
- Rubyเวอร์ชัน 2.5 ขึ้นไปรองรับ coroutines โดยตรง ซึ่งถูกนำไปใช้ในรูปแบบของfibers
- ดำเนินการโดย โทมัส ดับเบิลยู แบรนสัน
โครงการ
เนื่องจากSchemeรองรับ continuations อย่างเต็มที่ การใช้งาน coroutines จึงแทบไม่ต้องทำอะไรมาก เพียงแค่ต้องมีคิวของ continuations เท่านั้น
การสนทนาเล็กๆ น้อยๆ
เนื่องจากใน สภาพแวดล้อม Smalltalk ส่วนใหญ่ สแต็กการประมวลผลถือเป็นส่วนสำคัญ ดังนั้นจึงสามารถใช้งานโครูทีนได้โดยไม่ต้องใช้ไลบรารีหรือการสนับสนุน VM เพิ่มเติม
ทีซีแอล
ตั้งแต่เวอร์ชัน 8.6 เป็นต้นมา Tcl รองรับ coroutines ในภาษาหลัก [ 69 ]
วาลา
Valaรองรับ coroutine โดยตรง coroutine ถูกออกแบบมาเพื่อใช้งานร่วมกับ GTK Main Loop แต่ก็สามารถใช้งานได้โดยลำพังหากระมัดระวังเพื่อให้แน่ใจว่า callback สุดท้ายจะไม่ถูกเรียกก่อนที่จะมีการ yield อย่างน้อยหนึ่งครั้ง
ภาษาแอสเซมบลี
ภาษาแอสเซมบลีที่ขึ้นอยู่กับเครื่องมักจะมีวิธีการโดยตรงสำหรับการเรียกใช้โครูทีน ตัวอย่างเช่น ในMACRO-11 ซึ่งเป็น ภาษาแอสเซมบลีของ มินิคอมพิวเตอร์ตระกูล PDP-11การสลับโครูทีนแบบ "คลาสสิก" จะทำได้โดยคำสั่ง "JSR PC,@(SP)+" ซึ่งจะกระโดดไปยังแอดเดรสที่ดึงออกมาจากสแต็กและผลักแอดเดรสคำสั่ง ปัจจุบัน ( เช่นแอดเดรสของ คำสั่ง ถัดไป ) ลงบนสแต็ก ใน VAXen (ในVAX MACRO ) คำสั่งที่เทียบเคียงได้คือ "JSB @(SP)+" แม้แต่ในMotorola 6809ก็ยังมีคำสั่ง "JSR [,S++]" โปรดสังเกต "++" เนื่องจากมีการดึงแอดเดรส 2 ไบต์ออกจากสแต็ก คำสั่งนี้ถูกใช้มากในโปรแกรมตรวจสอบ (มาตรฐาน) Assist 09
ดูเพิ่มเติม
- อะซิงโครนัส/อะไวต์
- Pipelineซึ่งเป็น coroutine ชนิดหนึ่งที่ใช้สำหรับการสื่อสารระหว่างโปรแกรม[ 70 ]
- Protothreadsคือการใช้งานเธรดแบบไร้สแต็กที่มีน้ำหนักเบา โดยใช้กลไกคล้ายโครูทีน
อ่านเพิ่มเติม
- Ana Lucia de Moura; Roberto Ierusalimschy (2004). "การทบทวน Coroutines". ACM Transactions on Programming Languages and Systems . 31 (2): 1– 31. CiteSeerX 10.1.1.58.4017 . doi : 10.1145/1462166.1462167 . S2CID 9918449 .
ลิงก์ภายนอก
- หนังสือแนะนำโครูทีนฉบับสมบูรณ์ที่เน้นภาษาซี โดยไซมอน ทาแธม
- หน้าเว็บ Softpanorama เกี่ยวกับโครูทีน – ประกอบด้วยลิงก์โครูทีนภาษาแอสเซมบลีจำนวนมาก
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ โครูทีน
โครูทีน (Coroutines) คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของ ซับรูทีน (Subroutines ) เพื่อ การทำงาน...
คำจำกัดความและประเภท
ไม่มีคำจำกัดความที่แน่นอนเพียงคำเดียวสำหรับโครูทีน ในปี พ.ศ. 2523 คริสโตเฟอร์ ดี. มาร์ลิน [ 4 ] ได้สรุปคุณลักษณะพื้นฐานสองประการของโครูทีนที่ได้รับการยอมรับอย่างกว้างขวางไว้ดังนี้:
ซับรูทีน
ซับรูทีนเป็นกรณีพิเศษของโครูทีน [ 6 ] เมื่อมีการเรียกใช้ซับรูทีน การทำงานจะเริ่มต้นที่จุดเริ่มต้น และเมื่อซับรูทีนสิ้นสุดลง การทำงานก็จะสิ้นสุดลงเช่นกัน อินสแตนซ์ของซับรูทีนจะส่งคืนค่าเพียงครั้งเดียว และไม่เก็บสถานะระหว่างการเรียกใช้ ในทางตรงกันข้าม...
ด้าย
โครูทีนมีความคล้ายคลึงกับ เธรด มาก อย่างไรก็ตาม โครูทีนเป็นการ ทำงานแบบมัลติทาสก์แบบร่วมมือกัน ในขณะที่เธรดโดยทั่วไปเป็นการ ทำงาน แบบมัลติทาสก์แบบแทรกแซง โครูทีนช่วยให้เกิด ความพร้อมกัน (concurrency) เพราะอนุญาตให้ทำงานต่างๆ ได้โดยไม่เรียงลำดับ...