1.1 چینلز کا جائزہ

چینل گولانگ کی ایک بہت اہم خصوصیت ہے، جو مختلف گوروٹین کے درمیان مواصلت کے لیے استعمال ہوتا ہے۔ گولانگ کی ہمزمانیت کا نمونہ CSP (Communicating Sequential Processes) ہے، جس میں چینلز پیغامات کا کردار ادا کرتے ہیں۔ چینلز کا استعمال مشکل یاداشت کردہ کرداروں سے بچا سکتا ہے، جس سے ہم زمانہ پروگرام ڈیزائن سادہ اور محفوظ ہوتا ہے۔

1.2 چینلز کی تخلیق اور بند کرنا

گولانگ میں چینلز make فنکشن کا استعمال کرکے بنائے جاتے ہیں، جو چینل کی قسم اور بفر کا سائز مختص کر سکتا ہے۔ بفر کا سائز اختیاری ہے، اور اس کا سائز تعین نہیں کرنے سے ایک غیر بفر والا چینل بنایا جائے گا۔

ch := make(chan int)    // قسم int کا ایک غیر بفر چینل بنائیں
chBuffered := make(chan int, 10) // قسم int کا بفر کے ساتھ ایک بفر کردہ چینل بنائیں جس کا کپیسٹی 10 ہے

چینل کو مناسب طریقے سے بند کرنا بھی اہم ہے۔ جب ڈیٹا کو موصول ہونا بند کر دیا جائے تو دیگر گوروٹینز میں انتظار سے بچنے کے لیے چینل بند کرنا چاہئے، تاکہ ڈیڈلاک یا سیکوائیتن کی صورت میں پھنسے کو روکا جا سکے۔

close(ch) // چینل بند کریں

1.3 ڈیٹا بھیجنا اور موصول کرنا

چینل میں ڈیٹا بھیجنا اور موصول کرنا بہت آسان ہے، <- علامت کا استعمال ہوتا ہے۔ بھیجنے کا عمل بائیں ہاتھ پر ہوتا ہے، اور موصول کرنے کا عمل دائیں ہاتھ پر ہوتا ہے۔

ch <- 3 // چینل میں ڈیٹا بھیجیں
value := <- ch // چینل سے ڈیٹا موصول کریں

البتہ، اس بات کا خیال رکھنا ضروری ہے کہ بھیجنے کا عمل جب تک ڈیٹا موصول نہیں ہوتا تو روک دینے کا عمل کرے گا، اور موصول کرنے کا عمل بھی صرف اس وقت تک روکے گا جب تک موجودہ ڈیٹا پڑھا نہیں گیا ہو۔

fmt.Println(<-ch) // یہ بلاک کرے گا جب تک چینل سے ڈیٹا بھیجا نہیں گیا ہو

2 چینلز کا فنی استعمال

2.1 چینلز کی کپیسٹی اور بفر کرنا

چینلز بفر والے ہوسکتے ہیں یا غیر بفر۔ غیر بفر چینلز بھیجنے والے کو روک دیں گی جب تک موصول کرنے والا تیار نہیں ہوتا۔ غیر بفر چینلز بھیجنے اور موصول کرنے کی ہم زمانیت کو یقینی بناتے ہیں، عام طور پر دو گوروٹین کی ہمزمانیت کو مختص کرنے کے لیے استعمال ہوتی ہیں۔

ch := make(chan int) // غیر بفر چینل بنائیں
go func() {
    ch <- 1 // اگر کوئی گوروٹین موصول کرنے کے لیے تیار نہیں ہوتا تو یہ روک دیں گا
}()

بفر والے چینلز کا ایک کپیسٹی حد ہوتی ہے، اور چینل میں ڈیٹا بھیجنے والا صرف اس وقت روکے گا جب بفر بھر چکا ہوتا ہے۔ اسی طرح، خالی بفر سے ڈیٹا موصول کرنے کی کوشش کرنے کا عمل روک دے گا۔ بفر والے چینلز عموماً بلند ٹریفک اور غیر متسلسل مواصلت کے سیناریوز کے لیے استعمال ہوتے ہیں، جو انتظار سے پیدا ہونے والے برقیاتی نقصان کو کم کرنے میں مددگار ثابت ہوتے ہیں۔

