Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

พื้นฐาน Async Programming: Async, Await, Future และ Stream

operation หลายอย่างที่เราขอให้คอมพิวเตอร์ทำใช้เวลาสักครู่ที่จะเสร็จ จะดีถ้าเราทำอย่างอื่นได้ในขณะที่เรารอ process ที่รันนานเหล่านั้นเสร็จ คอมพิวเตอร์สมัยใหม่เสนอสองเทคนิคสำหรับทำงานบนมากกว่าหนึ่ง operation ในเวลาเดียวกัน — parallelism และ concurrency อย่างไรก็ตาม logic ของ โปรแกรมของเราถูกเขียนในแบบส่วนใหญ่ linear เราต้องการระบุ operation ที่โปรแกรมควรทำและจุดที่ฟังก์ชัน pause และส่วนอื่นของโปรแกรมรันแทน ได้ โดยไม่ต้องระบุล่วงหน้าแน่นอนลำดับและวิธีที่แต่ละชิ้นของโค้ดควร รัน Asynchronous programming เป็น abstraction ที่ให้เราแสดงโค้ดของ เราในเชิงของจุด pause ที่เป็นไปได้และผลในที่สุดที่ดูแลรายละเอียดของ การประสานงานให้เรา

บทนี้สร้างต่อจากการใช้เธรดสำหรับ parallelism และ concurrency ในบท ที่ 16 โดยแนะนำแนวทางทางเลือกในการเขียนโค้ด — future, stream ของ Rust และ syntax async และ await ที่ให้เราแสดงว่า operation asynchronous ได้ยังไง และ third-party crate ที่ implement asynchronous runtime — โค้ดที่จัดการและประสานการ execution ของ operation asynchronous

มาพิจารณาตัวอย่าง สมมติว่าคุณกำลัง export video ที่คุณสร้างของการ ฉลองครอบครัว operation ที่อาจใช้เวลาตั้งแต่นาทีถึงชั่วโมง การ export video จะใช้พลัง CPU และ GPU เท่าที่ทำได้ ถ้าคุณมีเพียง CPU core เดียวและ OS ของคุณไม่ pause การ export นั้นจนกว่ามันเสร็จ — นั่นคือ ถ้ามัน execute การ export synchronously — คุณทำอะไรอื่นบนคอมพิวเตอร์ ไม่ได้ขณะที่งานนั้นกำลังรัน นั่นจะเป็นประสบการณ์ที่ค่อนข้างหงุดหงิด โชคดี OS ของคอมพิวเตอร์ของคุณสามารถ และทำ interrupt การ export อย่าง มองไม่เห็นบ่อยพอเพื่อให้คุณทำงานอื่นได้พร้อมกัน

ตอนนี้สมมติคุณกำลัง download video ที่แชร์โดยคนอื่น ซึ่งยังใช้เวลา สักครู่แต่ไม่ใช้เวลา CPU มากเท่า ในกรณีนี้ CPU ต้องรอข้อมูลมาถึงจาก network ในขณะที่คุณเริ่มอ่านข้อมูลได้เมื่อมันเริ่มมาถึง มันอาจใช้ เวลาให้ทั้งหมดของมันปรากฏ แม้เมื่อข้อมูลทั้งหมดมา ถ้า video ใหญ่ อาจใช้เวลาอย่างน้อยหนึ่งหรือสองวินาทีในการโหลดทั้งหมด นั่นอาจไม่ฟัง ดูมาก แต่มันเป็นเวลานานมากสำหรับ processor สมัยใหม่ ซึ่งทำพันล้าน operation ได้ทุกวินาที อีกครั้ง OS ของคุณจะ interrupt โปรแกรมของ คุณอย่างมองไม่เห็นเพื่ออนุญาตให้ CPU ทำงานอื่นในขณะที่รอการเรียก network จบ

การ export video เป็นตัวอย่างของ operation CPU-bound หรือ compute-bound มันถูกจำกัดโดยความเร็วการประมวลผลข้อมูลที่เป็นไปได้ ของคอมพิวเตอร์ภายใน CPU หรือ GPU และเท่าไรของความเร็วนั้นที่มัน ทุ่มเทให้ operation ได้ การ download video เป็นตัวอย่างของ operation I/O-bound เพราะมันถูกจำกัดโดยความเร็วของ input และ output ของ คอมพิวเตอร์ — มันไปได้เร็วเท่าที่ข้อมูลส่งผ่าน network ได้

