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 دیاگرام #
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
.