سفر به اعماق channel ها

توسط mrbardia72
زمان خواندن 2 دقیقه
 سفر به اعماق channel ها

Channel

یک الگوی هست برای کارهای که نیاز به همزمانی دارد را همگام سازی می کند. در واقع گولنگ برای همزمانی به یک مدلی به نام CSP متکی هست که فلسفه اصلی ابن مدل همزمانی می باشد. که الگوی همگام سازی ار طریق کانال  را فراهم می کند.

What it takes to be a Channel

اینم یه نمونه کد خیلی ساده واسه تعریف کانال

func goRoutineA(a <-chan int) {
    val := <-a
    fmt.Println("goRoutineA received the data", val)
}
func main() {
    ch := make(chan int) //
    go goRoutineA(ch)
    time.Sleep(time.Second * 1)
}

وقتی شما یه کانال از نوع بافر شده میسازید دقیقا پشته صحنه این اتقاقات شکل پایین اتقاق می افته هربخش رو توی ادامه توضیح میدم

ch := make(chan int, 3)

hchan and waitq structs

وقتی شما یه کانال  به صورت زیر تعریف می کنید (به عنوان مثال)دقیقا پشته صحنه ساختاری به نام hchan ایجاد میشه براش(ساختار پایین)

ch := make(chan int, 2)

type hchan struct {
	qcount   uint
	dataqsiz uint
	buf      unsafe.Pointer
	elemsize uint16
	closed   uint32
	elemtype *_type
	sendx    uint
	recvx    uint
	recvq    waitq
	sendq    waitq
	lock mutex
}

type waitq struct {
	first *sudog
	last  *sudog
}

توضیحاتی درباره فیلد های این ساختار

qcount

کل دادهای که در صف قرار دارند

dataqsize

اندازه بافر رو مشخص می کند make(chan T, N)

elemsize

اندازه کانال مربوط به یک عنصر است.

buf

یک نوع مدل صف دایره ای است که داده های ما در آن  ذخیره می شود. (فقط برای کانال بافر استفاده می شود)

closed

وضعیت بسته بودن یا باز بودن کانال فعلی رو نشون میده اگر صفر باشد یعنی باز و اگر یک باشد یعنی  بسته هست کانال ما

sendx and recvx 

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

recvq and sendq

صف های انتظار recvq و sendq ، که برای ذخیره goroutines مسدود شده هنگام تلاش برای خواندن داده ها در کانال یا هنگام ارسال داده ها از کانال استفاده می شود.

lock

 برای قفل کردن کانال برای هر یک از عملیات خواندن و نوشتن ، ارسال و دریافت که این عملیات کاملاً متفاوتی باشد.

Important Field of sudog struct for channel

type sudog struct {

	g *g
	next *sudog
	prev *sudog
	elem unsafe.Pointer
	acquiretime int64
	releasetime int64
	ticket      uint32
	isSelect bool
	success bool
	parent   *sudog
	waitlink *sudog
	waittail *sudog
	c        *hchan
}

شمای یک گورتین را می دهد ساختار فوق

خوب توی این کد میخوایم بررسی کنیم که خط شماره ۲۲ چه روندی روی توی ساختار برامون ایجاد می کند(بررسی این خط کد روی ساختار)

Chan Struct at the runtime

این نما رو برای کد بالا در نظر بگیرید

توضیحات در زیر 

👇🏻👇🏻👇🏻👇🏻👇🏻

👆🏻توضیحات این  شمای و کدهای بالاش👆🏻

به خط هاب شماره 47 و 48 بالا توجه کنید. در شکل بالا 

به یاد داشته باشید توضیحات recvq از بالا  برای ذخیره گورتین های مسدود شده ای  که سعی در خواندن داده ها از کانال دارند استفاده می شود.

در کد مثال ما قبل از خط 22 دو goroutine (goroutineA و goroutineB) وجود دارد که سعی می کنند داده ها را از کانال ch بخوانند

از آنجا که قبل از خط 22 روی کانال ، هیچ داده ای در کانال قرار نداده ایم ، بنابراین هر دو goroutine مسدود شده برای عملیات دریافت هستند و در داخل ساختار sudog  و در recvq  وجود خواهند داشت مشخصاتشان.

Recvq structure

نمای از  recvq و sendq  که در اصل لیست پیوند  ای هستند

Send Opertaion Steps c <- x

انواع اصلی عملیات ارسال در کانال

sending on nil channel

sending on the closed channel.

A goroutine is blocked on the channel

Buffered Channel if there is currently space available for hchan.buf

The hchan.buf is full

sending on nil channel

اگر ما از کانال nil در حال ارسال هستیم ، goroutine فعلی عملکرد خود را متوقف می کند.

sending on the closed channel.

اگر بخواهیم داده ها را روی کانال بسته ارسال کنیم

A goroutine is blocked on the channel

اینجاست که ساختار recvq نقش مهمی را ایفا می کند.