ในทั้งสองตัวอย่างเหล่านี้ การ interrupt ที่มองไม่เห็นของ OS ให้รูป แบบของ concurrency concurrency นั้นเกิดเฉพาะที่ระดับของโปรแกรม ทั้งหมด — OS interrupt โปรแกรมหนึ่งเพื่อให้โปรแกรมอื่นทำงานได้ ใน หลายกรณี เพราะเราเข้าใจโปรแกรมของเราที่ระดับที่ละเอียดมากกว่า OS เราสังเกตโอกาสสำหรับ concurrency ที่ OS เห็นไม่ได้

ตัวอย่างเช่น ถ้าเรากำลัง build เครื่องมือเพื่อจัดการ download ไฟล์ เราควรเขียนโปรแกรมของเราเพื่อให้การเริ่ม download หนึ่งไม่ lock UI และ user ควรเริ่มหลาย download พร้อมกันได้ API หลายตัวของ OS สำหรับ interact กับ network เป็น blocking — นั่นคือ พวกมัน block ความก้าวหน้าของโปรแกรมจนกว่าข้อมูลที่พวกมันกำลังประมวลผลพร้อมสมบูรณ์

สังเกต — นี่คือวิธีที่ ส่วนใหญ่ ของการเรียกฟังก์ชันทำงาน ถ้าคุณ คิด อย่างไรก็ตาม คำว่า blocking มักสงวนสำหรับการเรียกฟังก์ชันที่ interact กับไฟล์, network หรือ resource อื่นบนคอมพิวเตอร์ เพราะ เหล่านั้นเป็นกรณีที่โปรแกรมแต่ละตัวจะได้ประโยชน์จาก operation ที่ ไม่ใช่ blocking

เราหลีกเลี่ยงการ block เธรดหลักได้โดย spawn เธรดเฉพาะเพื่อ download แต่ละไฟล์ อย่างไรก็ตาม overhead ของ resource ของระบบที่ใช้โดยเธรด เหล่านั้นในที่สุดจะกลายเป็นปัญหา จะดีกว่าถ้าการเรียกไม่ block ที่ แรก และแทน เรานิยามจำนวนของงานที่เราต้องการให้โปรแกรมของเราเสร็จ และอนุญาตให้ runtime เลือกลำดับและวิธีที่ดีที่สุดในการรันพวกมันได้

นั่นคือสิ่งที่ abstraction async (ย่อสำหรับ asynchronous) ของ Rust ให้เรา ในบทนี้ คุณจะเรียนทั้งหมดเกี่ยวกับ async ขณะที่เรา ครอบคลุมหัวข้อต่อไปนี้:

  • วิธีใช้ syntax async และ await ของ Rust และ execute ฟังก์ชัน asynchronous ด้วย runtime
  • วิธีใช้ model async ในการแก้บางความท้าทายเดียวกับที่เราดูในบทที่ 16
  • วิธีที่ multithreading และ async ให้วิธีแก้ที่เสริมกันที่คุณรวม กันในหลายกรณีได้

ก่อนเราเห็น async ทำงานในการปฏิบัติ เราต้องเบี่ยงสั้นเพื่อพูดถึง ความแตกต่างระหว่าง parallelism และ concurrency

Parallelism และ Concurrency

เราได้ปฏิบัติกับ parallelism และ concurrency ว่าส่วนใหญ่แลกเปลี่ยน ได้จนถึงตอนนี้ ตอนนี้เราต้องแยกระหว่างพวกมันแม่นยำขึ้น เพราะความ แตกต่างจะปรากฏเมื่อเราเริ่มทำงาน

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

เมื่อบุคคลทำงานบนหลายงานต่างกันก่อนงานใดเสร็จ นี่คือ concurrency วิธีหนึ่งในการ implement concurrency คล้ายกับการมีสองโปรเจกต์ต่างกัน check out บนคอมพิวเตอร์ของคุณ และเมื่อคุณเบื่อหรือติดบนโปรเจกต์หนึ่ง คุณสลับไปยังอีกอัน คุณเป็นเพียงคนเดียว ดังนั้นคุณก้าวหน้าบนทั้งสอง งานพร้อมกันแน่นอนไม่ได้ แต่คุณ multitask ก้าวหน้าบนทีละหนึ่งโดย สลับระหว่างพวกมันได้ (ดู Figure 17-1)

