کانال Golang بسیار ساده شده! – انجمن DEV

Summarize this content to 400 words in Persian Lang
TL; DR
این مقاله کانالهای Go را توضیح میدهد که ارتباط امن بین گوروتینها را امکانپذیر میسازد. نحوه ایجاد، ارسال و دریافت داده ها از طریق کانال ها را پوشش می دهد و بین انواع بافر و بافر تمایز قائل می شود. بر اهمیت بستن کانال ها برای جلوگیری از بن بست ها و بهبود مدیریت منابع تاکید می کند. در نهایت به معرفی select بیانیه ای برای مدیریت کارآمد عملیات چند کانال.
فهرست مطالب
معرفی Go Channels
ایجاد یک کانال
ارسال داده ها
در حال دریافت اطلاعات
انواع کانال در Go
کانال های بافر نشده
کانال های بافر شده
بستن یک کانال
قطعه کد بدون بستن کانال
خروجی و خطای مورد انتظار
قطعه کد با بستن کانال
با استفاده از بیانیه انتخاب کنید
نمونه ای از انتخاب با کانال ها
سوالات متداول در انتخاب
اطمینان از ارسال پیام از هر دو کانال با استفاده از WaitGroup
نمونه ای از استفاده از WaitGroup
نتیجه
معرفی Go Channels
Go یا Golang یک زبان برنامه نویسی قدرتمند است که برای سادگی و کارایی طراحی شده است. یکی از ویژگی های برجسته آن مفهوم کانال است که ارتباط بین گوروتین ها را تسهیل می کند. کانال ها امکان تبادل و همگام سازی امن داده ها را فراهم می کنند و برنامه نویسی همزمان را آسان تر و قابل مدیریت تر می کنند.
در این مقاله، کانالها را در Go بررسی میکنیم، ایجاد، انتقال داده و دریافت آنها را بررسی میکنیم. این به شما کمک می کند تا درک کنید که چگونه از کانال ها به طور موثر در برنامه های خود استفاده کنید.
ایجاد یک کانال
برای ایجاد یک کانال در Go، از make تابع. در اینجا یک قطعه کد ساده است که نحوه ایجاد یک کانال را نشان می دهد:
package main
import “fmt”
func main() {
// Create a channel of type int
ch := make(chan int)
fmt.Println(“Channel created:”, ch)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در این مثال یک کانال ایجاد می کنیم ch که می تواند اعداد صحیح ارسال و دریافت کند. کانال به طور پیش فرض بافر نشده است، به این معنی که تا زمانی که فرستنده و گیرنده هر دو آماده شوند، مسدود می شود.
وقتی کد Go ارائه شده را اجرا می کنید، خروجی چیزی شبیه به این خواهد بود:
Channel created: 0xc000102060
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
توضیح
ایجاد کانال:
خط ch := make(chan int) یک کانال جدید از نوع ایجاد می کند int. از این کانال می توان برای ارسال و دریافت مقادیر صحیح استفاده کرد.
آدرس کانال:
خروجی 0xc000102060 آدرس حافظه کانال است. در Go، هنگامی که یک کانال را چاپ می کنید، نمایش داخلی خود را نشان می دهد که شامل آدرس آن در حافظه است.
این آدرس محل ذخیره کانال در حافظه را نشان می دهد، اما هیچ اطلاعاتی در مورد وضعیت یا محتوای کانال ارائه نمی دهد.
ارسال داده ها
پس از ایجاد یک کانال، می توانید با استفاده از کانال، داده ها را به آن ارسال کنید operator. Here’s how you can send data to the channel:
go func() {
ch 42 // Sending the value 42 to the channel
}()
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در این قطعه، یک گوروتین جدید را شروع می کنیم که مقدار عدد صحیح را ارسال می کند 42 وارد کانال ch. این عملیات ناهمزمان به برنامه اصلی اجازه می دهد تا در حین ارسال مقدار به اجرا ادامه دهد.
در حال دریافت اطلاعات
برای دریافت داده از یک کانال، از operator. Here’s how to read from the channel:
value := ch // Receiving data from the channel
fmt.Println(“Received value:”, value)
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در این مثال از کانال می خوانیم ch و مقدار دریافتی را در متغیر ذخیره کنید value. برنامه در این خط مسدود می شود تا زمانی که یک مقدار برای خواندن در دسترس باشد.
انواع کانال در Go
در Go، کانال ها را می توان در درجه اول به دو نوع دسته بندی کرد: کانال های بافر و بافر. درک این انواع برای برنامه نویسی همزمان موثر ضروری است.
1. کانال های بافر نشده
کانال بدون بافر ساده ترین نوع است. هیچ ظرفیتی برای نگهداری داده ها ندارد. این امر مستلزم آن است که فرستنده و گیرنده همزمان آماده باشند.
مشخصات:
رفتار مسدود کردن: بلوک عملیات ارسال و دریافت تا زمانی که هر دو طرف آماده باشند. این هماهنگی بین گوروتین ها را تضمین می کند.
استفاده از مورد: بهترین حالت برای سناریوهایی که می خواهید همگام سازی دقیق داشته باشید یا زمانی که ارتباط کم است.
مثال:
ch := make(chan int) // Unbuffered channel
go func() {
ch 1 // Sends data; blocks until received
}()
value := ch // Receives data; blocks until sent
fmt.Println(“Received:”, value)
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
2. کانال های بافر
کانالهای بافر به شما امکان میدهند یک ظرفیت را مشخص کنید، به این معنی که میتوانند تعداد محدودی از مقادیر را قبل از مسدود کردن ارسالها نگه دارند.
مشخصات:
ارسال های غیر مسدود کننده: عملیات ارسال فقط زمانی مسدود می شود که بافر پر باشد. این به انعطاف پذیری بیشتری اجازه می دهد و می تواند عملکرد را در سناریوهای خاص بهبود بخشد.
استفاده از مورد: زمانی مفید است که می خواهید فرستنده و گیرنده را جدا کنید و به فرستنده اجازه می دهد تا زمانی که بافر پر شود به اجرا ادامه دهد.
مثال:
ch := make(chan int, 2) // Buffered channel with capacity of 2
ch 1 // Does not block
ch 2 // Does not block
// ch
fmt.Println(“Values sent to buffered channel.”)
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بستن کانال چیست؟
در Go، بستن یک کانال عملیاتی است که نشان می دهد هیچ مقدار دیگری در آن کانال ارسال نخواهد شد. این کار با استفاده از close(channel) تابع. پس از بسته شدن یک کانال، نمی توان آن را دوباره باز کرد یا دوباره به آن فرستاد.
چرا باید کانال ها را ببندیم؟
تکمیل سیگنال: بستن یک کانال به گوروتین دریافت کننده نشان می دهد که هیچ مقدار دیگری ارسال نخواهد شد. این به گیرنده اجازه می دهد تا بداند چه زمانی منتظر پیام های جدید نیست.
جلوگیری از بن بست: اگر یک گوروتین از کانالی میخواند که هرگز بسته نمیشود، میتواند منجر به بنبست شود که در آن برنامه به طور نامحدود متوقف میشود و منتظر دادههای بیشتری است که هرگز به دست نمیآیند.
مدیریت منابع: بستن کانال ها به مدیریت موثر منابع کمک می کند، زیرا به جمع کننده زباله اجازه می دهد تا حافظه مرتبط با کانال را پس از عدم استفاده از آن بازیابی کند.
کنترل تکرار: هنگام استفاده از a for range حلقه برای خواندن از یک کانال، بستن کانال راهی تمیز برای خروج از حلقه پس از پردازش همه پیام ها فراهم می کند.
در این بخش، یک قطعه کد Go را بررسی می کنیم که استفاده از کانال های بافر نشده را نشان می دهد. ما رفتار کد را با و بدون بستن کانال و همچنین پیامدهای هر رویکرد تجزیه و تحلیل خواهیم کرد.
قطعه کد بدون بستن کانال
در اینجا قطعه کد اصلی بدون آن است close بیانیه:
package main
import (
“fmt”
)
func main() {
messages := make(chan string)
go func() {
messages “Message 1”
messages “Message 2”
messages “Message 3”
// close(messages) // This line is removed
}()
for msg := range messages {
fmt.Println(msg)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی و خطای مورد انتظار
fatal error: all goroutines are asleep – deadlock!
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هنگامی که این کد را اجرا می کنید، کامپایل و اجرا می شود، اما بدون ایجاد خروجی مورد انتظار، به طور نامحدود هنگ می کند. دلیل آن این است که for msg := range messages حلقه همچنان منتظر پیام های بیشتر است و از آنجایی که کانال هرگز بسته نمی شود، حلقه راهی برای دانستن زمان پایان یافتن ندارد. این منجر به یک وضعیت بن بست می شود و باعث می شود برنامه متوقف شود.
قطعه کد با بستن کانال
حالا، بیایید اضافه کنیم close بیانیه بازگشت به کد:
package main
import (
“fmt”
)
func main() {
messages := make(chan string)
go func() {
messages “Message 1”
messages “Message 2”
messages “Message 3”
close(messages) // Close the channel when done
}()
for msg := range messages {
fmt.Println(msg)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی مورد انتظار
با close عبارت شامل، خروجی این کد خواهد بود:
Message 1
Message 2
Message 3
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
توضیح رفتار بسته
در این نسخه از کد:
را close(messages) بیانیه نشان می دهد که هیچ پیام دیگری در این مورد ارسال نخواهد شد messages کانال
را for msg := range messages حلقه اکنون می تواند پس از دریافت همه پیام ها به خوبی خاتمه یابد.
بستن کانال این امکان را به شما می دهد range حلقه برای خروج پس از پردازش همه پیام ها، جلوگیری از هر گونه وضعیت بن بست.
بازم اگه کانال رو نبندید چی؟
بیایید سناریویی را تصور کنیم که در آن کانالهای Go مانند افراد در یک مکالمه هستند.
صحنه: یک کافی شاپ
شخصیت ها:
آلیس: همیشه مشتاق به اشتراک گذاشتن ایده ها.
باب: جواب دادن زمان زیادی می برد.
گفتگو:
آلیس: “هی باب، آیا در مورد پروژه جدید چیزی شنیدی؟ ما نیاز به طوفان فکری داریم!”
باب قهوهاش را مینوشد و بیپروا خیره میشود. مکالمه متوقف می شود.
آلیس: “سلام؟ اونجا هستی؟”
باب به بالا نگاه می کند، هنوز در حال پردازش است.
باب: “اوه، ببخشید! داشتم… اوه… فکر می کردم.”
دقیقه ها می گذرد. آلیس شروع به تعجب می کند که آیا باب هنوز در چت است یا خیر.
آلیس: “آیا باید به صحبت کردن ادامه دهم یا فقط منتظر سیگنال باشم؟”
باب بالاخره پاسخ داد، اما کاملاً خارج از موضوع است.
باب: “آیا می دانستید که تنبل ها می توانند نفس خود را بیشتر از دلفین ها حبس کنند؟”
آلیس کف دست.
آلیس: “عالی، اما پروژه چطور؟”
باب شانه هایش را بالا می اندازد و دوباره در فکر فرو می رود. کافی شاپ به طرز عجیبی ساکت می شود.
آلیس: “این مکالمه تا به حال تمام می شود، یا من فقط برای همیشه اینجا خواهم بود؟”
باب که اکنون مجذوب باریستا شده است، چیزی در مورد دانه های قهوه زمزمه می کند.
آلیس: “این مانند یک کانال Go است که هرگز بسته نمی شود! احساس می کنم در یک حلقه بی نهایت گیر کرده ام!”
باب بالاخره به عقب نگاه می کند و پوزخند می زند.
باب: “پس… در مورد اون تنبل ها؟”
اخلاق داستان: گاهی اوقات، وقتی کانالها (یا مکالمات) بسته نمیشوند، در نهایت با موضوعات بیپایان و بدون راهحل مواجه میشوید – درست مانند یک چت که برای همیشه و بدون نتیجهگیری ادامه دارد!
برو کانال ها و select بیانیه
مدل همزمانی Go حول گوروتین ها و کانال ها ساخته شده است که ارتباط بین فرآیندهای همزمان را تسهیل می کند. را select بیانیه برای مدیریت موثر عملیات کانال های متعدد حیاتی است.
استفاده كردن select با کانال ها
در اینجا یک مثال از استفاده است select با کانال های:
package main
import (
“fmt”
“time”
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 “Result from channel 1”
}()
go func() {
time.Sleep(2 * time.Second)
ch2 “Result from channel 2”
}()
select {
case msg1 := ch1:
fmt.Println(msg1)
case msg2 := ch2:
fmt.Println(msg2)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی با select:
Result from channel 1
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
چرا فقط یک خروجی چاپ می کند؟
در برو، select بیانیه یک ساختار قدرتمند است که برای مدیریت عملیات چند کانال استفاده می شود. هنگام کار با کانال ها، ممکن است تعجب کنید که چرا یک برنامه زمانی که چندین کانال درگیر است، تنها یک خروجی را چاپ می کند. بیایید این مفهوم را از طریق یک مثال ساده بررسی کنیم.
نمای کلی سناریو
برنامه ای را در نظر بگیرید که شامل دو کانال است: ch1 و ch2. هر کانال پس از تاخیر یک پیام دریافت می کند، اما در پایان فقط یک پیام چاپ می شود. ممکن است بپرسید “چرا فقط یک خروجی چاپ می کند؟”
زمان بندی و همزمانی
راه اندازی کانال: هر دو ch1 و ch2 برای مدیریت پیام های رشته ای ایجاد می شوند.
گوروتین ها:
یک گوروتین پیامی به ch1 بعد از 1 ثانیه تاخیر
گوروتین دیگری پیامی به ch2 بعد از 2 ثانیه تاخیر
بیانیه را انتخاب کنید: select بیانیه به پیام های هر دو کانال گوش می دهد. تا زمانی که یکی از کانال ها برای ارسال پیام آماده شود مسدود می شود.
جریان اجرا
هنگامی که برنامه اجرا می شود، منتظر هر یک می شود ch1 یا ch2 برای ارسال پیام
بعد از 1 ثانیه، ch1 آماده است، اجازه می دهد select بیانیه برای اجرای پرونده ch1.
مهمتر از همه، select فقط می تواند یک مورد را در یک زمان اجرا کند. پس از انتخاب یک مورد، از آن خارج می شود select مسدود کردن.
سوالات متداول در select
س: آیا می توان منتظر ورود همه کانال ها بود؟ select برای چاپ همه خروجی ها؟پاسخ: نه، select بیانیه برای رسیدگی به یک مورد در یک زمان طراحی شده است. برای منتظر ماندن چندین کانال و چاپ همه خروجی ها، باید از یک حلقه یا گروه انتظار استفاده کنید.
س: اگر هر دو کانال همزمان آماده باشند چه اتفاقی می افتد؟پاسخ: اگر هر دو کانال به طور همزمان آماده باشند، Go به صورت تصادفی یکی را برای پردازش انتخاب می کند، بنابراین خروجی ممکن است بین اجراها متفاوت باشد.
س: آیا می توانم با وقفه های زمانی کنار بیایم؟ select?پاسخ: بله، می توانید یک مورد مهلت زمانی را در آن قرار دهید select بیانیه، به شما امکان می دهد مدت زمان انتظار برای پیام را مشخص کنید.
س: چگونه می توانم مطمئن شوم که از هر دو کانال پیام دریافت می کنم؟پاسخ: برای دریافت پیام از هر دو کانال، از یک حلقه با a استفاده کنید select عبارت داخل آن، یا از a استفاده کنید sync.WaitGroup منتظر بمانند تا چندین گوروتین وظایف خود را تکمیل کنند.
اطمینان از پیامهای هر دو کانال با استفاده از WaitGroup in Go
برای اطمینان از دریافت پیام از هر دو کانال در Go، می توانید از a استفاده کنید sync.WaitGroup. این به شما امکان می دهد قبل از ادامه منتظر بمانید تا چندین گوروتین کامل شود.
در اینجا یک مثال است:
package main
import (
“fmt”
“sync”
“time”
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
var wg sync.WaitGroup
// Start goroutine for channel 1
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(1 * time.Second)
ch1 “Result from channel 1”
}()
// Start goroutine for channel 2
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(2 * time.Second)
ch2 “Result from channel 2”
}()
// Wait for both goroutines to finish
go func() {
wg.Wait()
close(ch1)
close(ch2)
}()
// Collect results from both channels
results := []string{}
for i := 0; i 2; i++ {
select {
case msg1 := ch1:
results = append(results, msg1)
case msg2 := ch2:
results = append(results, msg2)
}
}
// Print all results
for _, result := range results {
fmt.Println(result)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی
Result from channel 1
Result from channel 2
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
توضیح
کانال ها و گروه انتظار: دو کانال ch1 و ch2، بوجود آمدند. آ sync.WaitGroup برای صبر کردن تا پایان هر دو گوروتین استفاده می شود.
گوروتین ها: هر گوروتین پس از تأخیر پیامی را به کانال خود ارسال می کند. را wg.Done() به تکمیل سیگنال فراخوانی می شود.
بستن کانال ها: پس از انجام تمامی برنامه ها، کانال ها بسته می شوند تا از ارسال بیشتر جلوگیری شود.
جمع آوری نتایج: یک حلقه با a select بیانیه برای دریافت پیام از هر دو کانال استفاده می شود تا زمانی که هر دو پیام جمع آوری شوند.
خروجی نهایی: پیام های جمع آوری شده چاپ می شوند.
این روش تضمین می کند که قبل از ادامه منتظر بمانید تا هر دو کانال پیام های خود را ارسال کنند.
اگر علاقه مند به یادگیری بیشتر در مورد استفاده هستید sync.WaitGroup در Go، این مقاله در مورد همزمانی را بررسی کنید: همزمانی Golang: یک سواری سرگرم کننده و سریع.
نمونه دنیای واقعی
بیایید دو نسخه یک برنامه را از نظر ساختار، اجرا و زمان بندی با هم مقایسه کنیم.
نسخه اجرای متوالی
این نسخه کارها را به صورت متوالی و یکی پس از دیگری پردازش می کند.
package main
import (
“fmt”
“time”
)
func worker(id int, job int) string {
time.Sleep(time.Second) // Simulate work
return fmt.Sprintf(“Worker %d completed job %d”, id, job)
}
func main() {
start := time.Now()
results := make([]string, 5)
for j := 1; j 5; j++ {
results[j-1] = worker(1, j) // Call the worker function directly
}
for _, result := range results {
fmt.Println(result)
}
duration := time.Since(start)
fmt.Printf(“It took %s to execute!”, duration)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی:
Worker 1 completed job 1
Worker 1 completed job 2
Worker 1 completed job 3
Worker 1 completed job 4
Worker 1 completed job 5
It took 5.048703s to execute!
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
نسخه اجرای همزمان
این نسخه کارها را همزمان با استفاده از گوروتین ها و کانال ها پردازش می کند.
package main
import (
“fmt”
“time”
)
func worker(id int, jobs chan int, results chan string) {
for job := range jobs {
time.Sleep(time.Second) // Simulate work
results fmt.Sprintf(“Worker %d completed job %d”, id, job)
}
}
func main() {
start := time.Now()
jobs := make(chan int, 5)
results := make(chan string)
for w := 1; w 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j 5; j++ {
jobs j
}
close(jobs)
for a := 1; a 5; a++ {
fmt.Println(results)
}
duration := time.Since(start)
fmt.Printf(“It took %s to execute!”, duration)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
خروجی:
Worker 1 completed job 1
Worker 2 completed job 2
Worker 3 completed job 3
Worker 1 completed job 4
Worker 2 completed job 5
It took 2.0227664s to execute!
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
مقایسه
ساختار:
نسخه متوالی: تابع کارگر را مستقیماً در یک حلقه فراخوانی می کند. بدون همزمانی
نسخه همزمان: از گوروتین ها برای اجرای همزمان چندین عملکرد کارگری و کانال هایی برای توزیع کار و جمع آوری نتایج استفاده می کند.
اجرا:
نسخه متوالی: هر کار یکی پس از دیگری پردازش می شود و برای هر کار 1 ثانیه طول می کشد، که منجر به کل زمان اجرا تقریباً برابر با تعداد کارها می شود (5 ثانیه برای 5 کار).
نسخه همزمان: چندین کارگر (در این مورد 3 نفر) کارها را به طور همزمان پردازش می کنند و زمان کل اجرا را به میزان قابل توجهی کاهش می دهند. مشاغل بین کارگران توزیع می شود و نتایج از طریق کانال ها جمع آوری می شود.
زمان سنجی:
نسخه متوالی: تقریباً 5.048703 ثانیه طول کشید.
نسخه همزمان: تقریباً 2.0227664 ثانیه طول کشید.
نسخه همزمان به طور قابل توجهی سریعتر است زیرا از اجرای موازی استفاده می کند و امکان پردازش همزمان چندین کار را فراهم می کند. این کار به جای جمعبندی زمان برای هر کار مانند نسخه متوالی، کل زمان اجرا را به حدود زمانی که طول میکشد برای تکمیل طولانیترین کار، تقسیم بر تعداد کارگران کاهش میدهد.
مراجع اسناد رسمی
برو مستندات – گوروتین هاگوروتین ها
برو اسناد – کانال هاکانال ها
Go Blog – Concurrency in Goهمزمانی در Go
Go Documentation – بیانیه انتخاب کنیدبیانیه را انتخاب کنید
برو تور – کانال هاTour of Go: کانال ها
نتیجه
به طور خلاصه، مقاله مروری واضح و ساده از کانالها در Go ارائه میکند و بر نقش آنها در تسهیل ارتباط ایمن بین گوروتینها تأکید میکند. با توضیح مفاهیم کانال های بافر نشده و بافر، این مقاله رفتارهای متمایز و موارد استفاده مناسب آنها را برجسته می کند. علاوه بر این، بر اهمیت بستن کانال ها برای جلوگیری از بن بست و اطمینان از مدیریت کارآمد منابع تأکید می کند. این مقاله با مثالهای کد عملی و تشابههای مرتبط، خوانندگان را با درک اساسی از نحوه استفاده مؤثر از کانالها در برنامههای Go خود مجهز میکند و راه را برای برنامهنویسی همزمان قویتر هموار میکند.
TL; DR
این مقاله کانالهای Go را توضیح میدهد که ارتباط امن بین گوروتینها را امکانپذیر میسازد. نحوه ایجاد، ارسال و دریافت داده ها از طریق کانال ها را پوشش می دهد و بین انواع بافر و بافر تمایز قائل می شود. بر اهمیت بستن کانال ها برای جلوگیری از بن بست ها و بهبود مدیریت منابع تاکید می کند. در نهایت به معرفی select
بیانیه ای برای مدیریت کارآمد عملیات چند کانال.
فهرست مطالب
- معرفی Go Channels
- ایجاد یک کانال
- ارسال داده ها
- در حال دریافت اطلاعات
-
انواع کانال در Go
- کانال های بافر نشده
- کانال های بافر شده
- بستن یک کانال
-
قطعه کد بدون بستن کانال
- خروجی و خطای مورد انتظار
- قطعه کد با بستن کانال
-
با استفاده از بیانیه انتخاب کنید
- نمونه ای از انتخاب با کانال ها
- سوالات متداول در انتخاب
-
اطمینان از ارسال پیام از هر دو کانال با استفاده از WaitGroup
- نمونه ای از استفاده از WaitGroup
- نتیجه
معرفی Go Channels
Go یا Golang یک زبان برنامه نویسی قدرتمند است که برای سادگی و کارایی طراحی شده است. یکی از ویژگی های برجسته آن مفهوم کانال است که ارتباط بین گوروتین ها را تسهیل می کند. کانال ها امکان تبادل و همگام سازی امن داده ها را فراهم می کنند و برنامه نویسی همزمان را آسان تر و قابل مدیریت تر می کنند.
در این مقاله، کانالها را در Go بررسی میکنیم، ایجاد، انتقال داده و دریافت آنها را بررسی میکنیم. این به شما کمک می کند تا درک کنید که چگونه از کانال ها به طور موثر در برنامه های خود استفاده کنید.
ایجاد یک کانال
برای ایجاد یک کانال در Go، از make
تابع. در اینجا یک قطعه کد ساده است که نحوه ایجاد یک کانال را نشان می دهد:
package main
import "fmt"
func main() {
// Create a channel of type int
ch := make(chan int)
fmt.Println("Channel created:", ch)
}
در این مثال یک کانال ایجاد می کنیم ch
که می تواند اعداد صحیح ارسال و دریافت کند. کانال به طور پیش فرض بافر نشده است، به این معنی که تا زمانی که فرستنده و گیرنده هر دو آماده شوند، مسدود می شود.
وقتی کد Go ارائه شده را اجرا می کنید، خروجی چیزی شبیه به این خواهد بود:
Channel created: 0xc000102060
توضیح
-
ایجاد کانال:
- خط
ch := make(chan int)
یک کانال جدید از نوع ایجاد می کندint
. از این کانال می توان برای ارسال و دریافت مقادیر صحیح استفاده کرد.
- خط
-
آدرس کانال:
- خروجی
0xc000102060
آدرس حافظه کانال است. در Go، هنگامی که یک کانال را چاپ می کنید، نمایش داخلی خود را نشان می دهد که شامل آدرس آن در حافظه است. - این آدرس محل ذخیره کانال در حافظه را نشان می دهد، اما هیچ اطلاعاتی در مورد وضعیت یا محتوای کانال ارائه نمی دهد.
- خروجی
ارسال داده ها
پس از ایجاد یک کانال، می توانید با استفاده از کانال، داده ها را به آن ارسال کنید operator. Here’s how you can send data to the channel:
go func() {
ch 42 // Sending the value 42 to the channel
}()
در این قطعه، یک گوروتین جدید را شروع می کنیم که مقدار عدد صحیح را ارسال می کند 42
وارد کانال ch
. این عملیات ناهمزمان به برنامه اصلی اجازه می دهد تا در حین ارسال مقدار به اجرا ادامه دهد.
در حال دریافت اطلاعات
برای دریافت داده از یک کانال، از operator. Here’s how to read from the channel:
value := ch // Receiving data from the channel
fmt.Println("Received value:", value)
در این مثال از کانال می خوانیم ch
و مقدار دریافتی را در متغیر ذخیره کنید value
. برنامه در این خط مسدود می شود تا زمانی که یک مقدار برای خواندن در دسترس باشد.
انواع کانال در Go
در Go، کانال ها را می توان در درجه اول به دو نوع دسته بندی کرد: کانال های بافر و بافر. درک این انواع برای برنامه نویسی همزمان موثر ضروری است.
1. کانال های بافر نشده
کانال بدون بافر ساده ترین نوع است. هیچ ظرفیتی برای نگهداری داده ها ندارد. این امر مستلزم آن است که فرستنده و گیرنده همزمان آماده باشند.
مشخصات:
- رفتار مسدود کردن: بلوک عملیات ارسال و دریافت تا زمانی که هر دو طرف آماده باشند. این هماهنگی بین گوروتین ها را تضمین می کند.
- استفاده از مورد: بهترین حالت برای سناریوهایی که می خواهید همگام سازی دقیق داشته باشید یا زمانی که ارتباط کم است.
مثال:
ch := make(chan int) // Unbuffered channel
go func() {
ch 1 // Sends data; blocks until received
}()
value := ch // Receives data; blocks until sent
fmt.Println("Received:", value)
2. کانال های بافر
کانالهای بافر به شما امکان میدهند یک ظرفیت را مشخص کنید، به این معنی که میتوانند تعداد محدودی از مقادیر را قبل از مسدود کردن ارسالها نگه دارند.
مشخصات:
- ارسال های غیر مسدود کننده: عملیات ارسال فقط زمانی مسدود می شود که بافر پر باشد. این به انعطاف پذیری بیشتری اجازه می دهد و می تواند عملکرد را در سناریوهای خاص بهبود بخشد.
- استفاده از مورد: زمانی مفید است که می خواهید فرستنده و گیرنده را جدا کنید و به فرستنده اجازه می دهد تا زمانی که بافر پر شود به اجرا ادامه دهد.
مثال:
ch := make(chan int, 2) // Buffered channel with capacity of 2
ch 1 // Does not block
ch 2 // Does not block
// ch
fmt.Println("Values sent to buffered channel.")
بستن کانال چیست؟
در Go، بستن یک کانال عملیاتی است که نشان می دهد هیچ مقدار دیگری در آن کانال ارسال نخواهد شد. این کار با استفاده از close(channel)
تابع. پس از بسته شدن یک کانال، نمی توان آن را دوباره باز کرد یا دوباره به آن فرستاد.
چرا باید کانال ها را ببندیم؟
-
تکمیل سیگنال: بستن یک کانال به گوروتین دریافت کننده نشان می دهد که هیچ مقدار دیگری ارسال نخواهد شد. این به گیرنده اجازه می دهد تا بداند چه زمانی منتظر پیام های جدید نیست.
-
جلوگیری از بن بست: اگر یک گوروتین از کانالی میخواند که هرگز بسته نمیشود، میتواند منجر به بنبست شود که در آن برنامه به طور نامحدود متوقف میشود و منتظر دادههای بیشتری است که هرگز به دست نمیآیند.
-
مدیریت منابع: بستن کانال ها به مدیریت موثر منابع کمک می کند، زیرا به جمع کننده زباله اجازه می دهد تا حافظه مرتبط با کانال را پس از عدم استفاده از آن بازیابی کند.
-
کنترل تکرار: هنگام استفاده از a
for range
حلقه برای خواندن از یک کانال، بستن کانال راهی تمیز برای خروج از حلقه پس از پردازش همه پیام ها فراهم می کند.
در این بخش، یک قطعه کد Go را بررسی می کنیم که استفاده از کانال های بافر نشده را نشان می دهد. ما رفتار کد را با و بدون بستن کانال و همچنین پیامدهای هر رویکرد تجزیه و تحلیل خواهیم کرد.
قطعه کد بدون بستن کانال
در اینجا قطعه کد اصلی بدون آن است close
بیانیه:
package main
import (
"fmt"
)
func main() {
messages := make(chan string)
go func() {
messages "Message 1"
messages "Message 2"
messages "Message 3"
// close(messages) // This line is removed
}()
for msg := range messages {
fmt.Println(msg)
}
}
خروجی و خطای مورد انتظار
fatal error: all goroutines are asleep - deadlock!
هنگامی که این کد را اجرا می کنید، کامپایل و اجرا می شود، اما بدون ایجاد خروجی مورد انتظار، به طور نامحدود هنگ می کند. دلیل آن این است که for msg := range messages
حلقه همچنان منتظر پیام های بیشتر است و از آنجایی که کانال هرگز بسته نمی شود، حلقه راهی برای دانستن زمان پایان یافتن ندارد. این منجر به یک وضعیت بن بست می شود و باعث می شود برنامه متوقف شود.
قطعه کد با بستن کانال
حالا، بیایید اضافه کنیم close
بیانیه بازگشت به کد:
package main
import (
"fmt"
)
func main() {
messages := make(chan string)
go func() {
messages "Message 1"
messages "Message 2"
messages "Message 3"
close(messages) // Close the channel when done
}()
for msg := range messages {
fmt.Println(msg)
}
}
خروجی مورد انتظار
با close
عبارت شامل، خروجی این کد خواهد بود:
Message 1
Message 2
Message 3
توضیح رفتار بسته
در این نسخه از کد:
- را
close(messages)
بیانیه نشان می دهد که هیچ پیام دیگری در این مورد ارسال نخواهد شدmessages
کانال - را
for msg := range messages
حلقه اکنون می تواند پس از دریافت همه پیام ها به خوبی خاتمه یابد. - بستن کانال این امکان را به شما می دهد
range
حلقه برای خروج پس از پردازش همه پیام ها، جلوگیری از هر گونه وضعیت بن بست.
بازم اگه کانال رو نبندید چی؟
بیایید سناریویی را تصور کنیم که در آن کانالهای Go مانند افراد در یک مکالمه هستند.
صحنه: یک کافی شاپ
شخصیت ها:
- آلیس: همیشه مشتاق به اشتراک گذاشتن ایده ها.
- باب: جواب دادن زمان زیادی می برد.
گفتگو:
آلیس: “هی باب، آیا در مورد پروژه جدید چیزی شنیدی؟ ما نیاز به طوفان فکری داریم!”
باب قهوهاش را مینوشد و بیپروا خیره میشود. مکالمه متوقف می شود.
آلیس: “سلام؟ اونجا هستی؟”
باب به بالا نگاه می کند، هنوز در حال پردازش است.
باب: “اوه، ببخشید! داشتم… اوه… فکر می کردم.”
دقیقه ها می گذرد. آلیس شروع به تعجب می کند که آیا باب هنوز در چت است یا خیر.
آلیس: “آیا باید به صحبت کردن ادامه دهم یا فقط منتظر سیگنال باشم؟”
باب بالاخره پاسخ داد، اما کاملاً خارج از موضوع است.
باب: “آیا می دانستید که تنبل ها می توانند نفس خود را بیشتر از دلفین ها حبس کنند؟”
آلیس کف دست.
آلیس: “عالی، اما پروژه چطور؟”
باب شانه هایش را بالا می اندازد و دوباره در فکر فرو می رود. کافی شاپ به طرز عجیبی ساکت می شود.
آلیس: “این مکالمه تا به حال تمام می شود، یا من فقط برای همیشه اینجا خواهم بود؟”
باب که اکنون مجذوب باریستا شده است، چیزی در مورد دانه های قهوه زمزمه می کند.
آلیس: “این مانند یک کانال Go است که هرگز بسته نمی شود! احساس می کنم در یک حلقه بی نهایت گیر کرده ام!”
باب بالاخره به عقب نگاه می کند و پوزخند می زند.
باب: “پس… در مورد اون تنبل ها؟”
اخلاق داستان: گاهی اوقات، وقتی کانالها (یا مکالمات) بسته نمیشوند، در نهایت با موضوعات بیپایان و بدون راهحل مواجه میشوید – درست مانند یک چت که برای همیشه و بدون نتیجهگیری ادامه دارد!
برو کانال ها و select
بیانیه
مدل همزمانی Go حول گوروتین ها و کانال ها ساخته شده است که ارتباط بین فرآیندهای همزمان را تسهیل می کند. را select
بیانیه برای مدیریت موثر عملیات کانال های متعدد حیاتی است.
استفاده كردن select
با کانال ها
در اینجا یک مثال از استفاده است select
با کانال های:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 "Result from channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 "Result from channel 2"
}()
select {
case msg1 := ch1:
fmt.Println(msg1)
case msg2 := ch2:
fmt.Println(msg2)
}
}
خروجی با select
:
Result from channel 1
چرا فقط یک خروجی چاپ می کند؟
در برو، select
بیانیه یک ساختار قدرتمند است که برای مدیریت عملیات چند کانال استفاده می شود. هنگام کار با کانال ها، ممکن است تعجب کنید که چرا یک برنامه زمانی که چندین کانال درگیر است، تنها یک خروجی را چاپ می کند. بیایید این مفهوم را از طریق یک مثال ساده بررسی کنیم.
نمای کلی سناریو
برنامه ای را در نظر بگیرید که شامل دو کانال است: ch1
و ch2
. هر کانال پس از تاخیر یک پیام دریافت می کند، اما در پایان فقط یک پیام چاپ می شود. ممکن است بپرسید “چرا فقط یک خروجی چاپ می کند؟”
زمان بندی و همزمانی
-
راه اندازی کانال: هر دو
ch1
وch2
برای مدیریت پیام های رشته ای ایجاد می شوند. -
گوروتین ها:
- یک گوروتین پیامی به
ch1
بعد از 1 ثانیه تاخیر - گوروتین دیگری پیامی به
ch2
بعد از 2 ثانیه تاخیر
- یک گوروتین پیامی به
-
بیانیه را انتخاب کنید:
select
بیانیه به پیام های هر دو کانال گوش می دهد. تا زمانی که یکی از کانال ها برای ارسال پیام آماده شود مسدود می شود.
جریان اجرا
- هنگامی که برنامه اجرا می شود، منتظر هر یک می شود
ch1
یاch2
برای ارسال پیام - بعد از 1 ثانیه،
ch1
آماده است، اجازه می دهدselect
بیانیه برای اجرای پروندهch1
. - مهمتر از همه،
select
فقط می تواند یک مورد را در یک زمان اجرا کند. پس از انتخاب یک مورد، از آن خارج می شودselect
مسدود کردن.
سوالات متداول در select
س: آیا می توان منتظر ورود همه کانال ها بود؟ select
برای چاپ همه خروجی ها؟
پاسخ: نه، select
بیانیه برای رسیدگی به یک مورد در یک زمان طراحی شده است. برای منتظر ماندن چندین کانال و چاپ همه خروجی ها، باید از یک حلقه یا گروه انتظار استفاده کنید.
س: اگر هر دو کانال همزمان آماده باشند چه اتفاقی می افتد؟
پاسخ: اگر هر دو کانال به طور همزمان آماده باشند، Go به صورت تصادفی یکی را برای پردازش انتخاب می کند، بنابراین خروجی ممکن است بین اجراها متفاوت باشد.
س: آیا می توانم با وقفه های زمانی کنار بیایم؟ select
?
پاسخ: بله، می توانید یک مورد مهلت زمانی را در آن قرار دهید select
بیانیه، به شما امکان می دهد مدت زمان انتظار برای پیام را مشخص کنید.
س: چگونه می توانم مطمئن شوم که از هر دو کانال پیام دریافت می کنم؟
پاسخ: برای دریافت پیام از هر دو کانال، از یک حلقه با a استفاده کنید select
عبارت داخل آن، یا از a استفاده کنید sync.WaitGroup
منتظر بمانند تا چندین گوروتین وظایف خود را تکمیل کنند.
اطمینان از پیامهای هر دو کانال با استفاده از WaitGroup in Go
برای اطمینان از دریافت پیام از هر دو کانال در Go، می توانید از a استفاده کنید sync.WaitGroup
. این به شما امکان می دهد قبل از ادامه منتظر بمانید تا چندین گوروتین کامل شود.
در اینجا یک مثال است:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
var wg sync.WaitGroup
// Start goroutine for channel 1
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(1 * time.Second)
ch1 "Result from channel 1"
}()
// Start goroutine for channel 2
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(2 * time.Second)
ch2 "Result from channel 2"
}()
// Wait for both goroutines to finish
go func() {
wg.Wait()
close(ch1)
close(ch2)
}()
// Collect results from both channels
results := []string{}
for i := 0; i 2; i++ {
select {
case msg1 := ch1:
results = append(results, msg1)
case msg2 := ch2:
results = append(results, msg2)
}
}
// Print all results
for _, result := range results {
fmt.Println(result)
}
}
خروجی
Result from channel 1
Result from channel 2
توضیح
-
کانال ها و گروه انتظار: دو کانال
ch1
وch2
، بوجود آمدند. آsync.WaitGroup
برای صبر کردن تا پایان هر دو گوروتین استفاده می شود. -
گوروتین ها: هر گوروتین پس از تأخیر پیامی را به کانال خود ارسال می کند. را
wg.Done()
به تکمیل سیگنال فراخوانی می شود. -
بستن کانال ها: پس از انجام تمامی برنامه ها، کانال ها بسته می شوند تا از ارسال بیشتر جلوگیری شود.
-
جمع آوری نتایج: یک حلقه با a
select
بیانیه برای دریافت پیام از هر دو کانال استفاده می شود تا زمانی که هر دو پیام جمع آوری شوند. -
خروجی نهایی: پیام های جمع آوری شده چاپ می شوند.
این روش تضمین می کند که قبل از ادامه منتظر بمانید تا هر دو کانال پیام های خود را ارسال کنند.
اگر علاقه مند به یادگیری بیشتر در مورد استفاده هستید
sync.WaitGroup
در Go، این مقاله در مورد همزمانی را بررسی کنید: همزمانی Golang: یک سواری سرگرم کننده و سریع.
نمونه دنیای واقعی
بیایید دو نسخه یک برنامه را از نظر ساختار، اجرا و زمان بندی با هم مقایسه کنیم.
نسخه اجرای متوالی
این نسخه کارها را به صورت متوالی و یکی پس از دیگری پردازش می کند.
package main
import (
"fmt"
"time"
)
func worker(id int, job int) string {
time.Sleep(time.Second) // Simulate work
return fmt.Sprintf("Worker %d completed job %d", id, job)
}
func main() {
start := time.Now()
results := make([]string, 5)
for j := 1; j 5; j++ {
results[j-1] = worker(1, j) // Call the worker function directly
}
for _, result := range results {
fmt.Println(result)
}
duration := time.Since(start)
fmt.Printf("It took %s to execute!", duration)
}
خروجی:
Worker 1 completed job 1
Worker 1 completed job 2
Worker 1 completed job 3
Worker 1 completed job 4
Worker 1 completed job 5
It took 5.048703s to execute!
نسخه اجرای همزمان
این نسخه کارها را همزمان با استفاده از گوروتین ها و کانال ها پردازش می کند.
package main
import (
"fmt"
"time"
)
func worker(id int, jobs chan int, results chan string) {
for job := range jobs {
time.Sleep(time.Second) // Simulate work
results fmt.Sprintf("Worker %d completed job %d", id, job)
}
}
func main() {
start := time.Now()
jobs := make(chan int, 5)
results := make(chan string)
for w := 1; w 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j 5; j++ {
jobs j
}
close(jobs)
for a := 1; a 5; a++ {
fmt.Println(results)
}
duration := time.Since(start)
fmt.Printf("It took %s to execute!", duration)
}
خروجی:
Worker 1 completed job 1
Worker 2 completed job 2
Worker 3 completed job 3
Worker 1 completed job 4
Worker 2 completed job 5
It took 2.0227664s to execute!
مقایسه
ساختار:
- نسخه متوالی: تابع کارگر را مستقیماً در یک حلقه فراخوانی می کند. بدون همزمانی
- نسخه همزمان: از گوروتین ها برای اجرای همزمان چندین عملکرد کارگری و کانال هایی برای توزیع کار و جمع آوری نتایج استفاده می کند.
اجرا:
- نسخه متوالی: هر کار یکی پس از دیگری پردازش می شود و برای هر کار 1 ثانیه طول می کشد، که منجر به کل زمان اجرا تقریباً برابر با تعداد کارها می شود (5 ثانیه برای 5 کار).
- نسخه همزمان: چندین کارگر (در این مورد 3 نفر) کارها را به طور همزمان پردازش می کنند و زمان کل اجرا را به میزان قابل توجهی کاهش می دهند. مشاغل بین کارگران توزیع می شود و نتایج از طریق کانال ها جمع آوری می شود.
زمان سنجی:
- نسخه متوالی: تقریباً 5.048703 ثانیه طول کشید.
- نسخه همزمان: تقریباً 2.0227664 ثانیه طول کشید.
نسخه همزمان به طور قابل توجهی سریعتر است زیرا از اجرای موازی استفاده می کند و امکان پردازش همزمان چندین کار را فراهم می کند. این کار به جای جمعبندی زمان برای هر کار مانند نسخه متوالی، کل زمان اجرا را به حدود زمانی که طول میکشد برای تکمیل طولانیترین کار، تقسیم بر تعداد کارگران کاهش میدهد.
مراجع اسناد رسمی
-
برو مستندات – گوروتین ها
گوروتین ها
-
برو اسناد – کانال ها
کانال ها
-
Go Blog – Concurrency in Go
همزمانی در Go
-
Go Documentation – بیانیه انتخاب کنید
بیانیه را انتخاب کنید
-
برو تور – کانال ها
Tour of Go: کانال ها
نتیجه
به طور خلاصه، مقاله مروری واضح و ساده از کانالها در Go ارائه میکند و بر نقش آنها در تسهیل ارتباط ایمن بین گوروتینها تأکید میکند. با توضیح مفاهیم کانال های بافر نشده و بافر، این مقاله رفتارهای متمایز و موارد استفاده مناسب آنها را برجسته می کند. علاوه بر این، بر اهمیت بستن کانال ها برای جلوگیری از بن بست و اطمینان از مدیریت کارآمد منابع تأکید می کند. این مقاله با مثالهای کد عملی و تشابههای مرتبط، خوانندگان را با درک اساسی از نحوه استفاده مؤثر از کانالها در برنامههای Go خود مجهز میکند و راه را برای برنامهنویسی همزمان قویتر هموار میکند.