ch := make(chan int, 10) // کپیسٹی 10 والا بفر چینل بنائیں
go func() {
    for i := 0; i < 10; i++ {
        ch <- i // یہ روکے غیر کرے گا جب تک چینل پہلے ہی بھر نہیں چکا
    }
    close(ch) // بھیجنے کے بعد چینل بند کریں
}()

چینل کی قسم کا انتخاب مواصلت کی فطرت پر منحصر ہوتا ہے: کیا ہم زمانیت کی ضمانت چاہئے، کیا بفر کی ضرورت ہوتی ہے، اور کارکردگی کی ضرورتیں وغیرہ۔

2.2 select سٹیٹمنٹ کا استعمال

اگر متعدد چینلز کے درمیان منتخب کرنا ہو تو select سٹیٹمنٹ بہت مفید ہوتا ہے۔ سوئچ سٹیٹمنٹ کی طرح، لیکن اس کے اندر ہر کیس میں چینل کا عمل شامل ہوتا ہے۔ یہ چینلز پر ڈیٹا فلو کو سن سکتا ہے، اور جب متعدد چینلز ایک ساتھ تیار ہوں، تو select کسی ایک کو بے قاعدہ طریقے سے منتخب کرے گا۔

ch1 := make(chan int)
ch2 := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch1 <- i
    }
}()

go func() {
    for i := 0; i < 5; i++ {
        ch2 <- i * 10
    }
}()

for i := 0; i < 5; i++ {
    select {
    case v1 := <-ch1:
        fmt.Println("ch1 سے موصول شدہ:", v1)
    case v2 := <-ch2:
        fmt.Println("ch2 سے موصول شدہ:", v2)
    }
}

select کا استعمال مختلف مواصلت کے سیناریوز کا سامنا کر سکتا ہے، جیسے کہ متعدد چینلز سے ایک ساتھ ڈیٹا موصول کرنا یا مخصوص حالات کے بنیاد پر ڈیٹا بھیجنا۔

2.3 چینلز کے لئے رینج لوپ

range کے استعمال سے چینل سے متواتر ڈیٹا حاصل کیا جا سکتا ہے جب تک وہ بند نہیں ہو جاتا۔ یہ خاص طور پر منتج-استہلک ماڈل میں نا معلوم تعداد کے ڈیٹا کے ساتھ کام کرتے وقت بہت کارآمد ثابت ہوتا ہے۔

ch := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // براہ کرم چینل بند کرنا مت بھولیں
}()

for n := range ch {
    fmt.Println("Received:", n)
}

جب چینل بند ہوجاتا ہے اور کوئی بچا ہوا ڈیٹا نہیں ہوتا، تو لوپ مکمل ہو جاتا ہے۔ اگر چینل بند کرنا بھول جائیں تو range ایک گوروٹین لیک کا سبب بن سکتا ہے، اور پروگرام بے حد ڈیٹا کا انتظار کرنے کے لئے بے منتہا رہ سکتا ہے۔

3 کنکرنس میں پیچیدہ مواقع کا منظر عام

3.1 سیاق و سباق کی کردار

گولانگ کی مساوی پروگرامنگ میں، context پیکیج ایک اہم کردار ادا کرتا ہے۔ سیاق و سباق میں ڈیٹا، منسوخی کی نشانیاں، مقرر شدہ وقتسے وغیرہ کے بیچ میں مضمون کاری کے کئی گوروٹینز کے درمیان۔

اگر ایک ویب سروس کو ڈیٹابیس کا سوال کرنا ہو اور ڈیٹا پر محاسبے کرنا ہو جو کئی گوروٹینوں میں کرنا ہو تو، اگر صارف نے اچانک سروس کو منسوخ کر دیا ہو یا سروس کو مقررہ وقت میں ریکویسٹ مکمل کرنا ہو، تو ہمیں تمام چل رہی گوروٹینز کو منسوخ کرنے کے لئے ایک آلہ کی ضرورت ہوتی ہے۔

