ตัวอย่าง C++ Coroutines

Tawxyang C Coroutines



Coroutines มีฟีเจอร์ภาษาที่ช่วยให้คุณสามารถเขียนโค้ดอะซิงโครนัสในลักษณะที่มีการจัดระเบียบและเป็นเส้นตรงมากขึ้น ส่งเสริมแนวทางที่มีโครงสร้างและต่อเนื่องกัน โดยให้กลไกในการหยุดชั่วคราวและรีสตาร์ทการทำงานของฟังก์ชันในบางกรณีโดยไม่หยุดทั้งเธรด Coroutines มีประโยชน์เมื่อจัดการกับงานที่ต้องรอการดำเนินการ I/O เช่น การอ่านจากไฟล์หรือการโทรผ่านเครือข่าย

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

การใช้โครูทีน

Coroutines จำเป็นด้วยเหตุผลหลายประการในการเขียนโปรแกรมสมัยใหม่ โดยเฉพาะในภาษาเช่น C++ ต่อไปนี้เป็นเหตุผลสำคัญบางประการว่าทำไม Coroutines จึงมีประโยชน์:







Coroutines มอบโซลูชันที่หรูหราสำหรับการเขียนโปรแกรมแบบอะซิงโครนัส ช่วยให้สามารถสร้างโค้ดที่ปรากฏตามลำดับและบล็อกได้ ซึ่งง่ายต่อการให้เหตุผลและเข้าใจ Coroutines สามารถระงับการดำเนินการ ณ จุดใดจุดหนึ่งโดยไม่ปิดกั้นเธรด ทำให้สามารถทำงานอื่นๆ แบบขนานได้ ด้วยเหตุนี้ ทรัพยากรระบบอาจถูกนำมาใช้อย่างมีประสิทธิภาพมากขึ้น และการตอบสนองจะเพิ่มขึ้นในแอปพลิเคชันที่เกี่ยวข้องกับการดำเนินการ I/O หรือการรอเหตุการณ์ภายนอก



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



Coroutines จัดเตรียมวิธีการที่มีโครงสร้างเพื่อจัดการกับภาวะเห็นพ้องกันและความเท่าเทียม ช่วยให้คุณสามารถแสดงรูปแบบการประสานงานที่ซับซ้อนและเวิร์กโฟลว์แบบอะซิงโครนัสโดยใช้ไวยากรณ์ที่ใช้งานง่ายยิ่งขึ้น ต่างจากรุ่นเธรดแบบดั้งเดิมที่เธรดอาจถูกบล็อก coroutines สามารถเพิ่มทรัพยากรระบบและเปิดใช้งานมัลติทาสก์ได้อย่างมีประสิทธิภาพ





มาสร้างตัวอย่างเพื่อสาธิตการใช้งาน Coroutines ใน C++

ตัวอย่างที่ 1: Coroutines พื้นฐาน

ตัวอย่าง Coroutines พื้นฐานมีดังต่อไปนี้:



#รวม

#รวม <โครูทีน>

โครงสร้าง นี้Corout {

โครงสร้าง สัญญา_ประเภท {

ThisCorout get_return_object ( ) { กลับ { } ; }

มาตรฐาน :: ระงับ_ไม่เคย Initial_suspend ( ) { กลับ { } ; }

มาตรฐาน :: ระงับ_ไม่เคย สุดท้าย_ระงับ ( ) ไม่ยกเว้น { กลับ { } ; }

เป็นโมฆะ ไม่สามารถจัดการ_ข้อยกเว้นได้ ( ) { }

เป็นโมฆะ กลับ_เป็นโมฆะ ( ) { }

} ;

บูล await_ready ( ) { กลับ เท็จ ; }

เป็นโมฆะ รอ_ระงับ ( มาตรฐาน :: coroutine_handle <> ชม. ) { }

เป็นโมฆะ await_resume ( ) { มาตรฐาน :: ศาล << 'โครูทีนกลับมาทำงานต่อแล้ว' << มาตรฐาน :: สิ้นสุด ; }

} ;

