گو روایتی تھریڈنگ ماڈلز سے بہتر کنکرنسی کو کیسے ہینڈل کرتا ہے۔

گو روایتی تھریڈنگ ماڈلز سے بہتر کنکرنسی کو کیسے ہینڈل کرتا ہے۔

جدید سافٹ ویئر انجینئرنگ میں، ایسی ایپلی کیشنز بنانا جو بیک وقت متعدد کام انجام دے سکتی ہیں اب کوئی عیش و آرام کی چیز نہیں رہی- یہ ایک بنیادی ضرورت ہے۔ ہائی تھرو پٹ ویب سرورز سے لے کر ریئل ٹائم اسٹریمنگ سروسز تک، ہم آہنگی کارکردگی کا مرکز ہے۔

کئی دہائیوں سے، روایتی پروگرامنگ زبانیں جیسے C++، Java، اور Python نے ہم آہنگی کے کاموں کو سنبھالنے کے لیے آپریٹنگ سسٹم کے مقامی تھریڈنگ ماڈلز پر انحصار کیا۔ تاہم، جب 2000 کی دہائی کے آخر میں گوگل نے گو (گولانگ) کو ڈیزائن کیا، تو انہوں نے یکسر مختلف راستہ اختیار کیا۔ خام OS تھریڈز کو سامنے لانے کے بجائے، Go نے Goroutines اور ایک خصوصی M:N شیڈیولر متعارف کرایا۔

اس مضمون میں، ہم روایتی تھریڈنگ ماڈلز کی تعمیراتی حدود کا جائزہ لیں گے اور دریافت کریں گے کہ کیوں گو کا ہم آہنگی ڈیزائن نمایاں طور پر زیادہ موثر، توسیع پذیر، اور ڈویلپر کے موافق ہے۔


1. روایتی تھریڈنگ کی رکاوٹیں (1:1 ماڈل)

زیادہ تر روایتی رن ٹائم سسٹم 1:1 تھریڈنگ ماڈل استعمال کرتے ہیں۔ اس ماڈل میں، یوزر اسپیس کوڈ میں بنایا گیا ہر تھریڈ براہ راست آپریٹنگ سسٹم (OS) کے زیر انتظام ایک کرنل اسپیس تھریڈ پر نقش ہوتا ہے۔

سادہ ہونے کے باوجود، یہ 1:1 میپنگ تین اہم رکاوٹوں کو متعارف کراتی ہے:

A. میموری اوور ہیڈ (بڑے اسٹیک سائز)

OS تھریڈ ایک بھاری وسیلہ ہے۔ پہلے سے طے شدہ طور پر، آپریٹنگ سسٹمز ہر تھریڈ (عام طور پر 1MB سے 8MB) کے لیے ایک مقررہ، متصل اسٹیک سائز مختص کرتے ہیں۔

  • اگر آپ 10,000 کنکرنٹ کنکشنز کو ہینڈل کرنا چاہتے ہیں تو صرف تھریڈ اسٹیک میموری کے لیے 10,000 OS تھریڈز مختص کرنے کے لیے 10GB اور 80GB RAM کی ضرورت ہوگی۔
  • یہ اعلی کنکشن شمار کے تحت اعلی ہم آہنگی کو سنبھالنا انتہائی مہنگا اور معیاری سرور ہارڈ ویئر پر تقریباً ناممکن بنا دیتا ہے۔

B. اعلی سیاق و سباق کو تبدیل کرنے کے اخراجات

جب OS عملدرآمد کو ایک تھریڈ سے دوسرے تھریڈ میں سوئچ کرتا ہے، تو یہ سیاق و سباق کا سوئچ انجام دیتا ہے۔ چونکہ OS تھریڈز کا انتظام کرنل کے ذریعے کیا جاتا ہے، اس لیے سیاق و سباق کے سوئچ کے لیے صارف-کرنل کی حد کو عبور کرنا ہوتا ہے۔ CPU لازمی ہے:

  1. موجودہ رجسٹروں کی حالت کو محفوظ کریں۔
  2. CPU کیش لائنوں کو فلش کریں اور پیج ٹیبلز کو اپ ڈیٹ کریں (ترجمہ Lookaside Buffers)۔
  3. کرنل موڈ میں جائیں، اگلا تھریڈ منتخب کریں، اور اس کی محفوظ شدہ حالت کو لوڈ کریں۔
  4. یوزر موڈ میں واپس منتقلی

