درس : Actors and Processes اکتورها و پراسس ها روش های جالبی رو برای پیاده سازی concurrency بهمون ارایه میدن، بدون بلایایی که shared memory ها سرمون میاوردن توی همزمانی. قبل از شروع یک تعریف اولیه ازشون بریم و ببینیم که اصلا چی هستن. اکتور: یک پردازشگر مجازی هستش که لوکال state خصوصی خودشو داره. هر اکتور یک mailbox داره، وقتی مسیجی براش میاد و اکتور توی حالت idle هستش، بیدار میشه و مسیج ورودی شو پردازش میکنه. وقتی پردازشش تموم شد، مسیج های بعدی توی mailbox شو پردازش میکنه تا موقعی که خالیش کنه و دوباره بره توی مد sleep . حین پردازش یک مسیج، اکتور میتونه اکتورهای دیگه ای رو بسازه، به اکتورهای دیگه مسیج بده و. پراسس: مفهوم عام تری از پردازشگر مجازی است که معمولا توسط سیستم عامل ایجاد میشه و پردازش خاصی رو انجام میده، پراسس ها میتونن طبق تعریف خاصی شبیه به اکتورها باشن که منظور ما در اینجا هم همینه. چیزهایی که توی تعریف و ذات اکتورها نمیگنجه: - یک چیز مشخص تحت کنترل وجود نداره. هیچ چیزی اسکژول نشده که توی استپ بعد اجرا بشه، و یا تبدیلگری وجود نداره که داده خامو به خروجی تبدیل کنه. - تنها استیت موجود در سیستم توسط مسیج ها نگهداری میشه، و توی استیت لوکال اکتور ها میره، مسیج ها رو نمیشه پیش بینی کرد مگر زمانی که توسط گیرنده پراسس بشن، و استیت لوکال اکتورها از بیرون در دسترس نیست. - مسیج ها یک طرفه اند، مفهومی مثل reply وجود نداره، اگر بخاید از اکتوری رسپانس بگیرید باید میل باکس خودتونو توی مسیج بزارید تا وقتی اکتور مسیجو پردازش کرد اونو براتون بفرسته. - یک اکتور مسیج ها رو دونه دونه پردازش میکنه تا میل باکسشو خالی کنه، توی هر لحظه بیش از یک مسیج نمیتونه توسط اکتور پردازش بشه. در نهایت، مجموعه ای از اکتورها به صورت همزمان و async کار میکنند و هیچ چیزی رو به اشتراک نمیزارن. اگر به اندازه کافی کور سی پی یو داشته باشید میتونید روی هر کدوم یک اکتور ران کنید وگرنه runtime هایی هستند که برامون context سیستم رو سوییچ میکنن و چندین اکتور رو همزمان و موازی اجرا میکنن. و در هر صورت کدی که توی هر نوع اکتور اجرا میشه، یک کد یکسانه و ما چرا اومدیم سراغ اکتورها، چون به همزمانی برسیم. بیاید مشابه مثال پای سیب درس قبل رو بزنیم. در اینجا ما سه اکتور درگیر داریم: (مشتری، گارسون و سینی پای سیب) فلوی کلی جریان مسیج ها به این شکل خاهد بود: - مشتری درخواست پای سیب میده به گارسون - گارسون از محل سینی پای سیب استعلام میگیره برای موجود بودن پای سیب درخواستی - اگر به اندازه کافی موجود بود، سینی دار، پای سیب رو ارسال و به گارسون اعلام میکنه - اگر هم موجودی کافی نباشه، به گارسون اطلاع میده و گارسون هم از مشتری عذرخواهی میکنه ما این کدو با JS پیاده کردیم و با استفاده از Nact Library که با یک سری wrapper تونستیم به سادگی نوشتنه object ها اکتور بنویسیم به شکلی که key مسیج های دریافتی هست و value فانکشنی که بایستی اجرا بشه. خیلی از اکتور سیستم ها چیزی مشابه این رو ارایه میدن، فقط به زبون و سینتکس های متفاوت. بیاید با مشتری شروع کنیم. یک مشتری میتونه سه مدل مسیج مختلف رو دریافت کنه: - گرسنه بودن (که از مکانی خارج از دامین این مثال میاد مثلا دوستش بهش یاداوری میکنه گرسنه ای) - پای سیب درخواستی موجوده (از سمت سینی دار پای میز ارسال میشه) - متاسفم، پای سیبی نمونده (ارسالی از گارسون) کد اکتور مشتری به این شکله: customerActor = { : ( msg , ctx , state ) => { dispatch ( state . waiter , { type , customer : ctx . self , wants : } } ( msg , ctx , _ state ) => console . log ( $ { ctx . self . name } $ { msg . food } : ( msg , ctx , _ state ) => console . log ( $ { ctx . self . name } } کد اکتور گارسون: waiterActor = { : ( msg , ctx , state ) => { ( msg . wants = { dispatch ( state . pieCase , { type , customer : msg . customer , waiter : ctx . self } } { console . dir ( $ { msg . wants } ; } } ( msg , ctx ) => console . log ( $ { msg . food } $ { msg . customer . name } ( msg , ctx ) => { dispatch ( msg . customer , { type , msg : msg . msg } ; console . log ( $ { msg . customer . name } $ { msg . msg } } } وقتی اکتوره گارسون یک مسیج مبنی بر order از مشتری دریافت میکنه، چک میکنه که سفارش پای سیبه یا نه و در صورتی که پای سیب باشه ارسالش میکنه به سینی دار (کسی که از سینی مراقبت میکنه و مسیولش هست) پای سیب و رفرنسها هم اگر مشاهده کنید توسط مسیج ها پاس داده میشه. اکتور سینی دار، استیتش یک آرایه از اسلایس های پای سیبه. وقتی مسیج get slice رو دریافت میکنه از گارسون، نگاه میکنه که آیا موجودی براش باقی مونده، اگر آره اسلایس رو برای مشتری می فرسته و به گارسون هم اطلاع میده. pieCaseActor = { : ( msg , context , state ) => { ( state . slices . length = ) { dispatch ( msg . waiter , { type , msg , customer : msg . customer } state } { slice = state . slices . shift ( +; dispatch ( msg . customer , { type , food : slice } ; dispatch ( msg . waiter , { type , food : slice , customer : msg . customer } ; state ; } } } همونطور که ملاحظه کردید ما یکسری اکتور رو توسط اکتورهای دیگه ایجاد کردیم و بهشون initial state هم دادیم (رفرنس مشتری، سفارش و . ، همچنین اکتورها میتونن داینامیک ایجاد و استارت بشن. actorSystem = start ( ; pieCase = start _ actor ( actorSystem , pieCaseActor , { slices : [, ] } ; waiter = start _ actor ( actorSystem , waiterActor , { pieCase : pieCase } ; c = start _ actor ( actorSystem , customerActor , { waiter : waiter } ; c = start _ actor ( actorSystem , customerActor , { waiter : waiter } ; فرض کنید این کدو توی حالتی که دو تا مشتری گرسنه داریم که مشتری اول سه تا و مشتری دوم دو تا پای سیب درخواست میدن اجرا میکنیم: dispatch ( c , { type , waiter : waiter } ; dispatch ( c , { type , waiter : waiter } ; dispatch ( c , { type , waiter : waiter } ; dispatch ( c , { type , waiter : waiter } ; dispatch ( c , { type , waiter : waiter } ; sleep ( ) then ( => { stop ( actorSystem ) ; } وقتی اجراش کنیم، ارتباط و درگیری بین اکتورها رو مشاهده خواهیم کرد و یکی از خروجی های متصور به این شکله: customer sees " apple pie slice " appear on the table customer sees " peach pie slice " appear on the table Waiter adds apple pie slice to customer ' s order Waiter adds peach pie slice to customer ' s order customer sees " cherry pie slice " appear on the table Waiter adds cherry pie slice to customer ' s order The waiter apologizes to customer : no pie left customer sulks . The waiter apologizes to customer : no pie left customer sulks .