นี้Corout foo ( ) {

มาตรฐาน :: ศาล << “โครูทีนได้เริ่มต้นแล้ว” << มาตรฐาน :: สิ้นสุด ;

co_await มาตรฐาน :: ระงับ_เสมอ { } ;

ร่วม_กลับ ;

}

ภายใน หลัก ( ) {

อัตโนมัติ cr = ฟู ( ) ;

มาตรฐาน :: ศาล << “โครูทีนถูกสร้างขึ้นแล้ว” << มาตรฐาน :: สิ้นสุด ;

cr. await_resume ( ) ;

มาตรฐาน :: ศาล << “โครูทีนเสร็จแล้ว” << มาตรฐาน :: สิ้นสุด ;

กลับ 0 ;

}

มาดูโค้ดที่ให้ไว้ก่อนหน้านี้และอธิบายโดยละเอียด:

หลังจากรวมไฟล์ส่วนหัวที่จำเป็นแล้ว เราจะกำหนดโครงสร้าง 'ThisCorout' ที่แสดงถึง Coroutine ภายใน 'ThisCorout' มีการกำหนดโครงสร้างอื่นซึ่งเป็น 'promise_type' ที่จัดการสัญญา Coroutine โครงสร้างนี้มีฟังก์ชันต่างๆ ที่จำเป็นสำหรับเครื่องจักรโครูทีน

ภายในวงเล็บ เราใช้ฟังก์ชัน get_return_object() มันจะส่งคืนวัตถุ coroutine เอง ในกรณีนี้ จะส่งคืนออบเจ็กต์ 'ThisCorout' ที่ว่างเปล่า จากนั้นฟังก์ชัน Initial_suspend() จะถูกเรียกใช้ซึ่งจะกำหนดลักษณะการทำงานเมื่อเริ่มต้น Coroutine เป็นครั้งแรก std::suspend_never หมายความว่าไม่ควรระงับ coroutine ในขั้นต้น

หลังจากนั้น เรามีฟังก์ชัน Final_suspend() ซึ่งจะกำหนดพฤติกรรมเมื่อ Coroutine กำลังจะเสร็จสิ้น std::suspend_never หมายความว่าไม่ควรระงับ coroutine ก่อนที่จะทำการสรุป

หาก Coroutine ส่งข้อยกเว้น ระบบจะเรียกใช้เมธอด unhandled_Exception() ในตัวอย่างนี้ เป็นฟังก์ชันว่าง แต่คุณสามารถจัดการข้อยกเว้นได้ตามต้องการ เมื่อ Coroutine สิ้นสุดลงโดยไม่ให้ค่า เมธอด return_void() จะถูกเรียกใช้ ในกรณีนี้ ก็เป็นฟังก์ชันว่างเช่นกัน

นอกจากนี้เรายังกำหนดฟังก์ชันสมาชิกสามรายการภายใน 'ThisCorout' ฟังก์ชัน await_ready() ถูกเรียกเพื่อตรวจสอบว่า Coroutine พร้อมที่จะดำเนินการต่อหรือไม่ ในตัวอย่างนี้ จะคืนค่าเท็จเสมอ ซึ่งบ่งชี้ว่าโครูทีนไม่พร้อมที่จะดำเนินการต่อทันที เมื่อ Coroutine จะถูกระงับ จะมีการเรียกเมธอด await_suspend() ตรงนี้เป็นฟังก์ชันว่างซึ่งหมายความว่าไม่จำเป็นต้องมีการระงับ โปรแกรมจะเรียก await_resume() เมื่อ coroutine กลับมาทำงานต่อหลังจากการระงับชั่วคราว มันแค่ส่งข้อความที่ระบุว่า coroutine กลับมาทำงานต่อแล้ว