A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to B1, B1 to A2, A2 to B2, B2 to A3, A3 to A4, and A4 to B3. The arrows between the subtasks cross the boxes between Task A and Task B.
Figure 17-1: workflow concurrent สลับระหว่าง Task A และ Task B

เมื่อทีมแยกกลุ่มของงานโดยให้แต่ละสมาชิกรับหนึ่งงานและทำงานบนมันคน เดียว นี่คือ parallelism แต่ละคนในทีมก้าวหน้าได้ในเวลาเดียวกัน แน่นอน (ดู Figure 17-2)

A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to A2, A2 to A3, A3 to A4, B1 to B2, and B2 to B3. No arrows cross between the boxes for Task A and Task B.
Figure 17-2: workflow parallel ที่งานเกิดบน Task A และ Task B อย่างอิสระ

ในทั้งสอง workflow เหล่านี้ คุณอาจต้องประสานระหว่างงานต่างกัน บางที คุณคิดว่างานที่ assign ให้คนหนึ่งเป็นอิสระจากงานของคนอื่นทั้งหมด แต่จริง ๆ มันต้องการอีกคนในทีมเสร็จงานของพวกเขาก่อน บางส่วนของงาน ทำแบบ parallel ได้ แต่บางส่วนจริง ๆ คือ serial — มันเกิดได้เฉพาะ เป็น series หนึ่งงานหลังอีก ดังใน Figure 17-3

A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. In Task A, arrows point from A1 to A2, from A2 to a pair of thick vertical lines like a “pause” symbol, and from that symbol to A3. In task B, arrows point from B1 to B2, from B2 to B3, from B3 to A3, and from B3 to B4.
Figure 17-3: workflow ที่ parallel บางส่วน ที่งานเกิดบน Task A และ Task B อย่างอิสระจนกว่า Task A3 ถูก block บนผลของ Task B3

ในทำนองเดียวกัน คุณอาจรู้ตัวว่าหนึ่งในงานของคุณเองขึ้นกับอีกงานของ คุณ ตอนนี้งาน concurrent ของคุณก็กลายเป็น serial

Parallelism และ concurrency ตัดกับกันและกันได้ด้วย ถ้าคุณรู้ว่าเพื่อน ร่วมงานติดจนกว่าคุณเสร็จหนึ่งในงานของคุณ คุณจะโฟกัสความพยายามทั้งหมด ของคุณบนงานนั้นเพื่อ “unblock” เพื่อนร่วมงานของคุณ คุณและเพื่อนร่วม งานไม่สามารถทำงานแบบ parallel อีก และคุณยังไม่สามารถทำงานแบบ concurrent บนงานของคุณเองอีก

dynamic พื้นฐานเดียวกันเข้ามาเล่นกับซอฟต์แวร์และ hardware บนเครื่อง ที่มี CPU core เดียว CPU ทำได้เพียงหนึ่ง operation ในเวลาเดียวกัน แต่มันยังทำงาน concurrent ได้ โดยใช้เครื่องมือเช่นเธรด, process และ async คอมพิวเตอร์ pause กิจกรรมหนึ่งและสลับไปอื่นก่อนในที่สุด cycle กลับไปยังกิจกรรมแรกอีก บนเครื่องที่มีหลาย CPU core มันยังทำงานแบบ parallel ได้ core หนึ่งทำงานหนึ่งในขณะที่อีก core ทำงานที่ไม่ เกี่ยวข้องสมบูรณ์ และ operation เหล่านั้นเกิดในเวลาเดียวกันจริง ๆ

การรันโค้ด async ใน Rust มักเกิด concurrent ขึ้นกับ hardware, OS และ async runtime ที่เราใช้ (เพิ่มเกี่ยวกับ async runtime ในไม่ช้า) concurrency นั้นอาจใช้ parallelism ใต้ฝ่ามือด้วย

ตอนนี้ มาดำดิ่งว่า async programming ใน Rust ทำงานยังไงจริง ๆ