ارسال سیگنالهای خاموش شدن
وقتی برنامه ما در حال اجراست، میتوانیم در هر زمانی آن را با ارسال یک سیگنال خاص خاتمه دهیم. یک روش رایج برای این کار که احتمالاً تا حالا از آن استفاده کردهاید، فشار دادن Ctrl+C روی صفحهکلید برای ارسال سیگنال وقفه است — که به عنوان SIGINT نیز شناخته میشود.
اما این تنها نوع سیگنالی نیست که میتواند برنامه ما را متوقف کند. برخی از انواع رایج دیگر عبارتند از:
| سیگنال | توضیحات | میانبر صفحهکلید | قابل دریافت |
|---|---|---|---|
SIGINT |
وقفه از صفحهکلید | Ctrl+C |
بله |
SIGQUIT |
خروج از صفحهکلید | Ctrl+\ |
بله |
SIGKILL |
کشتن پروسه (خاتمه فوری) | - | خیر |
SIGTERM |
خاتمه دادن به پروسه به صورت منظم | - | بله |
مهم است که از همان ابتدا توضیح دهیم برخی سیگنالها قابل دریافت هستند و برخی دیگر نیستند. سیگنالهای قابل دریافت توسط برنامه ما قابل رهگیری هستند و میتوانند نادیده گرفته شوند یا برای اجرای یک عمل خاص (مانند خاموش شدن صحیح) استفاده شوند. سیگنالهای دیگر، مانند SIGKILL، قابل دریافت نیستند و نمیتوان آنها را رهگیری کرد.
بیایید نگاهی سریع به عملکرد این سیگنالها بیندازیم. اگر میخواهید همراهی کنید، برنامه API را با همان دستور go run ./cmd/api مانند همیشه اجرا کنید:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
با انجام این کار باید پروسهای با نام api روی دستگاه شما شروع به کار کند. میتوانید از دستور pgrep برای تأیید وجود این پروسه استفاده کنید، به این صورت:
$ pgrep -l api 4414 api
در مورد من، میتوانم ببینم که پروسه api در حال اجراست و شناسه پروسه آن 4414 است (شناسه پروسه شما احتمالاً متفاوت خواهد بود).
پس از تأیید این موضوع، سعی کنید با استفاده از دستور pkill سیگنال SIGKILL را به پروسه api ارسال کنید، به این صورت:
$ pkill -SIGKILL api
اگر به پنجره ترمینالی که برنامه API را اجرا میکند برگردید، باید ببینید که برنامه خاتمه یافته و آخرین خط در جریان خروجی signal: killed است. مشابه این:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development signal: killed
میتوانید همان فرآیند را تکرار کنید، اما این بار به جای آن سیگنال SIGTERM را ارسال کنید:
$ pkill -SIGTERM api
این بار باید خط signal: terminated را در انتهای خروجی ببینید، به این صورت:
$ go run ./cmd/api time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development signal: terminated
همچنین میتوانید ارسال سیگنال SIGQUIT را امتحان کنید — با فشار دادن Ctrl+\ روی صفحهکلید یا اجرای pkill -SIGQUIT api. این کار باعث خروج برنامه با یک dump از پشته (stack) میشود، مشابه این:
$ go run ./cmd/api
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established"
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
SIGQUIT: quit
PC=0x46ebe1 m=0 sigcode=0
goroutine 0 [idle]:
runtime.futex(0x964870, 0x80, 0x0, 0x0, 0x0, 0x964720, 0x7ffd551034f8, 0x964420, 0x7ffd55103508, 0x40dcbf, ...)
/usr/local/go/src/runtime/sys_linux_amd64.s:579 +0x21
runtime.futexsleep(0x964870, 0x0, 0xffffffffffffffff)
/usr/local/go/src/runtime/os_linux.go:44 +0x46
runtime.notesleep(0x964870)
/usr/local/go/src/runtime/lock_futex.go:159 +0x9f
runtime.mPark()
/usr/local/go/src/runtime/proc.go:1340 +0x39
runtime.stopm()
/usr/local/go/src/runtime/proc.go:2257 +0x92
runtime.findrunnable(0xc00002c000, 0x0)
/usr/local/go/src/runtime/proc.go:2916 +0x72e
runtime.schedule()
/usr/local/go/src/runtime/proc.go:3125 +0x2d7
runtime.park_m(0xc000000180)
/usr/local/go/src/runtime/proc.go:3274 +0x9d
runtime.mcall(0x0)
/usr/local/go/src/runtime/asm_amd64.s:327 +0x5b
...
میتوانیم ببینیم که این سیگنالها در خاتمه دادن به برنامه ما مؤثر هستند — اما مشکلی که داریم این است که همه آنها باعث خروج فوری برنامه ما میشوند.
خوشبختانه، Go ابزارهایی را در پکیج os/signal فراهم میکند که میتوانیم از آنها برای رهگیری سیگنالهای قابل دریافت و اجرای خاموش شدن صحیح برنامه خود استفاده کنیم. در فصل بعدی نحوه انجام این کار را بررسی خواهیم کرد.