اس پورے راؤنڈ ٹرپ میں تقریباً 1 سے 2 مائیکرو سیکنڈز لگتے ہیں، جو سیکڑوں یا ہزاروں CPU سائیکلوں کی نمائندگی کرتا ہے جو حقیقی کاروباری منطق کو عملی جامہ پہنانے کے بجائے مکمل طور پر انتظامی اوور ہیڈ پر خرچ کیے جاتے ہیں۔

C. OS شیڈولنگ کی حدود

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


2. Go’s Solution: The M:N شیڈولر (Goroutines)

Go Goroutines متعارف کروا کر اور اپنا رن ٹائم شیڈیولر نافذ کر کے OS تھریڈنگ کی حدود کو نظرانداز کرتا ہے۔ تھریڈز 1:1 کی میپنگ کرنے کے بجائے، Go ایک M:N شیڈولنگ ماڈل استعمال کرتا ہے جہاں M ہلکے وزن والے گوروٹینز کو N فزیکل OS تھریڈز پر ملٹی پلیکس کیا جاتا ہے۔

روایتی 1:1 تھریڈنگ بمقابلہ Go M:N شیڈیولر ماڈل ڈایاگرام

یہ فن تعمیر تین بنیادی ساختی عناصر کے زیر انتظام ہے، جنہیں اکثر GMP ماڈل کہا جاتا ہے:

  • G (گوروٹین): ایک واحد گوروٹین کی نمائندگی کرتا ہے۔ اس میں گوروٹین کا عمل درآمد اسٹیک، پروگرام کاؤنٹر، اور ریاست شامل ہے۔ A G ایک OS تھریڈ نہیں ہے۔ یہ ایک سادہ گو ڈھانچہ ہے جو شروع کرنے کے لیے صرف 2KB میموری خرچ کرتا ہے۔
  • M (مشین): ایک فزیکل OS/کرنل تھریڈ کی نمائندگی کرتا ہے۔ اس کا انتظام OS شیڈولر کے ذریعے کیا جاتا ہے اور یہ goroutines کے مشین کوڈ کی ہدایات پر عمل درآمد کے لیے ذمہ دار ہے۔
  • P (پروسیسر): ایک منطقی پروسیسر یا عملدرآمد کے سیاق و سباق کی نمائندگی کرتا ہے۔ P مثالوں کی تعداد میزبان مشین پر منطقی CPU کور کی تعداد سے طے شدہ ہے (GOMAXPROCS کے ذریعے کنٹرول)۔ گو کوڈ کو چلانے کے لیے مشین M کو ایک منطقی پروسیسر P حاصل کرنا ہوگا۔

کیوں GMP ماڈل اعلیٰ ہے۔

کیونکہ گوروٹینز کو مکمل طور پر صارف کی جگہ پر Go رن ٹائم کے ذریعے منظم کیا جاتا ہے:

  1. ڈائنیمک اسٹیکس: گوروٹین 2KB کے ایک چھوٹے اسٹیک سے شروع ہوتی ہے۔ جیسے جیسے عمل درآمد کا مطالبہ ہوتا ہے، اسٹیک متحرک طور پر بڑھتا ہے (ڈھیر میں میموری کے بڑے حصے مختص کرتا ہے) اور سکڑ جاتا ہے۔ یہ Go کو ایک ہی لیپ ٹاپ پر بیک وقت سینکڑوں ہزاروں گوروٹینز چلانے کی اجازت دیتا ہے۔
  2. تیز سیاق و سباق کے سوئچز: گوروٹینز کے درمیان سوئچنگ مکمل طور پر یوزر اسپیس میں ہوتی ہے بغیر کرنل سیاق و سباق کے سوئچز کو استعمال کیے۔ صرف پروگرام کاؤنٹر اور چند سی پی یو رجسٹر محفوظ ہیں۔ یہ یوزر اسپیس سیاق و سباق کے سوئچ میں صرف 10 سے 100 نینو سیکنڈز لگتے ہیں—ایک OS تھریڈ سوئچ سے تقریباً 10x سے 100x تیز۔

