9.4.2 الگو Fan Out/In

9.4.2 الگو Fan Out/In

9.4.2.1 توضیحات #

الگوی Fan Out/In یکی از مهم‌ترین تکنیک‌های همزمانی در زبان Go است که برای افزایش کارایی و سرعت پردازش در سناریوهایی به‌کار می‌رود که نیاز داریم چندین کار مشابه یا مستقل را به صورت موازی انجام دهیم و در نهایت نتایج همه آن‌ها را جمع‌آوری و تجمیع کنیم. در این الگو، معمولاً یک goroutine اصلی چندین goroutine فرعی را راه‌اندازی می‌کند (Fan Out) تا هر کدام یک وظیفه مستقل را انجام دهند؛ سپس نتایج این goroutineها (که می‌تواند هر نوع داده یا حتی خطا باشد) از طریق کانال‌ها جمع‌آوری شده و پس از اتمام همه کارها، نتیجه نهایی (Fan In) به goroutine اصلی برگردانده می‌شود.

این الگو به طور گسترده در جاهایی مانند جمع‌آوری داده از منابع مختلف، پردازش موازی بخش‌های مختلف یک داده بزرگ، یا حتی در معماری‌های میکروسرویس برای جمع‌آوری پاسخ چندین سرویس کاربرد دارد. مزیت اصلی Fan Out/In در Go این است که با استفاده از goroutineها و channelها می‌توان به‌سادگی کارهای موازی را مدیریت کرد، منتظر اتمام تمام کارها ماند و در عین حال، همزمانی ایمن و بدون race condition را حفظ کرد. به عنوان مثال، اگر نیاز باشد صدها درخواست HTTP به طور همزمان ارسال و پاسخ‌ها تجمیع شوند، یا بخش‌های یک فایل بزرگ به صورت موازی پردازش شوند، این الگو بهترین انتخاب است و به سادگی در Go پیاده‌سازی می‌شود.

9.4.2.2 دیاگرام #

graph LR A[InputChannel] --> B1(Add) A --> B2(Add) A --> B3(Add) A --> B4(Add) B1 --> C(Merge) B2 --> C B3 --> C B4 --> C C --> D(Multiply) classDef faded fill:#fff,stroke:#bbb,stroke-width:2px; classDef step fill:#e3ffe3,stroke:#b4eeb4,stroke-width:2px; classDef merge fill:#ffecc7,stroke:#efb64f,stroke-width:2px; class A faded; class B1,B2,B3,B4 step; class C merge; class D step; B1-->|Fan-Out|A C-->|Fan-In|B1

9.4.2.3 نمونه کد #

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

در زیر یک مثال ساده زدیم :

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8// ساختار نتیجه دانلود
 9type DownloadResult struct {
10	URL  string
11	Data string
12	Err  error
13}
14
15func main() {
16	urls := []string{"url1", "url2", "url3"}
17	var wg sync.WaitGroup
18
19	results := make(chan DownloadResult, len(urls)) // کانال بافر دار به اندازه تعداد urlها
20
21	// Fan-Out: اجرای موازی دانلودها
22	for _, url := range urls {
23		wg.Add(1)
24		go func(url string) {
25			defer wg.Done()
26			data, err := downloadFile(url)
27			results <- DownloadResult{
28				URL:  url,
29				Data: data,
30				Err:  err,
31			}
32		}(url)
33	}
34
35	// Fan-In: بستن کانال پس از پایان همه goroutineها
36	go func() {
37		wg.Wait()
38		close(results)
39	}()
40
41	// جمع‌آوری و نمایش نتایج
42	for result := range results {
43		if result.Err != nil {
44			fmt.Printf("خطا در دانلود %s: %v\n", result.URL, result.Err)
45			continue
46		}
47		fmt.Printf("دانلود %s موفقیت‌آمیز: %s\n", result.URL, result.Data)
48	}
49}
50
51// شبیه‌ساز دانلود فایل
52func downloadFile(url string) (string, error) {
53	// در اینجا می‌توانید کد واقعی دانلود را قرار دهید
54	if url == "url2" {
55		return "", fmt.Errorf("اتصال برقرار نشد")
56	}
57	return "file contents of " + url, nil
58}
1$ go run main.go
2دانلود url1 موفقیت‌آمیز: file contents of url1
3دانلود url3 موفقیت‌آمیز: file contents of url3
4خطا در دانلود url2: اتصال برقرار نشد

در این مثال از الگوی Fan-Out/Fan-In در Go، عملیات دانلود فایل‌ها از چندین آدرس به صورت کاملاً موازی و ایمن انجام می‌شود و مدیریت خطاها، شناسایی منبع داده و خوانایی کد به شکل قابل توجهی ارتقا یافته است. ساختار جدیدی به نام DownloadResult تعریف شده که اطلاعات هر دانلود شامل آدرس منبع (URL)، داده دریافتی (Data) و هرگونه خطا (Err) را در خود نگه می‌دارد. این کار باعث می‌شود در زمان جمع‌آوری نتایج، به‌راحتی بتوان نتیجه‌ی هر عملیات را با منبع آن تطبیق داد و مدیریت خطا را به صورت مجزا و تمیز انجام داد؛ در حالی که در کد اولیه، فقط داده به صورت یک رشته ساده منتقل می‌شد و اگر خطایی رخ می‌داد، امکان تشخیص منبع آن وجود نداشت.