บรรทัดถัดไปของโค้ดจะกำหนดฟังก์ชัน foo() coroutine ภายใน foo() เราเริ่มต้นด้วยการพิมพ์ข้อความที่ระบุว่า Coroutine ได้เริ่มต้นแล้ว จากนั้น co_await std::suspend_always{} จะถูกใช้เพื่อระงับ coroutine และระบุว่าสามารถดำเนินการต่อได้ในภายหลัง คำสั่ง co_return ใช้เพื่อสิ้นสุด Coroutine โดยไม่ส่งคืนค่าใดๆ

ในฟังก์ชัน main() เราสร้างวัตถุ “cr” ประเภท “ThisCorout” โดยการเรียก foo() สิ่งนี้จะสร้างและเริ่มต้นโครูทีน จากนั้นจะมีการพิมพ์ข้อความที่ระบุว่าสร้าง Coroutine แล้ว ต่อไป เราจะเรียก await_resume() บนออบเจ็กต์ coroutine “cr” เพื่อดำเนินการต่อไป ภายใน await_resume() ข้อความ “The Coroutine is resumed” จะถูกพิมพ์ออกมา สุดท้ายนี้ เราจะแสดงข้อความที่ระบุว่าโครูทีนเสร็จสมบูรณ์ก่อนที่โปรแกรมจะสิ้นสุดลง

เมื่อคุณรันโปรแกรมนี้ ผลลัพธ์จะเป็นดังนี้:

ตัวอย่างที่ 2: Coroutine พร้อมพารามิเตอร์และการให้ผล

สำหรับภาพประกอบนี้ เราได้จัดเตรียมโค้ดที่สาธิตการใช้คอร์รูทีนพร้อมพารามิเตอร์และการให้ผลใน C++ เพื่อสร้างพฤติกรรมที่เหมือนตัวสร้างเพื่อสร้างลำดับของตัวเลข

#รวม

#รวม <โครูทีน>

#รวม <เวกเตอร์>

โครงสร้าง ใหม่โครูทีน {

โครงสร้าง p_type {

มาตรฐาน :: เวกเตอร์ < ภายใน > ค่านิยม ;

ใหม่Coroutine get_return_object ( ) { กลับ { } ; }

มาตรฐาน :: ระงับ_เสมอ Initial_suspend ( ) { กลับ { } ; }

มาตรฐาน :: ระงับ_เสมอ สุดท้าย_ระงับ ( ) ไม่ยกเว้น { กลับ { } ; }

เป็นโมฆะ ไม่สามารถจัดการ_ข้อยกเว้นได้ ( ) { }

เป็นโมฆะ กลับ_เป็นโมฆะ ( ) { }

มาตรฐาน :: ระงับ_เสมอ อัตราผลตอบแทน_value ( ภายใน ค่า ) {

ค่านิยม ผลักดัน_กลับ ( ค่า ) ;

กลับ { } ;

}

} ;

มาตรฐาน :: เวกเตอร์ < ภายใน > ค่านิยม ;

โครงสร้าง ตัววนซ้ำ {

มาตรฐาน :: coroutine_handle <> คอรัส_แฮนด์ ;

ตัวดำเนินการบูล != ( ค่าคงที่ ตัววนซ้ำ & อื่น ) ค่าคงที่ { กลับ คอรัส_แฮนด์ != อื่น. คอรัส_แฮนด์ ; }

ตัววนซ้ำ & ตัวดำเนินการ ++ ( ) { คอรัส_แฮนด์ ประวัติย่อ ( ) ; กลับ * นี้ ; }

ภายใน ตัวดำเนินการ * ( ) ค่าคงที่ { กลับ คอรัส_แฮนด์ สัญญา ( ) . ค่านิยม [ 0 ] ; }

} ;

ตัววนซ้ำเริ่มต้น ( ) { กลับ ตัววนซ้ำ { มาตรฐาน :: coroutine_handle < p_type >:: จาก_สัญญา ( สัญญา ( ) ) } ; }

สิ้นสุดตัววนซ้ำ ( ) { กลับ ตัววนซ้ำ { nullptr } ; }

มาตรฐาน :: coroutine_handle < p_type > สัญญา ( ) { กลับ
มาตรฐาน :: coroutine_handle < p_type >:: จาก_สัญญา ( * นี้ ) ; }

} ;

