Go Signal信号

news2024/11/15 18:32:26

目录

  • 信号
  • 信号的种类
    • 分类
    • 信号种类
  • Go Signal
    • handlers
    • handler
      • 信号的存储
      • 信号持有状态的获取
      • 信号持有状态的清空
    • Notify
    • Stop
    • Ignore & Reset
    • NotifyContext
  • 如何优雅的关闭web app
  • 优雅地重启

信号

信号(Signal)是Linux, 类Unix和其它POSIX兼容的操作系统中用来进程间通讯的一种方式。对于Linux系统来说,信号就是软中断,用来通知进程发生了异步事件。

当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。

例:

  1. 输入命令,在Shell下启动一个前台进程。
  2. 用户按下Ctrl-C,键盘输入产生一个硬件中断。
  3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行, CPU从用户态切换到内核态处理硬件中断。
  4. 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
  5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

Go 语言提供了对信号处理的包(os/signal)。

Go 中对信号的处理主要使用os/signal包中的两个方法:一个是notify方法用来监听收到的信号;一个是 stop方法用来取消监听。

Go信号通知机制可以通过往一个channel中发送os.Signal实现。

信号的种类

使用命令查看:kill -l

在这里插入图片描述

分类

  1. 非可靠信号:1~31号信号,信号可能会丢失
  2. 可靠信号:34~64号信号,信号不可能丢失

信号种类

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发

Go Signal

  1. Go中关于信号的处理主要集中于os/signal package中。
  2. os/signal中涉及的function主要有:Notify、Stop、Ignore、Reset、NotifyContext。
  3. os/signal中信号的存储在handlers。

handlers

var handlers struct {
	sync.Mutex
	m map[chan<- os.Signal]*handler
	ref [numSig]int64
	stopping []stopping
}

type stopping struct {
	c chan<- os.Signal
	h *handler
}

type handler struct {
	// numSig==65 一共65种信号
	// mask [3]uint32
	mask [(numSig + 31) / 32]uint32
}
  1. handlers包含了锁、具体存储channel及对应信号的map、信号接收map(用于确认信号是否需要开启/关闭)、待stop的channel
  2. Mutex锁用于handlers内的数据竞争管理。
  3. m中保存了信号需要发送的handler
  4. ref记录了每个信号的接收量
  5. stopping: 当信号被stop时映射channel到信号,不采用map是因为入口保留的只是很简洁的数据。需要1个独立的存储是因为需要m在任何时刻来对应ref,而且也需要保持对要被stop的channel的*handler值的追踪。

handler

  1. 当前所有系统的信号总数为65个,需要记录每个信号需要的状况,直接使用二进制可以极大的减少内存占用空间。

  2. 因此可以选用2个64位或3个32的数用于存储信号需要情况,多余的空间还可以用于以后的扩展。

  3. 3个32位数字相对于2个64位占用空间更小,因此采用3个32位数字用于存储信号需要状况。

type handler struct {
	mask [(numSig + 31) / 32]uint32
}

func (h *handler) want(sig int) bool {
	return (h.mask[sig/32]>>uint(sig&31))&1 != 0
}

func (h *handler) set(sig int) {
	h.mask[sig/32] |= 1 << uint(sig&31)
}

func (h *handler) clear(sig int) {
	h.mask[sig/32] &^= 1 << uint(sig&31)
}

信号的存储

(1)根据sig/32确定uint32在数组中的位置

(2)通过sig&31可以获取在uint32中的位置,左移相应位并设置对应位为1(或操作)

信号持有状态的获取

(1)根据sig/32确定uint32在数组中的位置

(2)通过sig&31可以获取在uint32中的位置,右移相应位,判断对应位是否为1(与操作)

信号持有状态的清空

(1)根据sig/32确定uint32在数组中的位置

(2)通过sig&31可以获取在uint32中的位置,右移相应位,通过异或清零(异或操作)

handler是1个[3]uint32数组,uint32的每位都可以可以存储对应的信号,意味着一个channel至多可以存储96个信号,当前所有系统的信号总数为65个,因此handler足以存储所有的信号。