یہاں، ہم context کا استعمال کرتے ہیں اس مطلب کی حصول کے لئے:

package main

import (
	"context"
	"fmt"
	"time"
)

func operation1(ctx context.Context) {
	time.Sleep(1 * time.Second)
	select {
	case <-ctx.Done():
		fmt.Println("operation1 canceled")
		return
	default:
		fmt.Println("operation1 completed")
	}
}

func operation2(ctx context.Context) {
	time.Sleep(2 * time.Second)
	select {
	case <-ctx.Done():
		fmt.Println("operation2 canceled")
		return
	default:
		fmt.Println("operation2 completed")
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

go operation1(ctx)
go operation2(ctx)

	<-ctx.Done()
	fmt.Println("main: context done")
}

اوپر دیئے گئے کوڈ میں، context.WithTimeout کا استعمال ایک کنٹیکسٹ بنانے کے لئے کیا گیا ہے جو خودبخود ایک مخصوص وقت کے بعد منسوخ ہوجاتا ہے۔ operation1 اور operation2 کی فنکشنوں میں select بلاک ہے جو ctx.Done() کی سنواتی ہے، جو انہیں فوراً رکنے دیتا ہے جب کنٹیکسٹ نشانی بھیجتا ہے۔

3.2 چینلز کے ساتھ خرابیوں کا سامنا

جب بات کنکرنٹ پروگرامنگ کی ہو تو، خرابیوں کا سامنا کرنا بہت اہم ہوتا ہے۔ گولانگ میں، آپ گوروٹینز کے ساتھ چینلز کا استعمال کر کے ایررز کو غیر مختصر طریقے سے ہینڈل کر سکتے ہیں۔

نیچے دیے گئے کوڈ کا مثال اس بات کا دوکھائی دیتی ہے کہ گوروٹینز سے خرابیاں باہر بھیجنے اور انہیں مین گوروٹین میں ہینڈل کرنے کا طریقہ کار کیسے ہے:

package main

import (
	"errors"
	"fmt"
	"time"
)

func performTask(id int, errCh chan<- error) {
	// ایک ٹاسک کی تصور کریں جو بے وقوعہ طریقے سے کامیاب ہو یا ناکام ہو سکتا ہے
	if id%2 == 0 {
		time.Sleep(2 * time.Second)
		errCh <- errors.New("task failed")
	} else {
		fmt.Printf("task %d completed successfully\n", id)
		errCh <- nil
	}
}

func main() {
	tasks := 5
	errCh := make(chan error, tasks)

	for i := 0; i < tasks; i++ {
		go performTask(i, errCh)
	}

	for i := 0; i < tasks; i++ {
		err := <-errCh
		if err != nil {
			fmt.Printf("received error: %s\n", err)
		}
	}
	fmt.Println("finished processing all tasks")
}

اس مثال میں، ہم performTask فنکشن کو تعین دیتے ہیں کہ کسی ٹاسک کو کامیابی سے یا ناکامی سے خاتم ہونے کا تجربہ کرے۔ خرابیاں errCh چینل کے ذریعے مین گوروٹین تک بھیجی جاتی ہیں جو پیرامیٹر کے طور پر پاس کی جاتی ہے۔ مین گوروٹین تمام ٹاسکس کے مکمل ہونے کا انتظار کرتی ہے اور ایرر میسیجز پڑھتی ہے۔ بفرض شکست۔ ہم بفرض شکست کرنے کے لئے بفرض کرتے ہیں کہ غیرمکمل خرابیاں کا تالال بند ہوجائے۔

یہ تراکیب کنکرنٹ پروگرامنگ میں پیچیدہ صورتحالوں کا سامنا کرنے کے لئے قوتورانہ آلات ہیں۔ انہیں مناسب طریقے سے استعمال کرنے سے کوڈ محفوظ، سمجھنے کے قابل اور قائم رکھا جاسکتا ہے۔