نحوه عملکرد برنامه نویسی ناهمزمان در Rust – Futures و Async/Await با مثال هایی توضیح داده شده است
اگر با زبان هایی مانند جاوا اسکریپت و پایتون آشنایی دارید، ممکن است در مورد برنامه نویسی ناهمزمان شنیده باشید. و شاید شما تعجب کنید که چگونه در Rust کار می کند.
در این مقاله، یک نمای کلی ساده از نحوه عملکرد برنامه نویسی ناهمزمان در Rust به شما ارائه می دهم. در مورد معاملات آتی و همچنین async
/ .await
خواهید آموخت.
این مقاله یک راهنمای مبتدی برای Rust یا برنامه نویسی ناهمزمان نیست، پس برای استفاده حداکثری از این راهنما باید کمی تجربه در برنامه نویسی در Rust و آشنایی با برنامه نویسی ناهمزمان داشته باشید.
با این گفته، بیایید شروع کنیم!
چه زمانی باید از برنامه نویسی ناهمزمان استفاده کرد؟
وظایف ناهمزمان مانند یک نسخه یکپارچه تر از رشته ها کار می کنند. شما می توانید آنها را در بسیاری از سناریوهای مشابه که می توانید از چندین رشته استفاده کنید استفاده کنید. مزایایی که برنامهنویسی همگام در چندین رشته ارائه میکند این است که برنامههای چند رشتهای در مقایسه با برنامههای ناهمزمان، سربار بیشتری برای کار روی چندین کار دارند.
اما این برنامه های ناهمزمان را بهتر از برنامه های چند رشته ای نمی کند. برنامه های چند رشته ای می توانند چندین کار محاسباتی فشرده را به طور همزمان اجرا کنند - و چندین برابر سریعتر از زمانی که همه وظایف را در یک رشته اجرا می کنید.
با کدنویسی ناهمزمان، تلاش برای اجرای همزمان چندین برنامه محاسباتی فشرده بسیار کندتر از اجرای هر کار بر روی یک رشته واحد خواهد بود.
برنامه نویسی ناهمزمان برای ساخت برنامه هایی که درخواست های شبکه زیادی می کنند یا بسیاری از عملیات IO مانند خواندن و نوشتن فایل بسیار خوب است.
من نمی توانم هر موردی را که می خواهید از تکنیک های ناهمزمان استفاده کنید، پوشش دهم. اما می توانم بگویم که برای کارهایی که زمان بیکاری زیادی دارند (مثلاً منتظر ماندن سرور برای پاسخ به درخواست شبکه) سودمندتر است.
در طول زمان بیکاری یک کار ناهمزمان، به جای مسدود کردن رشته، کنترل کننده رویداد روی سایر وظایف برنامه کار می کند تا زمانی که کار ناهمزمان برای پیشرفت آماده شود.
تحلیل اجمالی زنگ ناهمزمان - آینده
اصول اولیه Rust ناهمزمان Futures هستند. معاملات آتی شبیه به وعده های جاوا اسکریپت است. آنها روشی هستند که Rust می گوید: "هی، من بعداً نتیجه را به شما خواهم داد، اما فقط به این (نمونه آینده) ادامه دهید تا پیگیری کنید که آیا نتیجه آماده است یا خیر."
آینده ها صفاتی هستند، با حالت poll
که یا Poll::Pending
که نشان می دهد آینده در حال انجام وظیفه خود است، یا Poll::Ready
نشان می دهد که آینده با وظیفه خود تمام شده است.
آینده ها تنبل هستند. آنها زمانی اجرا می شوند که شما آنها را .await
(ما در بخش بعدی نحوه .await
آنها را تحلیل خواهیم کرد). .await
در آینده، اجرای یک رشته ناهمزمان را متوقف می کند و کار خود را شروع می کند. در این مرحله نتیجه روش poll
Poll::Pending
است. هنگامی که آینده با وظیفه خود تمام شد، وضعیت poll
به Poll::Ready
تبدیل می شود و آینده به رشته آن اجازه می دهد تا ادامه یابد.
اگر میخواهید بیشتر با جزئیات فنی درباره آتی آشنا شوید، میتوانید مستندات را تحلیل کنید.
async/.wait در Rust
async
و .await
راه های اصلی کار با کدهای ناهمزمان در Rust هستند. async
یک کلمه کلیدی برای اعلام توابع ناهمزمان است. در این توابع، کلمه کلیدی .await
اجرای خود را تا زمانی که نتیجه آینده آماده شود متوقف می کند.
بیایید به یک مثال نگاه کنیم:
async fn add (a: u8 , b: u8 ) -> u8 { a + b } async fn secondFunc () -> u8 { let a = 10 ; let b = 20 ; let result = add(a, b). await ; return result; }
هر تابع ناهمزمان اعلام شده با async fn
مقدار بازگشتی خود را در آینده میپیچد. در خط سوم secondFunc
، ما .await
آینده از add(a, b)
هستیم تا قبل از بازگرداندن آن، نتیجه آن را بدست آوریم.
نحوه کار با عملیات ناهمزمان به صورت main
Rust به شما اجازه نمی دهد که عملکردهای اصلی async fn
. اجرای عملیات ناهمزمان از یک تابع غیرهمزمان ممکن است منجر به عدم پایان کامل برخی از عملیات قبل از خروج از رشته اصلی شود.
در این بخش به دو راه حل این مشکل می پردازیم. دو راه برای انجام این کار با futures
و tokio
هستند. بیایید به هر دو نگاه کنیم.
tokio
tokio
پلتفرمی است که ابزارها و APIها را برای اجرای برنامه های ناهمزمان ارائه می دهد. tokio
همچنین به شما اجازه می دهد تا به راحتی یک تابع اصلی ناهمزمان را اعلام کنید، که به مشکلی که قبلا توضیح دادم کمک می کند.
برای نصب tokio
در پروژه خود، این خط را به Cargo.toml
آن اضافه کنید:
[dependencies] tokio = { version = "1" , features = [ "full" ] }
پس از اضافه کردن خط، می توانید توابع main
خود را به صورت زیر بنویسید:
async fn add (a: u8 , b: u8 ) -> u8 { a + b } #[tokio::main] async fn main () { let a = 10 ; let b = 20 ; let result = add(a, b). await ; println! ( "{result}" ); }
کتابخانه futures
futures
کتابخانه ای است که روش هایی را برای کار با Rust ناهمزمان ارائه می دهد. برای مورد استفاده ما، futures
futures::executor::block_on()
ارائه می دهد. این روش مشابه .await
در توابع ناهمزمان عمل می کند. تا زمانی که نتیجه آینده آماده شود، نخ اصلی را مسدود می کند. futures::executor::block_on()
نیز نتیجه آینده تکمیل شده را برمی گرداند.
برای نصب futures
در پروژه خود، این خط را به Cargo.toml
آن اضافه کنید:
[dependencies] futures = "0.3"
پس از نصب کتابخانه، می توانید کاری شبیه به این انجام دهید:
use futures::executor::block_on; async fn add (a: u8 , b: u8 ) -> u8 { a + b } fn main () { let a = 10 ; let b = 20 ; let result = block_on(add(a, b)); println! ( "{result}" ); }
ابتدا متد block_on
را در خط اول وارد می کنیم و تا زمانی که نتیجه تابع add(a, b)
آماده شود از متد برای مسدود کردن رشته اصلی استفاده می کنیم. ما همچنین مجبور نبودیم عملکرد main
را مانند tokio
ناهمزمان کنیم.
نتیجه گیری
برنامه نویسی ناهمزمان به شما امکان می دهد عملیات ها را به صورت موازی و با سربار و پیچیدگی کمتر در مقایسه با چند رشته ای سنتی اجرا کنید. در Rust به شما امکان می دهد برنامه های ورودی/خروجی و شبکه را کارآمدتر بسازید.
در حالی که این مقاله به شما کمک می کند تا با اصول برنامه نویسی ناهمزمان در Rust آشنا شوید، اما هنوز هم فقط یک نمای کلی است. مواردی وجود دارد که باید از ساختارهای دیگری مانند Pinning، Arcs و غیره در Rust استفاده کنید.
اگر سوال یا نظری دارید با من در میان بگذارید. با تشکر برای خواندن!
ارسال نظر