因为信号要存储在数组中,因此通过sig/32确定uint32的位置,然后再根据设置/获取对应位的数据完成数据的存取。

Notify

  1. Notify用于将信号注册至对应channel,系统收到对应信号时会发送至此channel。
  2. channel一定要有足够的缓存接收信号,否则会因阻塞而导致信号发送失败。

Notify过程:

  1. 获取锁

  2. 检查当前channel是否已存入m,若没有,则创建新的handler存入。

  3. 若未指定信号,则依次将所有信号添加到handler中;否则将指定的信号依次添加到handler中

  4. 具体的添加过程如下:

    1. 检查信号,无效直接退出
    2. 检查信号是否已加入handler,没有的话将添加信号,若是全局第一次添加则启动信号监听循环
    3. 将对应信号的待接收数加1(handlers.ref[n]++)
  5. 释放锁

Stop

Stop用于取消channel对信号的监听。

Stop过程:

  1. 获取锁
  2. 若m中没当前channel,则退出并释放锁。
  3. m中存在当前channel的handler,先从m中移除channel。
  4. 遍历所有信号,若信号已在handler中注册,将待接收数减1,当接收数为0时,说明当前信号已不被Notify,关闭信号
  5. 存入stopping(此处为避免Stop时处于发送与关的竞争,选择发送后移除)
  6. 释放锁
  7. 等待stopping完成信号的发送
  8. 将当前channel的stopping移除
  9. 释放锁

Ignore & Reset

  1. Ignore用于全局忽略信号,忽略的信号,不会再被接收到
  2. Reset用于全局重置信号,重置后,可以接收到原已忽略的此信号

NotifyContext

NotifyContext相对Notify,多了context用以传递上下文,返回的stop可用以释放相关资源,内部调用的仍是Notify。

如何优雅的关闭web app

// +build go1.8

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "Welcome Gin Server")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	go func() {
		// 开启一个goroutine启动服务
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时
	quit := make(chan os.Signal, 1) // 创建一个接收信号的通道
	// kill 默认会发送 syscall.SIGTERM 信号
	// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
	// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
	// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)  // 此处不会阻塞
	<-quit  // 阻塞在此,当接收到上述两种信号时才会往下执行
	log.Println("Shutdown Server ...")
	// 创建一个5秒超时的context
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown: ", err)
	}

	log.Println("Server exiting")
}

按下Ctrl+C时会发送syscall.SIGINT来通知程序优雅关机

优雅地重启

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/fvbock/endless"
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "hello gin!")
	})
	// 默认endless服务器会监听下列信号:
	// syscall.SIGHUP,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM和syscall.SIGTSTP
	// 接收到 SIGHUP 信号将触发`fork/restart` 实现优雅重启(kill -1 pid会发送SIGHUP信号)
	// 接收到 syscall.SIGINT或syscall.SIGTERM 信号将触发优雅关机
	// 接收到 SIGUSR2 信号将触发HammerTime
	// SIGUSR1 和 SIGTSTP 被用来触发一些用户自定义的hook函数
	if err := endless.ListenAndServe(":8080", router); err!=nil{
		log.Fatalf("listen: %s\n", err)
	}

	log.Println("Server exiting")
}

通过执行kill -1 pid命令发送syscall.SIGINT来通知程序优雅重启,具体做法如下:

  1. 打开终端,go build -o graceful_restart编译并执行./graceful_restart,终端输出当前pid(假设为43682)
  2. 将代码中处理请求函数返回的hello gin!修改为hello gin2i!,再次编译go build -o graceful_restart
  3. 打开一个浏览器,访问127.0.0.1:8080/,此时浏览器白屏等待服务端返回响应。
  4. 在终端迅速执行kill -1 43682命令给程序发送syscall.SIGHUP信号
  5. 等第3步浏览器收到响应信息hello gin!后再次访问127.0.0.1:8080/会收到hello gin2i!的响应。
  6. 在不影响当前未处理完请求的同时完成了程序代码的替换,实现了优雅重启。

