9.4.18 الگو Channel Cancellation

9.4.18 الگو Channel Cancellation

9.4.18.1 توضیحات #

الگوی Channel Cancellation یا «لغو با کانال» یکی از الگوهای کلیدی در طراحی برنامه‌های همزمان (concurrent) در زبان Go است. این الگو زمانی استفاده می‌شود که نیاز باشد یک یا چند گوروتین را به‌صورت هماهنگ و ایمن متوقف کنیم، به‌ویژه در شرایطی که ادامه اجرای آن‌ها بی‌فایده یا مضر است (مثلاً خطا رخ داده، زمان‌سنج تمام شده یا برنامه در حال خاتمه است). این الگو برخلاف استفاده از context.Context (که در Go برای لغو استاندارد توصیه می‌شود)، از یک channel اختصاصی برای ارسال سیگنال لغو استفاده می‌کند.

در این الگو، یک کانال معمولاً از نوع chan struct{} (یا chan bool) تعریف می‌شود که فقط یک بار مقدار می‌گیرد و بعد از آن، تمام گوروتین‌هایی که روی آن منتظر هستند متوجه لغو می‌شوند. گوروتین‌های مصرف‌کننده با استفاده از select بررسی می‌کنند که آیا سیگنال لغو دریافت شده یا نه، و در صورت دریافت آن، بلافاصله متوقف می‌شوند. به این ترتیب، سیستم بدون استفاده از متغیرهای مشترک یا قفل (mutex) می‌تواند گوروتین‌های متعدد را متوقف کند.

مزیت اصلی Channel Cancellation در سادگی و سازگاری بالا با سایر کانال‌ها و ساختارهای Go است. این الگو به راحتی در ترکیب با select در کنار کانال‌های داده به کار می‌رود، به طوری که هر گوروتین همزمان می‌تواند منتظر داده یا سیگنال لغو باشد. این الگو همچنین مناسب سیستم‌هایی است که به سبک event-driven طراحی شده‌اند یا نیاز دارند از عملیات طولانی یا مسدودکننده (blocking) خارج شوند.

در نهایت، گرچه امروزه استفاده از context.Context در اغلب موقعیت‌های لغو توصیه می‌شود، الگوی Channel Cancellation همچنان بسیار مفید، سبک و قابل فهم است—مخصوصاً در کدهایی که ساده‌تر یا فاقد نیاز به توابع context-aware هستند. این الگو پایه‌ای برای پیاده‌سازی shutdown graceful، stop کردن workerها، لغو عملیات IO و کنترل حلقه‌های طولانی‌مدت در برنامه‌های Go محسوب می‌شود.

9.4.18.2 دیاگرام #

sequenceDiagram participant Main participant Worker1 participant Worker2 participant CancelChan as Cancel Channel Main->>Worker1: start with cancel channel Main->>Worker2: start with cancel channel Note over Worker1,Worker2: کار ادامه دارد... Main-->>CancelChan: close(cancel) Worker1-->>CancelChan: <- cancel Worker2-->>CancelChan: <- cancel Worker1->>Main: stopped gracefully Worker2->>Main: stopped gracefully

9.4.18.3 نمونه کد #

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8// تابعی که تا زمان دریافت سیگنال لغو کار می‌کند
 9func worker(id int, cancel <-chan struct{}) {
10	for {
11		select {
12		case <-cancel:
13			fmt.Printf("⛔️ Worker %d متوقف شد\n", id)
14			return
15		default:
16			fmt.Printf("⚙️ Worker %d در حال کار...\n", id)
17			time.Sleep(500 * time.Millisecond)
18		}
19	}
20}
21
22func main() {
23	cancel := make(chan struct{}) // کانال لغو مشترک
24
25	// اجرای چند گوروتین worker
26	for i := 1; i <= 3; i++ {
27		go worker(i, cancel)
28	}
29
30	// اجرای اصلی تا ۲ ثانیه صبر می‌کند
31	time.Sleep(2 * time.Second)
32
33	// ارسال سیگنال لغو با بستن کانال
34	fmt.Println("📢 ارسال سیگنال لغو به همه گوروتین‌ها...")
35	close(cancel)
36
37	// صبر برای پایان اجرای گوروتین‌ها
38	time.Sleep(1 * time.Second)
39	fmt.Println("🏁 پایان برنامه")
40}
 1$ go run main.go
 2⚙️ Worker 3 در حال کار...
 3⚙️ Worker 1 در حال کار...
 4⚙️ Worker 2 در حال کار...
 5⚙️ Worker 1 در حال کار...
 6⚙️ Worker 3 در حال کار...
 7⚙️ Worker 2 در حال کار...
 8⚙️ Worker 2 در حال کار...
 9⚙️ Worker 3 در حال کار...
