กลับไปหน้าบทความ

อ่าน 15 นาที

โครูทีน

โครูทีน (Coroutines) คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของ ซับรูทีน (Subroutines ) เพื่อ การทำงาน...

โครูทีน

โครูทีน (Coroutines)คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของซับรูทีน (Subroutines ) เพื่อ การทำงาน แบบมัลติทาสกิ้งร่วมกันโครูทีนเหมาะอย่างยิ่งสำหรับการนำไปใช้ในการสร้างส่วนประกอบของโปรแกรมที่คุ้นเคย เช่นงานร่วมมือ (Cooperative tasks) , ข้อยกเว้น (Exceptions ) , ลูปเหตุการณ์ (Event loops) , ตัววนซ้ำ (Iterators) , รายการอนันต์ (Infinite lists ) และท่อส่งข้อมูล (Pipes )

ฟังก์ชันเหล่านี้ได้รับการอธิบายว่าเป็น "ฟังก์ชันที่คุณสามารถหยุดการทำงานชั่วคราวได้" [ 1 ]

เมลวิน คอนเวย์บัญญัติศัพท์คำว่าcoroutineในปี พ.ศ. 2501 เมื่อเขานำไปใช้ในการสร้างโปรแกรมแอสเซมบลี [ 2 ] คำอธิบายเกี่ยวกับ coroutine ที่ตีพิมพ์ครั้งแรกปรากฏขึ้นในภายหลังในปี พ.ศ. 2506 [ 3 ]

คำจำกัดความและประเภท

ไม่มีคำจำกัดความที่แน่นอนเพียงคำเดียวสำหรับโครูทีน ในปี พ.ศ. 2523 คริสโตเฟอร์ ดี. มาร์ลิน[ 4 ]ได้สรุปคุณลักษณะพื้นฐานสองประการของโครูทีนที่ได้รับการยอมรับอย่างกว้างขวางไว้ดังนี้:

  1. ค่าของข้อมูลเฉพาะที่อยู่ภายในโครูทีนจะคงอยู่ระหว่างการเรียกใช้งานครั้งต่อๆ ไป
  2. การทำงานของโครูทีนจะถูกระงับเมื่อการควบคุมออกจากโครูทีนนั้น และจะดำเนินการต่อจากจุดที่หยุดไว้เมื่อการควบคุมกลับเข้าสู่โครูทีนอีกครั้งในภายหลัง

นอกจากนั้น การใช้งานโครูทีนยังมีคุณสมบัติ 3 ประการดังนี้:

  1. กลไกการถ่ายโอนการควบคุม โดยปกติแล้ว โครูทีนแบบไม่สมมาตรจะมีคีย์เวิร์ด เช่น ` yield` yieldและresume`yield` โปรแกรมเมอร์ไม่สามารถเลือกเฟรมที่จะส่งการทำงานไปได้อย่างอิสระ รันไทม์จะส่งการทำงานไปยังผู้เรียกที่ใกล้ที่สุดของโครูทีนปัจจุบันเท่านั้น ในทางกลับกัน ในโครูทีนแบบสมมาตรโปรแกรมเมอร์ต้องระบุปลายทางของการส่งการทำงาน
  2. ไม่ว่าโครูทีนจะถูกจัดเตรียมไว้ในภาษาในฐานะอ็อบเจ็กต์ระดับเฟิร์สคลาสซึ่งโปรแกรมเมอร์สามารถจัดการได้อย่างอิสระ หรือในฐานะโครงสร้างที่มีข้อจำกัด
  3. โครูทีนสามารถระงับการทำงานภายในฟังก์ชันที่ซ้อนกันได้หรือไม่ โครูทีนแบบนั้นเรียกว่า โครูทีนแบบ มีสแต็ก (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)มีประโยชน์สำหรับสตรีม  โดยเฉพาะอย่างยิ่งสตรีมอินพุต/เอาต์พุต และสำหรับการท่องไปในโครงสร้างข้อมูลทั่วไป
  • กระบวนการสื่อสารแบบลำดับที่แต่ละกระบวนการย่อยเป็นโครูทีน การรับ/ส่งข้อมูลผ่านช่องทางและการดำเนินการแบบบล็อกจะสร้างโครูทีนขึ้นมา และตัวกำหนดตารางเวลาจะปลดบล็อกโครูทีนเหล่านั้นเมื่อเสร็จสิ้น หรืออีกทางหนึ่ง แต่ละกระบวนการย่อยอาจเป็นกระบวนการหลักของกระบวนการถัดไปในไปป์ไลน์ข้อมูล (หรือกระบวนการก่อนหน้า ซึ่งในกรณีนี้ รูปแบบสามารถแสดงได้ในรูปของตัวสร้างแบบซ้อนกัน)
  • การสื่อสารแบบย้อนกลับ มักใช้ในซอฟต์แวร์ทางคณิตศาสตร์ ซึ่งกระบวนการต่างๆ เช่น ตัวแก้สมการ ตัวประเมินปริพันธ์ ฯลฯ ต้องการให้กระบวนการใช้งานทำการคำนวณ เช่น การประเมินสมการหรือตัวอินทิกรัล

การสนับสนุนแบบเนทีฟ

โครูทีนมีต้นกำเนิดมาจาก วิธีการ ในภาษาแอสเซมบลีแต่ได้รับการสนับสนุนในภาษาโปรแกรมระดับสูง บาง ภาษา

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 เกี่ยวกับโครูทีน  – ประกอบด้วยลิงก์โครูทีนภาษาแอสเซมบลีจำนวนมาก
ดึงข้อมูลมาจาก " https://en.wikipedia.org/w/index.php?title=Coroutine&oldid=1359470092 "

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ โครูทีน

โครูทีน (Coroutines) คือ ส่วนประกอบ ของโปรแกรมคอมพิวเตอร์ ที่สามารถหยุดชั่วคราวและเริ่มทำงานต่อได้ ซึ่งเป็นการขยายแนวคิดของ ซับรูทีน (Subroutines ) เพื่อ การทำงาน...

คำจำกัดความและประเภท

ไม่มีคำจำกัดความที่แน่นอนเพียงคำเดียวสำหรับโครูทีน ในปี พ.ศ. 2523 คริสโตเฟอร์ ดี. มาร์ลิน [ 4 ] ได้สรุปคุณลักษณะพื้นฐานสองประการของโครูทีนที่ได้รับการยอมรับอย่างกว้างขวางไว้ดังนี้:

ซับรูทีน

ซับรูทีนเป็นกรณีพิเศษของโครูทีน [ 6 ] เมื่อมีการเรียกใช้ซับรูทีน การทำงานจะเริ่มต้นที่จุดเริ่มต้น และเมื่อซับรูทีนสิ้นสุดลง การทำงานก็จะสิ้นสุดลงเช่นกัน อินสแตนซ์ของซับรูทีนจะส่งคืนค่าเพียงครั้งเดียว และไม่เก็บสถานะระหว่างการเรียกใช้ ในทางตรงกันข้าม...

ด้าย

โครูทีนมีความคล้ายคลึงกับ เธรด มาก อย่างไรก็ตาม โครูทีนเป็นการ ทำงานแบบมัลติทาสก์แบบร่วมมือกัน ในขณะที่เธรดโดยทั่วไปเป็นการ ทำงาน แบบมัลติทาสก์แบบแทรกแซง โครูทีนช่วยให้เกิด ความพร้อมกัน (concurrency) เพราะอนุญาตให้ทำงานต่างๆ ได้โดยไม่เรียงลำดับ...