در مرحله Fan-Out، با استفاده از یک حلقه، برای هر URL یک goroutine راه‌اندازی می‌شود که وظیفه دانلود داده از آن آدرس را بر عهده دارد. به‌منظور جلوگیری از بلاک شدن ناخواسته goroutineها (به ویژه اگر دریافت روی کانال با تأخیر انجام شود)، یک کانال بافر دار به اندازه تعداد آدرس‌ها تعریف شده است. هر goroutine پس از اتمام کار، نتیجه (اعم از موفق یا ناموفق) را به صورت یک ساختار DownloadResult داخل کانال قرار می‌دهد و با استفاده از wg.Done() پایان کار خود را به WaitGroup اعلام می‌کند. به‌طور همزمان، یک goroutine دیگر وظیفه دارد پس از اتمام همه عملیات‌ها (یعنی وقتی شمارنده WaitGroup به صفر رسید)، کانال نتایج را ببندد تا مصرف‌کننده (main goroutine) از پایان عملیات‌ها مطلع شود.

در مرحله Fan-In، کد اصلی با استفاده از حلقه for result := range results همه نتایج را از کانال دریافت می‌کند. اگر برای هر آدرس خطایی رخ داده باشد، پیام خطا به همراه نام منبع نمایش داده می‌شود و در غیر این صورت، پیام موفقیت و محتوای دانلود شده چاپ می‌گردد. به این ترتیب، امکان مدیریت خطاها، ثبت وضعیت هر دانلود و افزایش شفافیت و قابلیت دیباگ کد به دست می‌آید. از این الگو می‌توان به عنوان هسته اصلی جمع‌آوری موازی داده‌ها، پردازش موازی یا کار با چندین سرویس مختلف استفاده کرد؛ در حالی که همچنان کد خوانا، ایمن و توسعه‌پذیر باقی می‌ماند.

این نسخه، به خاطر ساختار‌بندی داده‌ها، مدیریت دقیق goroutineها و رعایت نکات idiomatic زبان Go، کاملاً مناسب پروژه‌های جدی و تولیدی است و به راحتی می‌توان قابلیت‌هایی مثل شمارش زمان پاسخ‌دهی، ذخیره خروجی‌ها یا مدیریت خطاهای خاص را به آن اضافه کرد. این رویکرد بهترین تمرین‌های تولیدی (production-ready best practices) برای کار با همزمانی در Go را به نمایش می‌گذارد و برای سناریوهای واقعی مانند Web Scraping، جمع‌آوری داده از میکروسرویس‌ها یا پردازش موازی فایل‌ها کاملاً قابل اتکا است.

9.4.2.4 کاربردها #

  • پردازش داده (Data Processing): با استفاده از الگوی Fan-Out/Fan-In می‌توانید حجم زیادی از داده‌ها را به طور موازی پردازش کنید. به عنوان مثال، زمانی که یک داده بزرگ دارید و باید روی آن پردازش انجام دهید، می‌توانید آن را به بخش‌های کوچک‌تر تقسیم کرده و هر بخش را به یک goroutine اختصاص دهید تا پردازش موازی انجام شود. در نهایت، با جمع‌آوری خروجی‌ها از طریق channel، نتیجه نهایی ترکیب و آماده می‌شود. این روش برای سناریوهایی مثل پردازش موازی تصاویر، فایل‌های متنی بزرگ یا داده‌های عددی بسیار مناسب است.
  • Web Scraping: الگوی Fan-Out/Fan-In برای پیاده‌سازی عملیات scraping همزمان روی چندین وب‌سایت یا صفحات وب بسیار کاربردی است. به این صورت که چندین goroutine برای استخراج داده از صفحات مختلف راه‌اندازی می‌شوند و هر کدام نتیجه استخراج‌شده را از طریق یک channel بازمی‌گردانند. سپس این نتایج جمع‌آوری و در صورت نیاز پردازش یا ذخیره می‌شوند. این روش هم سرعت scraping را به شدت افزایش می‌دهد و هم مدیریت منابع را آسان می‌کند.
  • محاسبات توزیع‌شده (Distributed Computing): در سناریوهای محاسبات توزیع‌شده می‌توانید با Fan-Out، چندین goroutine ایجاد کنید که هرکدام وظیفه ارسال یک job به node یا ماشین متفاوتی را دارند و پس از دریافت پاسخ (مثلاً از طریق RPC یا REST)، نتیجه را از طریق channel ارسال می‌کنند. سپس goroutine اصلی تمام نتایج را جمع‌آوری و به صورت ترکیبی یا aggregate پردازش می‌کند. این مدل برای اجرای موازی taskها در خوشه‌های محاسباتی یا توزیع workload در زیرساخت distributed بسیار مناسب است.
  • مدیریت شبکه و اتصالات همزمان: با استفاده از Fan-Out می‌توان برای مدیریت چندین کانکشن ورودی (مثلاً در سرورهای TCP یا HTTP) برای هر کانکشن یک goroutine راه‌اندازی کرد. هر goroutine پردازش مربوط به کانکشن خود را انجام می‌دهد و نتیجه را به channel ارسال می‌کند. سپس در مرحله Fan-In، نتایج تمام کانکشن‌ها جمع‌آوری، ترکیب و بر اساس نیاز پردازش نهایی یا پاسخ به کلاینت انجام می‌شود. این معماری به سادگی مقیاس‌پذیر و مناسب برای سرورهای real-time یا سیستم‌های event-driven است.