10⚙️ Worker 1 در حال کار...
11⚙️ Worker 3 در حال کار...
12⚙️ Worker 2 در حال کار...
13⚙️ Worker 1 در حال کار...
14⚙️ Worker 2 در حال کار...
15📢 ارسال سیگنال لغو به همه گوروتین‌ها...
16⚙️ Worker 3 در حال کار...
17⚙️ Worker 1 در حال کار...
18⛔️ Worker 3 متوقف شد
19⛔️ Worker 2 متوقف شد
20⛔️ Worker 1 متوقف شد
21🏁 پایان برنامه

در این مثال، الگوی Channel Cancellation برای متوقف کردن همزمان چند گوروتین به کار گرفته شده است. هدف این الگو آن است که بدون نیاز به متغیرهای اشتراکی یا قفل (mutex)، چند گوروتین را به‌صورت هماهنگ و ایمن متوقف کنیم، آن‌هم با استفاده از یک کانال ساده که نقش سیگنال لغو (cancel signal) را ایفا می‌کند.

در ابتدای برنامه، یک کانال از نوع chan struct{} به نام cancel تعریف می‌شود. این کانال هیچ داده‌ای حمل نمی‌کند و فقط نقش «علامت توقف» دارد. سپس با استفاده از یک حلقه، سه گوروتین worker راه‌اندازی می‌شوند که همگی به این کانال دسترسی دارند. هر گوروتین درون یک حلقه بی‌نهایت اجرا می‌شود و درون select بررسی می‌کند که آیا سیگنال لغو از کانال دریافت شده یا نه. اگر سیگنال لغو دریافت شود (<-cancel)، گوروتین با چاپ یک پیام متوقف می‌شود. در غیر این صورت، کار شبیه‌سازی‌شده‌ای انجام می‌دهد و سپس کمی می‌خوابد.

در تابع main، برنامه به مدت ۲ ثانیه منتظر می‌ماند تا گوروتین‌ها چند بار پیام «در حال کار» چاپ کنند. سپس کانال cancel را می‌بندد (close(cancel)) که این عمل به عنوان سیگنال توقف برای تمام گوروتین‌ها عمل می‌کند. در نتیجه، هر گوروتینی که در حالت انتظار روی آن کانال بوده، فعال می‌شود و مسیر لغو را طی می‌کند. پس از آن، main کمی صبر می‌کند تا همه گوروتین‌ها فرصت چاپ پیام «⛔️ متوقف شد» را پیدا کنند.

این مثال ساده اما مؤثر نشان می‌دهد که چگونه می‌توان با استفاده از یک کانال مشترک، گوروتین‌های متعدد را به‌صورت هماهنگ متوقف کرد، بدون نیاز به اشتراک‌گذاری متغیر یا وضعیت پیچیده. این الگو در سناریوهای واقعی مانند متوقف‌سازی graceful در سرورها، لغو همزمان چند worker، یا واکنش به خطاهای بحرانی بسیار مفید و پراستفاده است.

9.4.18.4 کاربردها #

  • خاموش‌سازی گوروتین‌ها: برای متوقف کردن چند گوروتین به‌صورت هماهنگ هنگام اتمام کار یا دریافت سیگنال خروج.
  • مدیریت worker pool: برای لغو همه‌ی workerها در صورت خطای بحرانی یا پایان پردازش.
  • جلوگیری از نشت گوروتین (goroutine leak): برای پایان دادن به گوروتین‌هایی که دیگر نیازی به ادامه کار آن‌ها نیست.
  • کنترل زمان اجرا: برای قطع عملیات‌های طولانی یا مسدود شده پس از یک مدت مشخص.
  • پیاده‌سازی graceful shutdown: برای پایان ایمن و منظم سرویس هنگام خاموش شدن یا دریافت سیگنال SIGINT.