Coroutines คืออะไรใน c ++ 20เหรอ?
แตกต่างจาก "Parallelism2" หรือ / และ "Concurrency2" อย่างไร (ดูภาพด้านล่าง)
ภาพด้านล่างมาจาก ISOCPP
Coroutines คืออะไรใน c ++ 20เหรอ?
แตกต่างจาก "Parallelism2" หรือ / และ "Concurrency2" อย่างไร (ดูภาพด้านล่าง)
ภาพด้านล่างมาจาก ISOCPP
คำตอบ:
ในระดับนามธรรม Coroutines แยกแนวคิดในการมีสถานะการดำเนินการออกจากแนวคิดที่จะมีเธรดการดำเนินการ
SIMD (คำสั่งเดียวหลายข้อมูล) มี "เธรดการดำเนินการ" หลายรายการ แต่มีสถานะการดำเนินการเพียงสถานะเดียว (ใช้งานได้กับข้อมูลหลายรายการ) อัลกอริธึมแบบคู่ขนานนั้นค่อนข้างจะเป็นเช่นนี้โดยที่คุณมี "โปรแกรม" หนึ่งโปรแกรมที่ทำงานบนข้อมูลที่แตกต่างกัน
เธรดมี "เธรดการดำเนินการ" หลายรายการและสถานะการดำเนินการหลายรายการ คุณมีโปรแกรมมากกว่าหนึ่งโปรแกรมและมีเธรดการดำเนินการมากกว่าหนึ่งชุด
Coroutines มีสถานะการดำเนินการหลายสถานะ แต่ไม่มีเธรดการดำเนินการ คุณมีโปรแกรมและโปรแกรมมีสถานะ แต่ไม่มีเธรดการดำเนินการ
ตัวอย่างที่ง่ายที่สุดของโครูทีนคือเครื่องกำเนิดไฟฟ้าหรือตัวนับจากภาษาอื่น
ในรหัสหลอก:
function Generator() {
for (i = 0 to 100)
produce i
}
เรียกว่าและครั้งแรกที่ถูกเรียกมันว่าผลตอบแทนGenerator
0
สถานะของมันเป็นที่จดจำ (สถานะที่แตกต่างกันไปตามการใช้งานโครูทีน) และในครั้งต่อไปที่คุณเรียกมันว่าจะดำเนินต่อจากจุดที่ค้างไว้ ดังนั้นจะคืนค่า 1 ในครั้งต่อไป จากนั้น 2.
ในที่สุดก็มาถึงจุดสิ้นสุดของลูปและหลุดออกจากจุดสิ้นสุดของฟังก์ชัน โครูทีนเสร็จสิ้น (สิ่งที่เกิดขึ้นที่นี่จะแตกต่างกันไปตามภาษาที่เรากำลังพูดถึงใน python จะแสดงข้อยกเว้น)
Coroutines นำความสามารถนี้มาสู่ C ++
โครูทีนมีสองชนิด ซ้อนกันและไม่ซ้อนกัน
โครูทีนแบบไม่ซ้อนกันจะเก็บเฉพาะตัวแปรโลคัลในสถานะและตำแหน่งของการดำเนินการเท่านั้น
โครูทีนแบบเรียงซ้อนจะจัดเก็บสแต็กทั้งหมด (เช่นเธรด)
โครูทีนแบบไม่เรียงซ้อนอาจมีน้ำหนักเบามาก ข้อเสนอสุดท้ายที่ฉันอ่านเกี่ยวข้องโดยทั่วไปแล้วการเขียนฟังก์ชันของคุณใหม่ให้เป็นแลมด้า ตัวแปรโลคัลทั้งหมดเข้าสู่สถานะของอ็อบเจ็กต์และเลเบลถูกใช้เพื่อข้ามไปยัง / จากตำแหน่งที่โครูทีน "สร้าง" ผลลัพธ์ระดับกลาง
กระบวนการสร้างมูลค่าเรียกว่า "ผลผลิต" เนื่องจากโครูทีนมีลักษณะคล้ายกับมัลติเธรดแบบร่วมมือ คุณกำลังให้จุดประหารกลับไปยังผู้โทร
Boost มีการใช้โครูทีนแบบซ้อนกัน ช่วยให้คุณสามารถเรียกใช้ฟังก์ชันเพื่อให้ผลตอบแทนสำหรับคุณ โครูทีนแบบเรียงซ้อนมีประสิทธิภาพมากกว่า แต่ก็มีราคาแพงกว่าด้วย
มีอะไรมากกว่าเครื่องกำเนิดไฟฟ้าธรรมดา คุณสามารถรอโครูทีนในโครูทีนซึ่งช่วยให้คุณสามารถเขียนโครูทีนได้อย่างมีประโยชน์
โครูทีนเช่น if ลูปและการเรียกใช้ฟังก์ชันเป็น "goto ที่มีโครงสร้าง" อีกประเภทหนึ่งที่ช่วยให้คุณสามารถแสดงรูปแบบที่เป็นประโยชน์บางอย่าง (เช่นเครื่องสเตต) ได้อย่างเป็นธรรมชาติมากขึ้น
การนำ Coroutines ไปใช้งานเฉพาะใน C ++ นั้นน่าสนใจเล็กน้อย
ในระดับพื้นฐานที่สุดจะเพิ่มคำหลักสองสามคำลงใน C ++: co_return
co_await
co_yield
พร้อมกับไลบรารีบางประเภทที่ใช้งานได้
ฟังก์ชันจะกลายเป็นโครูทีนโดยมีหนึ่งในฟังก์ชันนั้นอยู่ในตัว ดังนั้นจากการประกาศของพวกเขาพวกเขาจึงแยกไม่ออกจากฟังก์ชัน
เมื่อใช้คำสำคัญหนึ่งในสามคำเหล่านี้ในเนื้อความของฟังก์ชันการตรวจสอบประเภทการส่งคืนและอาร์กิวเมนต์ที่ได้รับคำสั่งมาตรฐานจะเกิดขึ้นและฟังก์ชันจะถูกเปลี่ยนเป็นโครูทีน การตรวจสอบนี้จะบอกคอมไพลเลอร์ว่าจะเก็บสถานะฟังก์ชันไว้ที่ใดเมื่อฟังก์ชันถูกระงับ
โครูทีนที่ง่ายที่สุดคือเครื่องกำเนิดไฟฟ้า:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; true; current+= step)
co_yield current;
}
co_yield
ระงับการดำเนินการฟังก์ชั่นร้านค้าที่ของรัฐในgenerator<int>
แล้วส่งกลับค่าของผ่านcurrent
generator<int>
คุณสามารถวนซ้ำจำนวนเต็มที่ส่งคืนได้
co_await
ในขณะเดียวกันให้คุณประกบโครูทีนหนึ่งเข้ากับอีกอันหนึ่ง หากคุณอยู่ในโครูทีนเดียวและคุณต้องการผลลัพธ์ของสิ่งที่รอคอย (มักจะเป็นโครูทีน) ก่อนที่จะดำเนินการต่อคุณco_await
ก็ต้องดำเนินการต่อไป หากพร้อมให้ดำเนินการต่อทันที หากไม่เป็นเช่นนั้นคุณจะหยุดชั่วคราวจนกว่าสิ่งที่คุณรอคอยจะพร้อม
std::future<std::expected<std::string>> load_data( std::string resource )
{
auto handle = co_await open_resouce(resource);
while( auto line = co_await read_line(handle)) {
if (std::optional<std::string> r = parse_data_from_line( line ))
co_return *r;
}
co_return std::unexpected( resource_lacks_data(resource) );
}
load_data
เป็นโครูทีนที่สร้างstd::future
เมื่อทรัพยากรที่ระบุชื่อถูกเปิดและเราจัดการเพื่อแยกวิเคราะห์ไปยังจุดที่เราพบข้อมูลที่ร้องขอ
open_resource
และread_line
อาจเป็น async coroutines ที่เปิดไฟล์และอ่านบรรทัดจากไฟล์ co_await
เชื่อมต่อพักและรัฐพร้อมload_data
ที่จะมีความคืบหน้าของพวกเขา
โครูทีน C ++ มีความยืดหยุ่นมากกว่านี้เนื่องจากมีการใช้งานเป็นชุดคุณสมบัติภาษาขั้นต่ำที่ด้านบนของประเภทพื้นที่ผู้ใช้ ประเภทพื้นที่ผู้ใช้กำหนดความหมายco_return
co_await
และความco_yield
หมายได้อย่างมีประสิทธิภาพ- ฉันเคยเห็นผู้คนใช้มันเพื่อใช้นิพจน์ทางเลือกแบบ monadic เช่นco_await
บนตัวเลือกที่ว่างเปล่าจะขับเคลื่อนสถานะว่างให้เป็นตัวเลือกภายนอกโดยอัตโนมัติ:
modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
return (co_await a) + (co_await b);
}
แทน
std::optional<int> add( std::optional<int> a, std::optional<int> b ) {
if (!a) return std::nullopt;
if (!b) return std::nullopt;
return *a + *b;
}
;;
ขอโทษที่ควรจะเป็น
โครูทีนเปรียบเสมือนฟังก์ชัน C ซึ่งมีคำสั่งส่งคืนหลายคำสั่งและเมื่อเรียกครั้งที่ 2 จะไม่เริ่มดำเนินการเมื่อเริ่มต้นฟังก์ชัน แต่เป็นคำสั่งแรกหลังจากการส่งคืนที่ดำเนินการก่อนหน้านี้ ตำแหน่งการดำเนินการนี้ถูกบันทึกพร้อมกับตัวแปรอัตโนมัติทั้งหมดที่จะอยู่บนสแต็กในฟังก์ชันที่ไม่ใช่โครูทีน
การใช้งานโครูทีนแบบทดลองก่อนหน้านี้จาก Microsoft ใช้สแต็กที่คัดลอกมาเพื่อให้คุณสามารถย้อนกลับจากฟังก์ชันที่ซ้อนกันอยู่ลึก ๆ แต่เวอร์ชันนี้ถูกปฏิเสธโดยคณะกรรมการ C ++ คุณสามารถรับการนำไปใช้งานนี้ได้เช่นกับไลบรารี Boosts fiber
โครูทีนควรจะเป็นฟังก์ชัน (ใน C ++) ที่สามารถ "รอ" ให้รูทีนอื่น ๆ ดำเนินการให้เสร็จสิ้นและจัดเตรียมสิ่งที่จำเป็นสำหรับกิจวัตรที่ถูกระงับหยุดชั่วคราวรอและดำเนินการต่อไป คุณลักษณะที่น่าสนใจที่สุดสำหรับชาว C ++ คือโครูทีนไม่ควรใช้พื้นที่สแต็ก ... C # สามารถทำสิ่งนี้ได้แล้วด้วยการรอและให้ผล แต่ C ++ อาจต้องสร้างขึ้นใหม่เพื่อให้ได้มา
การทำงานพร้อมกันจะเน้นอย่างมากในการแยกความกังวลโดยที่ความกังวลเป็นงานที่โปรแกรมควรทำให้เสร็จ การแยกข้อกังวลนี้อาจทำได้โดยหลายวิธี ... โดยปกติจะเป็นการมอบหมายบางประเภท แนวคิดของการเกิดขึ้นพร้อมกันคือกระบวนการหลายอย่างสามารถทำงานได้อย่างอิสระ (การแยกความกังวล) และ 'ผู้ฟัง' จะนำสิ่งที่เกิดขึ้นจากความกังวลที่แยกจากกันไปยังทุกที่ที่ควรจะไป สิ่งนี้ขึ้นอยู่กับการจัดการแบบอะซิงโครนัสบางประเภท มีหลายวิธีในการทำงานพร้อมกันรวมถึงการเขียนโปรแกรมเชิง Aspect และอื่น ๆ C # มีตัวดำเนินการ 'ผู้รับมอบสิทธิ์' ซึ่งทำงานได้ดีทีเดียว
ความเท่าเทียมกันฟังดูเหมือนการเกิดขึ้นพร้อมกันและอาจเกี่ยวข้อง แต่จริงๆแล้วเป็นโครงสร้างทางกายภาพที่เกี่ยวข้องกับโปรเซสเซอร์หลายตัวที่จัดเรียงในรูปแบบคู่ขนานไม่มากก็น้อยด้วยซอฟต์แวร์ที่สามารถกำหนดส่วนของโค้ดไปยังโปรเซสเซอร์ต่างๆที่จะเรียกใช้และผลลัพธ์จะได้รับกลับมา พร้อมกัน