但是需要注意的是,此时程序的PID变化了,因为endless 是通过fork子进程处理新请求,待原进程处理完当前请求后再退出的方式实现优雅重启的。所以当你的项目是使用类似supervisor的软件管理进程时就不适用这种方式了。

无论是优雅关机还是优雅重启归根结底都是通过监听特定系统信号,然后执行一定的逻辑处理保障当前系统正在处理的请求被正常处理后再关闭当前进程。使用优雅关机还是使用优雅重启以及怎么实现,这就需要根据项目实际情况来决定了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399417.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hibernate学习(二)

hibernate学习&#xff08;二&#xff09; 一、hibernate常见配置&#xff1a; 1.XML提示问题配置&#xff1a; 二、hibernate映射的配置&#xff1a; &#xff08;1&#xff09;class标签的配置&#xff1a; 标签用来建立类与表之间的映射关系属性&#xff1a; 1.name&…

Clickhouse中bitmap介绍以及计算留存Demo

前言 参考了腾迅的大数据分析-计算留存,能够根据用户自定义属性,以及玩家行为进行留存的计算。最初计算留存的方法使用的是clickhosue自带的rentention函数,使用这个函数不用关注太多细节,只需要把留存条件放入函数即可。但是这个如果需要关联用户属性,就比较麻烦了。因此…

如何应对危害机房安全的这几个常见要素?

随着现代化进程的推进&#xff0c;各行业对计算机的依赖性日益增高&#xff0c;计算机系统已经成为业务系统的重要组成部分。 在这种情况下&#xff0c;一旦机房设备出现故障&#xff0c;就会影响机房的正常运行&#xff0c;造成严重后果。尤其是银行、证券、海关等需要实时数据…

前端学习第三阶段-第1、2章 JavaScript 基础语法

01第一章 JavaScript网页编程课前导学 1-1 JavaScript网页编程课前导学 02第二章 JavaScript 基础语法 2-1 计算机基础和Javascript介绍 01-计算机基础导读 02-编程语言 03-计算机基础 04-JavaScript初识导读 05-初始JavaScript 06-浏览器执行JS过程 07-JS三部分组成 08-JS三种…

【微信小程序】-- 页面处理总结(三十一)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

SSL/TLS协议工作原理

SSL/TLS协议工作原理 SLL/TLS协议工作在应用层和传输层之间&#xff0c;应用层数据需要经过SSL/TLS层的加密之后才会发送到传输层。SSL/TLS协议有两个重要协议&#xff1a;握手协议、记录协议。 1. 握手协议 TCP三次握手完成后&#xff0c;才能进行SSL/TLS的握手。 因为&#…

SNAP中根据入射角和干涉图使用波段计算器计算垂直形变--以门源地震为例

SNAP中根据入射角和相干图使用波段计算器计算垂直形变--以门源地震为例0 写在前面1 具体步骤1.1 准备数据1.2 在SNAP中打开波段运算Band Maths1.3 之前计算的水平位移displacement如下图数据的其他处理请参考博文在SNAP中用sentinel-1数据做InSAR测量&#xff0c;以门源地震为例…

【JavaEE初阶】第四节.文件操作 和 IO (下篇)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言三、文件内容的操作 3.1 读文件 3.1.1 使用字节流读文件 3.2 写文件 3.2.1 使用字节流写文件 …

信捷 XDH Ethercat A_VELMOVE

本文描述信捷 EthercatA_VELMOVE指令&#xff0c;以设定的速度持续运行 上图中&#xff0c;在M100的上升沿&#xff0c;执行A_VELMOVE指令。A_VELMOVE HD100 D100 M101 K0HD100输入参数起始地址 &#xff0c;HD118输入参数末尾地址HD100~HD103,双精度浮点数&#xff08;64位&am…

【剧前爆米花--爪哇岛寻宝】Java实现无头单向非循环链表和无头双向链表与相关题目

