前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang语言情怀-第40期 Go 语言设计模式 信号

Golang语言情怀-第40期 Go 语言设计模式 信号

作者头像
李海彬
发布2021-03-09 10:55:00
5960
发布2021-03-09 10:55:00
举报
文章被收录于专栏:Golang语言社区Golang语言社区

1、使用场景

实际项目中,我们希望修改了配置文件后,但又不想通过重启进程让它重新加载配置文件,可以使用signal的方式进行信号传递,或者我们希望通过信号控制,实现一种优雅的退出方式。Golang为我们提供了signal包,实现信号处理机制,允许Go 程序与传入的信号进行交互。

2、常用的Term信号

3、简单的栗子

代码语言:javascript
复制
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信号

代码语言:javascript
复制
[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

代码语言:javascript
复制
#此时,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守护进程

代码语言:javascript
复制
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)
}

执行程序:

代码语言:javascript
复制
[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代码片段

代码语言:javascript
复制
1for (i=0; i<niters; i++){
2    cnt ++;
3}

cnt为全局变量,一个线程执行该代码片段的时候的汇编代码如下:

代码语言:javascript
复制
 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

2.2 信号量其中6-8行分别对应对应着加载cnt,更新cnt和存储cnt。将cnt变量从内存位置读出,加载到CPU寄存器中,在CPU运算器中加1,然后存储到cnt的内存位置。虽然代码中cnt++只有一行,但是转换为汇编代码的时候不只有一个操作,也就是说该语句不是原子操作。如果多个线程同时执行代码,按照之前的条件,不对CPU的执行顺序做任何假设,如果其中线程a在执行7行汇编代码,而线程b执行6行汇编代码,那么b将"看不到"线程a对全局变量cnt加1的操作,那么每次执行的结果cnt也不完全一致。

计算机领域先驱Dijkstra提出经典的解决上述问题的方法:信号量(semaphore)。它是一个非负整数的全局变量。而且该变量只能有两个特殊操作来处理: PV

  • P(s):如果s非零,那么Ps1,并且立即返回。如果s为零,那么就挂起这个线程,知道s为非零。
  • V(s): V操作将s1。如果有任何线程阻塞在P操作等待s非零,那么V将重启其中线程中的一个。

Posix标准定义需要操作信号量的函数

代码语言:javascript
复制
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小节出现同步问题解决呢?首先定义全局信号量

代码语言:javascript
复制
1volatile long cnt = 0; /* global variable */
2sem_t mutex; /*global semaphore*/

初始化信号量,在这里初始值为1

代码语言:javascript
复制
1sem_init(&mutex, 0, 1);

最后使用信号量操作函数将临界区域代码包含起来

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言情怀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、使用场景
  • 2、常用的Term信号
  • 3、简单的栗子
  • 4、优雅的退出守护进程
  • 5、信号的订阅
  • 信号量
    • 2.1 共享变量
    • 信号量,锁和 golang 相关源码分析
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档