3. ورک اسٹیلنگ اور نان بلاکنگ I/O

گو رن ٹائم دو جدید شیڈولنگ میکانزم کا استعمال کرتا ہے اس بات کو یقینی بنانے کے لیے کہ فزیکل سی پی یو کور کو کبھی کم استعمال نہ کیا جائے: ورک اسٹیلنگ اور سسکال ہینڈ آف۔

A. کام چوری کرنے والا الگورتھم

ہر منطقی پروسیسر P گوروٹینز کی اپنی مقامی رن قطار کو برقرار رکھتا ہے۔ مزید برآں، اوور فلو کے لیے عالمی سطح پر رن ​​کی قطار ہے۔ اگر ایک پروسیسر P اپنی مقامی رن قطار میں تمام گوروٹینز کو ختم کر دیتا ہے، تو یہ نیند میں نہیں جاتا ہے۔ اس کے بجائے، یہ کام چوری انجام دیتا ہے: یہ دوسرے منطقی پروسیسرز کو چیک کرتا ہے اور تمام CPU کوروں میں کام کے بوجھ کو متوازن کرنے کے لیے ان کے قطار میں لگائے گئے گوروٹینز کا نصف چوری کرتا ہے۔

Local Queue P1: [ G1, G2, G3 ]  ---> Running on Thread M1
Local Queue P2: [ ]            ---> Running on Thread M2 (Idle)
                                     P2 steals G3 and G2 from P1!

B. نیٹ ورک پولر اور نان بلاکنگ I/O

جب گوروٹین نیٹ ورک I/O انجام دیتا ہے (مثال کے طور پر، ڈیٹا بیس کنکشن سے پڑھنا یا HTTP کال کرنا)، Go بنیادی OS تھریڈ M کو مسدود نہیں کرتا ہے۔

اس کے بجائے، Go رن ٹائم بلاک کو ایک وقف شدہ نیٹ ورک پولر کے ساتھ رجسٹر کرتا ہے (جو لینکس پر epoll، macOS پر kqueue، یا Windows پر IOCP جیسے موثر OS سے متعلق ملٹی پلیکسنگ APIs کا استعمال کرتا ہے)۔ بلاک شدہ گوروٹین G کو تھریڈ M سے الگ کر کے نیٹ ورک پولر میں کھڑا کر دیا گیا ہے۔

دھاگہ M فوری طور پر قطار میں سے ایک اور چلنے کے قابل گوروٹین کو اٹھا لیتا ہے۔ ایک بار جب I/O ایونٹ مکمل ہو جاتا ہے، نیٹ ورک پولر G کو ایک فعال رن کیو میں واپس چلا جاتا ہے تاکہ عمل درآمد کو دوبارہ شروع کیا جا سکے۔


4. چینلز بمقابلہ مشترکہ میموری (CSP ماڈل)

روایتی تھریڈنگ ماڈل میموری کو شیئر کرکے ہم آہنگ کاموں کو مربوط کرتے ہیں (مثال کے طور پر، تھریڈز کے درمیان پوائنٹر پاس کرنا)۔ ڈیٹا ریس کو روکنے کے لیے، ڈویلپرز کو دستی طور پر تالے، mutexes، اور حالت متغیر کا انتظام کرنا چاہیے:

// Traditional Java Shared Memory Approach
synchronized(lock) {
    sharedResource.updateState();
}

یہ ماڈل بدنام زمانہ غلطی کا شکار ہے، جس کے نتیجے میں اکثر تعطل، دوڑ کے حالات، اور کیشے ہم آہنگی کی رکاوٹیں پیدا ہوتی ہیں۔

Go Communicating Sequential Processes (CSP) باضابطہ ماڈل کو نافذ کرتا ہے، جس کا خلاصہ مشہور گولانگ کہاوت ہے:

“میموری شیئر کرکے بات چیت نہ کریں؛ اس کے بجائے، بات چیت کرکے میموری کو شیئر کریں۔”

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


5. عملی نفاذ: گوروٹینز اور چینلز ان ایکشن

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