作者&#xff1a;困了电视剧 专栏&#xff1a;《数据结构--Java》 文章分布&#xff1a;这是关于数据结构链表的文章&#xff0c;包含了自己的无头单向非循环链表和无头双向链表实现简单实现&#xff0c;和相关题目&#xff0c;想对你有所帮助。 目录 无头单向非循环链表实现 …

Web Components学习(1)

一、什么是web components 开发项目的时候为什么不手写原生 JS&#xff0c;而是要用现如今非常流行的前端框架&#xff0c;原因有很多&#xff0c;例如&#xff1a; 良好的生态数据驱动试图模块化组件化等 Web Components 就是为了解决“组件化”而诞生的&#xff0c;它是浏…

4.Elasticsearch深入了解

4.Elasticsearch深入了解[toc]1.Elasticsearch架构原理Elasticsearch的节点类型在Elasticsearch主要分成两类节点&#xff0c;一类是Master&#xff0c;一类是DataNode。Master节点在Elasticsearch启动时&#xff0c;会选举出来一个Master节点。当某个节点启动后&#xff0c;然…

A Star算法最通俗易懂的一个版本

01-概述虽然掌握了 A* 算法的人认为它容易&#xff0c;但是对于初学者来说&#xff0c; A* 算法还是很复杂的。02-搜索区域(The Search Area)我们假设某人要从 A 点移动到 B 点&#xff0c;但是这两点之间被一堵墙隔开。如图 1 &#xff0c;绿色是 A &#xff0c;红色是 B &…

猿创征文 | re:Invent 朝圣之路:“云“行业风向标

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; AWS 亚马逊云科技re:Invent全球大会 2022年亚马逊云科技re:Invent全球大会震撼来袭&#xff0c;即将于北京时间11月30日-12月2日在美国内华达州&#xff0c;拉斯维加斯…

【MySQL】将 CSV文件快速导入 MySQL 中

【MySQL】将 CSV文件快速导入 MySQL 中方法一&#xff1a;使用navicat等软件的导入向导如果出现中文乱码方法二&#xff1a;命令行导入&#xff08;LOAD DATA INFILE SQL&#xff09;一般来说&#xff0c;将csv文件导入mysql数据库有两种办法&#xff1a; 使用 navicat、workbe…

易优cms links 友情链接调用标签

links 友情链接调用 【基础用法】 标签&#xff1a;links 描述&#xff1a;用于获取友情链接列表。 用法&#xff1a; {eyou:links typetext loop30 titlelen15} <a href{$field.url} {$field.target} {$field.nofollow}>{$field.title}</a> {/eyou:links} …

模型杂谈:快速上手元宇宙大厂 Meta “开源泄露”的大模型(LLaMA)

本篇文章聊聊如何低成本快速上手使用 Meta&#xff08;Facebook&#xff09;的开源模型 LLaMA。 写在前面 在积累点赞&#xff0c;兑现朋友提供的显卡算力之前&#xff0c;我们先来玩玩“小号的”大模型吧。我相信 2023 年了&#xff0c;应该不需要再赘述如何使用 Docker 干净…

Go的 context 包的使用

文章目录背景简介主要方法获得顶级上下文当前协程上下文的操作创建下级协程的Context场景示例背景 在父子协程协作过程中, 父协程需要给子协程传递信息, 子协程依据父协程传递的信息来决定自己的操作. 这种需求下可以使用 context 包 简介 Context通常被称为上下文&#xff…

AUTOSAR知识点Com(六):CANIf规范时序图

目录 1、概述 2、规范时序 2.1、Transmit request (single CAN Driver) 2.2、Transmit request (multiple CAN Drivers) 2.3、Transmit confirmation (interrupt mode) ​2.4、Transmit confirmation (polling mode) 2.5、Transmit confirmation (with buffering) 2.6、T…

国际物流是怎么给货物打包的

国际物流常见的包装方法有好几种&#xff0c;而且国际物流公司针对物品的包装都是格外重视&#xff0c;国际物流公司会依据物品的不同种类搭配不同的包装&#xff0c;便于物品完好无损的到的目的地。包装无论大小形态&#xff0c;它的核心目的是为了保护性、分辨性与便利性&…