Sajjad Manafi
Sajjad Manafi
خواندن ۲ دقیقه·۳ سال پیش

Concurrency is not parallelism! #3

سلام؛ سعی کردم توی این نوشته، بخش های مهم ارائه Concurrency is not parallelism که توسط Rob Pike رو یه جورایی ترجمه کنم. و خوشحال میشم اگر مشکلی توی متن یا ترجمه بود کامنت بزارید تا تصحیح اش کنم.

این ارائه نسبتا طولانیه پس قراره چند تا پارت داشته باشیم و میتونید لینک پارت های قبلی و بعدی رو در انتهای نوشته پیدا کنید.

و اسلاید های ارائه رو هم میتونید از اینجا ببینید.

Goroutine

چیزی که احتمالا شبیه threads ها در نظرش میگیرید. ولی در واقع متفاوت اند. که من زیاد به جزئیات تفاوت اونها نمی پردازم. فقط میخوام بگم چی هستن.

f(&quothello&quot, &quotworld&quot) // f runs; we wait

خب ما اینجا یه function داخل برناممون داریم. در حالت عادی که هممون باهاش آشنا هستیم. ما صبر میکنیم که اجرای این function تموم بشه و بعد میریم خط بعدی.

go f(&quothello&quot, &quotworld&quot) // f starts running g() // does not wait for f to return

ولی اگر عبارت go رو قبل از اجرای function امون بزاریم چی؟ اتفاقی که میافته اینه که این تابع f شروع میکنه به اجرا شدن، ولی دیگه شما منتظر تموم شدن اجرای اون نمیموند و خط بعدی اجرا میشه. حداقل از نظر مفهومی نه لزوما. (concurrency vs parallel)

یعنی از لحاظ مفهومی برنامه ما به اجرا شدنش ادامه میده همزمان با اینکه تابع f داره کارای خودشو انجام میده.

اگر کمی گیج کننده هست، به این فکر کنید که بسیار شبیه ampersand (&) داخل shell هست. که کامند میره و توی بکگراند برای خودش اجرا میشه

A single ampersand terminates an asynchronous command. An ampersand does the same thing as a semicolon or newline in that it indicates the end of a command, but it causes Bash to execute the command asynchronously. That means Bash will run it in the background and run the next command immediately after, without waiting for the former to end.

Goroutines are not threads

خب goroutine ها خیلی شبیه thread ها هستن. ولی خب اونا خیلی ارزون ترن! یعنی ساختشون خیلی آسون و کم هزینه تره. و در صورت لزوم به صورت dynamic روی thread های سیستم عامل multiplex میشن. پس نیازی نیست نگران scheduling و blocking و... باشیم چون سیستم خودش حواسش به اینا هست و مراقبه :)

و هروقت که یک goroutine نیاز به block شدن داره، مثل یه خوندن یه چیزی یا... ، هیچ goroutine دیگه ای نیاز به منتظر موندن نداره، چون همشون به صورت dynamic برنامه ریزی میشن. بنابرین شاید شبیه thread ها به نظر برسن، ولی goroutine ها خیلی خیلی سبک تر و کم هزینه تر از اونا هستن.

خب گفتیم که باید بین این ها ارتباط برقرار کنیم. اما چطور؟

Channels

برای ارتباط برقرار کردن channel هارو داریم که یکمی شبیه pipe ها داخل shell هست.

Channels are typed values that allow goroutines to synchronize and exchange information.
?هرچقدر به جمله ی فارسی فکر کردم دیدم واقعا منظورو نمیشه رسوند

اینجا یه مثال نسبتا ساده براش داریم:

timerChan := make(chan time.Time) go func() { time.Sleep(deltaT) timerChan <- time.Now() // send time on timerChan }() // Do something else; when ready, receive. // Receive will block until timerChan delivers. // Value sent is other goroutine's completion time. completedAt := <-timerChan

یک timerChan داریم، که یک channel از نوع time هستش. و بعدش اون فانکشن رو توی بکگراند اجرا میکنیم. داخلش چند ثانیه ای sleep داریم. و بعدش زمان اون لحظه رو داخل timerChan میفرسته. و چون فانکشن بالایی رو با عبارت go اجرا کردیم، بلافاصله میره سراغ اجرای ادامه ی کد، و بعدش اون یکی process آمادست و منتظره یه value مونده تا از چنل بگیره. و goroutine بلاک میشه تا دریافتش کنه و بعد ادامه بده.

Select

The select statement is like a switch, but the decision is based on ability to communicate rather than equal values.

این امکان رو به ما میده که در لحظه به چندین channel گوش کنیم و رفتار برناممون رو کنترل کنیم. در واقع چک میکنه که کدوم یکی آماده برقراری ارتباط هست.

select { case v := <-ch1: fmt.Println(&quotchannel 1 sends&quot, v) case v := <-ch2: fmt.Println(&quotchannel 2 sends&quot, v) default: // optional fmt.Println(&quotneither channel was ready&quot) }

اگر هیچ کدوم از channel ها چیزی نفرستادن، default اجرا میشه. و اگر default رو نداشتیم و هیچ کدوم آماده برقراری ارتباط نبودن، صبر میکنه تا یکیشون آماده برقراری ارتباط بشن. و اگر هر دوتاشونم آماده بودن، سیستم به صورت رندوم یکیشونو انتخاب میکنه.

این خیلی شبیه switch statement هستش ولی برای ارتباط برقرار کردن...

بیاین یکم بیشتر concurrency یاد بگیریم

ولی قبلش یه اشاره ای به closure بکنم. ما میتونیم از closure ها برای اینکه یه سری عملیات توی بکگراند انجام بشن و لازم نباشه براشون صبر کنیم، استفاده کنیم. (این تنها کاربردشون نیست)

این closure ها چی ان؟ خیلی خلاصه بخوام بگم یه نوع فانکشن لوکال بی نام?!

خودتون میتونید بیشتر راجبش بخونید.

func(message string) { fmt.Println(message) }(&quotHello World!&quot) // output: Hello World!

یا مثالی که خود Rob Pike میزنه:

func Compose(f, g func(x float) float) func(x float) float { return func(x float) float { return f(g(x)) } } print(Compose(sin, cos)(0.5))

بگذریم...

go func() { // copy input to output for val := range input { output <- val } }()

اینجا ما دوتا channel داریم. و میخوایم input رو روی output کپی کنیم. و نمیخوایم تا تموم شدن کپی صبر کنیم.

پس قبل closure امون عبارت go رو می نویسیم، و بعد داخلش یه for داریم که قراره کار خوندن و نوشتن رو برامون انجام بده. پس روی input که یک channel هستش، توسط range حرکت میکنیم ، و هر value ای که داخلش وجود داره رو میریزیم داخل channel دوممون، یعنی output.

با استفاده از range ما میتونیم روی یک channel تا زمانی که داخلش value وجود داره حرکت کنیم و وقتی تموم شد خودش خارج میشه.

توی پارت بعدی قراره یه load balancer بنویسیم و مثال های واقعی تری داشته باشیم. پس حتما پارت بعدی رو بخونید ؛)

پارت اول

پارت دوم

پارت چهارم


gogolangconcurrencyparallelism
یه برنامه نویس بک اند | gopher
شاید از این پست‌ها خوشتان بیاید