1、使用场景
实际项目中,我们希望修改了配置文件后,但又不想通过重启进程让它重新加载配置文件,可以使用signal的方式进行信号传递,或者我们希望通过信号控制,实现一种优雅的退出方式。Golang为我们提供了signal包,实现信号处理机制,允许Go 程序与传入的信号进行交互。
2、常用的Term信号
3、简单的栗子
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
c := make(chan os.Signal)
signal.Notify(c)
fmt.Println("start..")
s := <-c
fmt.Println("End...", s)
}
(1)传递SIGINT信号
[homework@xxxxx signal]$ go run monitor.go
start..
#此时,CTL+C发送一个SIGINT信号量,得到输出为:
[homework@xxxxx signal]$ go run monitor.go
start..
^CEnd... interrupt
(2)传递SIGTERM信号
打开2个Term窗口 第一个运行go run monitor.go程序 第二个执行:ps -ef | grep monitor.go | grep grep -v | awk '{print $2}' | xargs kill
#此时,kill命令发送一个SIGTERM信号量,得到输出为:
[homework@xxxxx signal]$ go run monitor.go
start..
Terminated
4、优雅的退出守护进程
(1)何为优雅(graceful)?
Linux Server端的应用程序经常会长时间运行,在运行过程中,可能申请了很多系统资源,也可能保存了很多状态。
在这些场景下,我们希望进程在退出前,可以释放资源或将当前状态dump到磁盘上或打印一些重要的日志,即希望进程优雅退出。
(2)从对优雅退出的理解不难看出:优雅退出可以通过捕获SIGTERM来实现。
A、注册SIGTERM信号的处理函数并在处理函数中做一些进程退出的准备,信号处理函数的注册sigaction()来实现。
B、在主进程的main()中,通过类似于while(!fQuit)的逻辑来检测那个flag变量,一旦fQuit在signal handler function中被置为true,则主进程退出while()循环,接下来就是一些释放资源或dump进程当前状态或记录日志的动作,完成这些后,主进程退出。
栗子:优雅退出go守护进程
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
//创建监听退出chan
c := make(chan os.Signal)
//监听指定信号 ctrl+c kill
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM,
syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
go func() {
for s := range c {
switch s {
case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
fmt.Println("Program Exit...", s)
GracefullExit()
case syscall.SIGUSR1:
fmt.Println("usr1 signal", s)
case syscall.SIGUSR2:
fmt.Println("usr2 signal", s)
default:
fmt.Println("other signal", s)
}
}
}()
fmt.Println("Program Start...")
sum := 0
for {
sum++
fmt.Println("sum:", sum)
time.Sleep(time.Second)
}
}
func GracefullExit() {
fmt.Println("Start Exit...")
fmt.Println("Execute Clean...")
fmt.Println("End Exit...")
os.Exit(0)
}
执行程序:
[homework@xxxx signal]$ go run monitor.go
Program Start...
sum: 1
sum: 2
sum: 3
sum: 4
sum: 5
sum: 6
^CProgram Exit... interrupt
Start Exit...
Execute Clean...
End Exit...
5、信号的订阅
信号的订阅是通过 channel实现的,每个os.Signal channel 都会收听自己相应的事件集。
关于Golang之信号处理的文章就写到这里,Done!
信号量2.1 共享变量
在理解信号量之前,先了解采用共享变量使用多线程会出现什么问题。下面是一个C代码片段
1for (i=0; i<niters; i++){
2 cnt ++;
3}
cnt
1 movq (%rdi), %rcx
2 testq %rcx, %rcx
3 jle .L2
4 movl $0, %eax
5.L3:
6 movq cnt(%rip), %rdx
7 addq %eax
8 movq %eax, cnt(%rip)
9 addq $1, %rax
10 cmpq %rcx, %rax
11 jne .L3
12.L2
6-8cntcntcntcntcntcnt++a7b6bacntcnt
DijkstraPV
sPs1ssVs1PsV
Posix
1#include <semaphore.h>
2int sem_init(sem_t *sem, 0, unsigned int value);
3int sem_wait(sem_t *s); /*P(s)*/
4int sem_post(sem_t *s); /*P(s)*/
那么如何使用信号量是的2.1小节出现同步问题解决呢?首先定义全局信号量
1volatile long cnt = 0; /* global variable */
2sem_t mutex; /*global semaphore*/
1
1sem_init(&mutex, 0, 1);
最后使用信号量操作函数将临界区域代码包含起来
1for (i =0; i<niters; i++){
2 sem_wait(&mutex);
3 cnt++;
4 sem_post(&mutex);
5}
参考资料:
Go语言信号处理
https://zhuanlan.zhihu.com/p/128953024
信号量,锁和 golang 相关源码分析https://cloud.tencent.com/developer/article/1357636