package main

import (
	"fmt"
	"time"
)

// Task represents a unit of work
type Task struct {
	ID       int
	Duration time.Duration
}

// Result represents the outcome of a processed task
type Result struct {
	TaskID int
	Value  string
}

// worker processes incoming tasks from the jobs channel and sends results
func worker(id int, jobs <-chan Task, results chan<- Result) {
	for job := range jobs {
		fmt.Printf("Worker %d: Started processing job %d\n", id, job.ID)
		time.Sleep(job.Duration) // Simulate CPU/IO delay
		
		results <- Result{
			TaskID: job.ID,
			Value:  fmt.Sprintf("Processed by worker %d in %v", id, job.Duration),
		}
	}
}

func main() {
	// Create tasks
	tasks := []Task{
		{ID: 101, Duration: 200 * time.Millisecond},
		{ID: 102, Duration: 400 * time.Millisecond},
		{ID: 103, Duration: 100 * time.Millisecond},
		{ID: 104, Duration: 300 * time.Millisecond},
	}

	// Buffered channels prevent sender blocking
	jobs := make(chan Task, len(tasks))
	results := make(chan Result, len(tasks))

	// Spawn 3 concurrent worker goroutines
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	// Feed tasks into the job channel
	for _, task := range tasks {
		jobs <- task
	}
	close(jobs) // Closing tells workers no more jobs are coming

	// Collect and aggregate results
	for i := 0; i < len(tasks); i++ {
		res := <-results
		fmt.Printf("[RESULT] Job %d: %s\n", res.TaskID, res.Value)
	}
	close(results)
}

6. فن تعمیر کا موازنہ: OS تھریڈز بمقابلہ گوروٹائنز

میٹرک / فیچر روایتی OS تھریڈز (1:1 ماڈل) Go Goroutines (M:N ماڈل)
اسٹارٹ اپ میموری فکسڈ (عام طور پر 1MB - 8MB) متحرک (~2KB سے شروع ہوتا ہے)
سیاق و سباق سوئچ ٹائم آہستہ (1,000 - 2,000 نینو سیکنڈز) تیز (10 - 100 نینو سیکنڈز)
سوئچنگ اسپیس کرنل اسپیس (بھاری سیاق و سباق سوئچ) یوزر اسپیس (رن ٹائم شیڈیولر)
تخلیق کی قیمت مہنگا (OS سسٹم کالز شامل ہے) انتہائی سستا (سادہ مختص)
مواصلات مشترکہ میموری (Mutexes، Semaphore) چینلز (CSP پیغام پاس کرنا)
ڈیڈ لاک کا خطرہ ہائی (دستی طور پر ٹریس کرنا مشکل) چینل ڈیزائن کے ذریعے کم کیا گیا اور رن ٹائم کا پتہ لگانے کو مرتب کیا گیا

نتیجہ

آپریٹنگ سسٹم کے خام تھریڈنگ ماڈل سے ہم آہنگی کو ڈیکپلنگ کرکے، Go نے جدید بیک اینڈ آرکیٹیکچرز کے بنیادی اسکیلنگ کے مسائل کو حل کیا۔

Goroutines کم سے کم میموری اوور ہیڈ کے ساتھ بڑے پیمانے پر ہم آہنگی کو قابل بناتا ہے، M:N شیڈیولر CPU کے استعمال کو کام کی چوری کے ذریعے کرنل سیاق و سباق کو تبدیل کرنے کے اخراجات کے بغیر بہتر بناتا ہے، اور چینلز ایک محفوظ، اظہار خیال کنکرنسی پیراڈائم فراہم کرتے ہیں۔

چاہے آپ مائیکرو سروسز بنا رہے ہوں یا بڑے تقسیم شدہ نظام، Go کا بلٹ ان کنکرنسی آرکیٹیکچر یقینی بناتا ہے کہ آپ کی ایپلیکیشن ریسپانسیو، وسائل کے لحاظ سے موثر اور برقرار رکھنے میں آسان رہے۔


غزنکس بلاگ پر مزید سافٹ ویئر ڈویلپمنٹ اور بیک اینڈ انجینئرنگ کی بصیرتیں دریافت کریں →