|
| 1 | +--- |
| 2 | +title: 9.4.18 الگو Channel Cancellation |
| 3 | +slug: go-concurrency-pattern-channel-cancellation |
| 4 | +weight: 177018 |
| 5 | +mermaid: "true" |
| 6 | +--- |
| 7 | + |
| 8 | + |
| 9 | +## 9.4.18.1 توضیحات |
| 10 | + |
| 11 | +الگوی **Channel Cancellation** یا «لغو با کانال» یکی از الگوهای کلیدی در طراحی برنامههای همزمان (concurrent) در زبان Go است. این الگو زمانی استفاده میشود که نیاز باشد یک یا چند گوروتین را بهصورت هماهنگ و ایمن متوقف کنیم، بهویژه در شرایطی که ادامه اجرای آنها بیفایده یا مضر است (مثلاً خطا رخ داده، زمانسنج تمام شده یا برنامه در حال خاتمه است). این الگو برخلاف استفاده از `context.Context` (که در Go برای لغو استاندارد توصیه میشود)، از یک `channel` اختصاصی برای ارسال سیگنال لغو استفاده میکند. |
| 12 | + |
| 13 | +در این الگو، یک کانال معمولاً از نوع `chan struct{}` (یا `chan bool`) تعریف میشود که فقط یک بار مقدار میگیرد و بعد از آن، تمام گوروتینهایی که روی آن منتظر هستند متوجه لغو میشوند. گوروتینهای مصرفکننده با استفاده از `select` بررسی میکنند که آیا سیگنال لغو دریافت شده یا نه، و در صورت دریافت آن، بلافاصله متوقف میشوند. به این ترتیب، سیستم بدون استفاده از متغیرهای مشترک یا قفل (mutex) میتواند گوروتینهای متعدد را متوقف کند. |
| 14 | + |
| 15 | +مزیت اصلی Channel Cancellation در سادگی و سازگاری بالا با سایر کانالها و ساختارهای Go است. این الگو به راحتی در ترکیب با `select` در کنار کانالهای داده به کار میرود، به طوری که هر گوروتین همزمان میتواند منتظر داده یا سیگنال لغو باشد. این الگو همچنین مناسب سیستمهایی است که به سبک event-driven طراحی شدهاند یا نیاز دارند از عملیات طولانی یا مسدودکننده (blocking) خارج شوند. |
| 16 | + |
| 17 | +در نهایت، گرچه امروزه استفاده از `context.Context` در اغلب موقعیتهای لغو توصیه میشود، الگوی Channel Cancellation همچنان بسیار مفید، سبک و قابل فهم است—مخصوصاً در کدهایی که سادهتر یا فاقد نیاز به توابع context-aware هستند. این الگو پایهای برای پیادهسازی shutdown graceful، stop کردن workerها، لغو عملیات IO و کنترل حلقههای طولانیمدت در برنامههای Go محسوب میشود. |
| 18 | + |
| 19 | +## 9.4.18.2 دیاگرام |
| 20 | + |
| 21 | + |
| 22 | +{{< mermaid >}} |
| 23 | +sequenceDiagram |
| 24 | + participant Main |
| 25 | + participant Worker1 |
| 26 | + participant Worker2 |
| 27 | + participant CancelChan as Cancel Channel |
| 28 | + |
| 29 | + Main->>Worker1: start with cancel channel |
| 30 | + Main->>Worker2: start with cancel channel |
| 31 | + |
| 32 | + Note over Worker1,Worker2: کار ادامه دارد... |
| 33 | + |
| 34 | + Main-->>CancelChan: close(cancel) |
| 35 | + |
| 36 | + Worker1-->>CancelChan: <- cancel |
| 37 | + Worker2-->>CancelChan: <- cancel |
| 38 | + |
| 39 | + Worker1->>Main: stopped gracefully |
| 40 | + Worker2->>Main: stopped gracefully |
| 41 | +{{< /mermaid >}} |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +## 9.4.18.3 نمونه کد |
| 47 | + |
| 48 | +```go |
| 49 | +package main |
| 50 | + |
| 51 | +import ( |
| 52 | + "fmt" |
| 53 | + "time" |
| 54 | +) |
| 55 | + |
| 56 | +// تابعی که تا زمان دریافت سیگنال لغو کار میکند |
| 57 | +func worker(id int, cancel <-chan struct{}) { |
| 58 | + for { |
| 59 | + select { |
| 60 | + case <-cancel: |
| 61 | + fmt.Printf("⛔️ Worker %d متوقف شد\n", id) |
| 62 | + return |
| 63 | + default: |
| 64 | + fmt.Printf("⚙️ Worker %d در حال کار...\n", id) |
| 65 | + time.Sleep(500 * time.Millisecond) |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +func main() { |
| 71 | + cancel := make(chan struct{}) // کانال لغو مشترک |
| 72 | + |
| 73 | + // اجرای چند گوروتین worker |
| 74 | + for i := 1; i <= 3; i++ { |
| 75 | + go worker(i, cancel) |
| 76 | + } |
| 77 | + |
| 78 | + // اجرای اصلی تا ۲ ثانیه صبر میکند |
| 79 | + time.Sleep(2 * time.Second) |
| 80 | + |
| 81 | + // ارسال سیگنال لغو با بستن کانال |
| 82 | + fmt.Println("📢 ارسال سیگنال لغو به همه گوروتینها...") |
| 83 | + close(cancel) |
| 84 | + |
| 85 | + // صبر برای پایان اجرای گوروتینها |
| 86 | + time.Sleep(1 * time.Second) |
| 87 | + fmt.Println("🏁 پایان برنامه") |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +```shell |
| 92 | +$ go run main.go |
| 93 | +⚙️ Worker 3 در حال کار... |
| 94 | +⚙️ Worker 1 در حال کار... |
| 95 | +⚙️ Worker 2 در حال کار... |
| 96 | +⚙️ Worker 1 در حال کار... |
| 97 | +⚙️ Worker 3 در حال کار... |
| 98 | +⚙️ Worker 2 در حال کار... |
| 99 | +⚙️ Worker 2 در حال کار... |
| 100 | +⚙️ Worker 3 در حال کار... |
| 101 | +⚙️ Worker 1 در حال کار... |
| 102 | +⚙️ Worker 3 در حال کار... |
| 103 | +⚙️ Worker 2 در حال کار... |
| 104 | +⚙️ Worker 1 در حال کار... |
| 105 | +⚙️ Worker 2 در حال کار... |
| 106 | +📢 ارسال سیگنال لغو به همه گوروتینها... |
| 107 | +⚙️ Worker 3 در حال کار... |
| 108 | +⚙️ Worker 1 در حال کار... |
| 109 | +⛔️ Worker 3 متوقف شد |
| 110 | +⛔️ Worker 2 متوقف شد |
| 111 | +⛔️ Worker 1 متوقف شد |
| 112 | +🏁 پایان برنامه |
| 113 | +``` |
| 114 | + |
| 115 | + |
| 116 | +در این مثال، الگوی **Channel Cancellation** برای متوقف کردن همزمان چند گوروتین به کار گرفته شده است. هدف این الگو آن است که بدون نیاز به متغیرهای اشتراکی یا قفل (mutex)، چند گوروتین را بهصورت هماهنگ و ایمن متوقف کنیم، آنهم با استفاده از یک کانال ساده که نقش سیگنال لغو (cancel signal) را ایفا میکند. |
| 117 | + |
| 118 | +در ابتدای برنامه، یک کانال از نوع `chan struct{}` به نام `cancel` تعریف میشود. این کانال هیچ دادهای حمل نمیکند و فقط نقش «علامت توقف» دارد. سپس با استفاده از یک حلقه، سه گوروتین worker راهاندازی میشوند که همگی به این کانال دسترسی دارند. هر گوروتین درون یک حلقه بینهایت اجرا میشود و درون `select` بررسی میکند که آیا سیگنال لغو از کانال دریافت شده یا نه. اگر سیگنال لغو دریافت شود (`<-cancel`)، گوروتین با چاپ یک پیام متوقف میشود. در غیر این صورت، کار شبیهسازیشدهای انجام میدهد و سپس کمی میخوابد. |
| 119 | + |
| 120 | +در تابع `main`، برنامه به مدت ۲ ثانیه منتظر میماند تا گوروتینها چند بار پیام «در حال کار» چاپ کنند. سپس کانال `cancel` را میبندد (`close(cancel)`) که این عمل به عنوان سیگنال توقف برای تمام گوروتینها عمل میکند. در نتیجه، هر گوروتینی که در حالت انتظار روی آن کانال بوده، فعال میشود و مسیر لغو را طی میکند. پس از آن، `main` کمی صبر میکند تا همه گوروتینها فرصت چاپ پیام «⛔️ متوقف شد» را پیدا کنند. |
| 121 | + |
| 122 | +این مثال ساده اما مؤثر نشان میدهد که چگونه میتوان با استفاده از یک کانال مشترک، گوروتینهای متعدد را بهصورت هماهنگ متوقف کرد، بدون نیاز به اشتراکگذاری متغیر یا وضعیت پیچیده. این الگو در سناریوهای واقعی مانند متوقفسازی graceful در سرورها، لغو همزمان چند worker، یا واکنش به خطاهای بحرانی بسیار مفید و پراستفاده است. |
| 123 | + |
| 124 | +## 9.4.18.4 کاربردها |
| 125 | + |
| 126 | + |
| 127 | +- **خاموشسازی گوروتینها:** برای متوقف کردن چند گوروتین بهصورت هماهنگ هنگام اتمام کار یا دریافت سیگنال خروج. |
| 128 | +- **مدیریت worker pool:** برای لغو همهی workerها در صورت خطای بحرانی یا پایان پردازش. |
| 129 | +- **جلوگیری از نشت گوروتین (goroutine leak):** برای پایان دادن به گوروتینهایی که دیگر نیازی به ادامه کار آنها نیست. |
| 130 | +- **کنترل زمان اجرا:** برای قطع عملیاتهای طولانی یا مسدود شده پس از یک مدت مشخص. |
| 131 | +- **پیادهسازی graceful shutdown:** برای پایان ایمن و منظم سرویس هنگام خاموش شدن یا دریافت سیگنال `SIGINT`. |
0 commit comments