ใหม่Coroutine สร้างตัวเลข ( ) {

ร่วมผลผลิต 5 ;

ร่วมผลผลิต 6 ;

ร่วมผลผลิต 7 ;

}

ภายใน หลัก ( ) {

ใหม่Coroutine nc = สร้างตัวเลข ( ) ;

สำหรับ ( ภายใน ค่า : : nc ) {

มาตรฐาน :: ศาล << ค่า << ' ' ;

}

มาตรฐาน :: ศาล << มาตรฐาน :: สิ้นสุด ;

กลับ 0 ;

}

ในโค้ดก่อนหน้านี้ โครงสร้าง NEWCoroutine แสดงถึงตัวสร้างที่ใช้ Coroutine มีโครงสร้าง 'p_type' ที่ซ้อนกันซึ่งทำหน้าที่เป็นประเภทสัญญาสำหรับ coroutine โครงสร้าง p_type กำหนดฟังก์ชันที่จำเป็นโดยเครื่องจักร Coroutine เช่น get_return_object(), Initial_suspend(), Final_suspend(), unhandled_Exception() และ return_void() โครงสร้าง p_type ยังมีฟังก์ชัน Yield_value(int value) ซึ่งใช้เพื่อให้ได้ค่าจาก Coroutine โดยจะเพิ่มค่าที่ระบุให้กับเวกเตอร์ค่า

โครงสร้าง NEWCoroutine รวมถึงตัวแปรสมาชิก std::vector ที่เรียกว่า 'ค่า' ซึ่งแสดงถึงค่าที่สร้างขึ้น ภายใน NEWCoroutine มีตัววนซ้ำโครงสร้างแบบซ้อนที่อนุญาตให้วนซ้ำค่าที่สร้างขึ้น โดยจะมี coro_handle ซึ่งเป็นตัวจัดการ coroutine และกำหนดตัวดำเนินการ เช่น !=, ++ และ * สำหรับการวนซ้ำ

เราใช้ฟังก์ชัน beginning() เพื่อสร้างตัววนซ้ำที่จุดเริ่มต้นของ coroutine โดยรับ coro_handle จากสัญญา p_type ในขณะที่ฟังก์ชัน end() จะสร้างตัววนซ้ำที่แสดงถึงจุดสิ้นสุดของ coroutine และสร้างด้วย nullptr coro_handle หลังจากนั้น ฟังก์ชัน Promise() จะถูกนำมาใช้เพื่อส่งคืนประเภทสัญญาโดยสร้าง coroutine_handle จากสัญญา p_type ฟังก์ชัน GenerateNumbers() คือ Coroutine ที่ให้ค่าสามค่า ได้แก่ 5, 6 และ 7 โดยใช้คีย์เวิร์ด co_yield

ในฟังก์ชัน main() อินสแตนซ์ของ NEWCoroutine ชื่อ 'nc' จะถูกสร้างขึ้นโดยการเรียกใช้ GenerateNumbers() coroutine สิ่งนี้จะเริ่มต้นโครูทีนและบันทึกสถานะของมัน ลูป 'for' ตามช่วงใช้เพื่อวนซ้ำค่า 'nc' และแต่ละค่าจะถูกพิมพ์ซึ่งคั่นด้วยช่องว่างโดยใช้ std::cout

ผลลัพธ์ที่สร้างขึ้นจะเป็นดังนี้:

บทสรุป

บทความนี้สาธิตการใช้งาน Coroutines ใน C++ เราได้พูดคุยกันสองตัวอย่าง สำหรับภาพประกอบแรก โครูทีนพื้นฐานจะถูกสร้างขึ้นในโปรแกรม C++ โดยใช้ฟังก์ชันโครูทีน ในขณะที่การสาธิตครั้งที่สองดำเนินการโดยการใช้โครูทีนพร้อมพารามิเตอร์และยอมจำนนเพื่อสร้างพฤติกรรมเหมือนตัวกำเนิดเพื่อสร้างลำดับของตัวเลข