اگر در recvq گوروتین وجود داشته باشد ،  گیرنده منتظر است و عملیات نوشتن  برای کانال را می تواند مستقیماً به آن گیرنده منتقل کند.

👇🏻پیاده سازی عملکرد ارسال در شکل زیر👇🏻

به خط 396 goready (gp، skip + 1) توجه کنید که Goroutine مسدود شده است در حالی که منتظر اطلاعات هست و با تماس با goready دوباره قابل اجرا شده است و برنامه ریز go مجدداً goroutine را اجرا می کند.

Buffered Channel if there is currently space available for hchan.buf

اگر در حال حاضر فضای خالی  در hchan.buf دسترس باشد: داده ها را در بافر خود قرار می دهد.

در واقع کار تابع chanbuf (c، i) به منطقه ای از حافظه  دسترسی پیدا می کند که قرار هست داده های را ارسال کتد

در این کد با مقایسه qcount و dataqsiz مشخص می کند که آیا hchan.buf فضای خالی دارد.

اگر داشت عنصر را کپی می کند  و در بافر قرار می دهد  و برای ارسال  sendx و qcount را تنظیم می کند.

The hchan.buf is full

این متد acquisSudog برای قرار دادن goroutine فعلی در حالت park می باشد و سپس goroutine را در  sendq کانال می فرستدد.

اشارگره ep داده ها را از باقر می خواند و در تابع Enqueue میفرستد.

یعنی بافر ما خالی نیست و دادهای برای ارسال از  این مکانیزم استفاده می کتتد

Send operation Summary

خلاصه روند ارسال در کانال ها

کل ساختار کانال  lock در گام اول.

در واقعه این recvq  داده ای را از صف مورد انتظار می گیرد و سپس داده را  مستقیماً به goroutine  می دهد.

اگر recvq خالی است ، بررسی می کند که  آیا بافر موجود است یا خیر. در صورت موجود بودن ، *کپی می کند  داده ها را از goroutine فعلی به بافر وارد می کند. در واقع - memmove () برای کپی کردن یک بلوک حافظه از یک مکان به مکان دیگر استفاده می شود.

  اگر بافر پر باشد ، عنصری که باید نوشته شود در ساختار goroutine در حال اجرا ذخیره می شود و goroutine فعلی از زمان اجرا در sendq مسدود و به حالت تعلیق در می آید.

اگر بافر پر باشد ، عنصری که باید نوشته شود ، در ساختار گوروتین در حال اجرا ذخیره می شود.

درک بهتری از شماره چهار از  خلاصه روند ارسال در کانال ها

دوباره بخوانید  شماره چهار رو ، به همین دلیل است که کانال غیر بافر در واقع "unbuffered" نامیده می شود حتی اگر ساختار "hchan" دارای عنصر "buf" باشد.  🔹اگر برای یک کانال غیر بافر  گیرنده ای وجود نداشته باشد و بخواهید داده ارسال کنید ، داده ها در elem ساختار sudog ذخیره می شوند. (برای کانال بافر نیز صدق می کند).

بگذارید مثالی بزنم تا جزئیات بیشتر نکته شماره 4 را روشن کنید. فرض کنید کد زیر را داریم.👇🏻👇🏻👇🏻👇🏻

🎯ساختار زمان اجرای chan c2 در خط شماره 10 چگونه خواهد بود؟🎯

runtime unbuffered

از آنجا که goroutineA سعی داشت مقدار را به کانال c2 ارسال کند و هیچ گیرنده ای آماده وجود ندارد ، بنابراین goroutineA به لیست sendq کانال c2 اضافه می شود و با مسدود شدن اصطلاحا پارک می شود. بعدش برای تأیید می تواند ساختار زمان اجرای sendq مسدود کننده را بررسی کنیم.

🎯به یاد داشته باشید تمام انتقال مقادیر کانال ها در حال انتقال با کپی مقدار اتفاق می افتد.🎯

🎯خروجی کد فوق🎯

&{Ankur 25}

modifyUser Received Value &{Ankur Anand 100}

printUser goRoutine called &{Ankur 25}

&{Anand 100}

🎯شمای عملکرد کد فوق- با توضیحات که داده شده بسیار واضح هست🎯

select channel Example

Multiplexing on multiple channel.

scase structure

هر scase در آرایه scases یک ساختار است که شامل نوع عملکرد فعلی  کانال می باشد که روی آن کار می کند. منظور از عملگرد همان حالت قفل بودن یا نبود کانال می باشد

poll order

این یه روش نظرسنجی مانند هست که چک کند وضعیت هر کانال را که کدام الان آماده برای ارسال داده می باشد یعنی بافر خالی الان دارد که بتوان داده ای در آن کانال ارسال کرد

park goroutine in select case

توی ااین شبه کد اگر هیج داده ای روی کانال نباشد وضعیت گورتین به حالت انتظار برای دریافت می رود