Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
Go语言中实现优雅且高效的事件监听与服务关闭_创想鸟

Go语言中实现优雅且高效的事件监听与服务关闭

go语言中实现优雅且高效的事件监听与服务关闭

本文探讨Go语言中实现可关闭事件循环的惯用方法。针对传统方案中因SetDeadline导致的关闭延迟问题,我们提出一种更高效的模式。通过利用net.Listener.Close()在被阻塞的Accept()调用中触发错误返回的特性,可以实现服务监听协程的即时退出,从而避免不必要的超时等待,确保服务快速而优雅地关闭。

传统事件循环模式及其局限性

在Go语言中,实现一个监听网络连接的服务器通常涉及一个循环,不断调用net.Listener.Accept()来接受新连接。当需要实现服务的优雅关闭时,一个常见的思路是使用select语句结合一个关闭通道(closeChan)来接收关闭信号。然而,Accept()是一个阻塞操作,如果直接将其放入select的default分支,会导致CPU空转。为了避免这种情况,有时会配合net.Listener.SetDeadline()设置一个超时,使得Accept()在指定时间后返回错误,从而允许select有机会检查closeChan。

以下是这种模式的一个示例:

type Server struct {    listener  net.Listener    closeChan chan struct{} // 使用空结构体更节省内存    routines  sync.WaitGroup}func (s *Server) Serve() {    s.routines.Add(1)    defer s.routines.Done()    defer s.listener.Close() // 确保listener在协程退出时关闭    for {        select {        case <-s.closeChan:            // 收到关闭信号,准备退出            fmt.Println("Server received close signal, shutting down...")            return // 退出Serve协程        default:            // 设置Accept的超时,以避免长时间阻塞            s.listener.SetDeadline(time.Now().Add(2 * time.Second))            conn, err := s.listener.Accept()            if err != nil {                // 检查是否是超时错误,如果是则继续循环                if netErr, ok := err.(net.Error); ok && netErr.Timeout() {                    continue                }                // 其他错误(如listener已关闭),则退出                fmt.Printf("Error accepting connection: %vn", err)                return            }            // 处理连接的逻辑,通常在一个新的goroutine中            s.routines.Add(1)            go func(conn net.Conn) {                defer s.routines.Done()                defer conn.Close()                // handle conn logic                fmt.Printf("Handling connection from %sn", conn.RemoteAddr())                time.Sleep(1 * time.Second) // 模拟处理            }(conn)        }    }}func (s *Server) Close() {    close(s.closeChan) // 发送关闭信号    s.routines.Wait()  // 等待所有协程完成    fmt.Println("All server routines finished.")}

这种实现方式的缺点在于,当调用Close()函数发送关闭信号时,Serve()协程并不会立即退出。它必须等待当前的SetDeadline超时(例如2秒)结束后,Accept()返回错误或超时,select语句才能再次执行并检查closeChan。这意味着服务的关闭时间至少会比实际需要的时间多出这个超时时长,影响了服务的响应性和优雅性。

Go 惯用事件监听与优雅关闭

Go语言提供了一种更符合其并发哲学且更高效的优雅关闭机制。其核心思想是利用net.Listener.Close()方法的一个关键特性:当一个net.Listener被关闭时,所有当前正在阻塞等待Accept()调用的协程都会立即解除阻塞,并返回一个错误(通常是net.ErrClosed或类似“use of closed network connection”的错误)。我们可以利用这个特性来作为监听协程的退出信号,从而避免设置人工超时。

立即学习“go语言免费学习笔记(深入)”;

以下是改进后的惯用模式:

import (    "fmt"    "net"    "sync"    "time")type ImprovedServer struct {    listener  net.Listener    closeOnce sync.Once // 确保Close操作只执行一次    routines  sync.WaitGroup    // closeChan用于在外部触发关闭,但Serve内部不再直接监听它    // 相反,它用于通知一个专门的goroutine来关闭listener    closeChan chan struct{} }// NewImprovedServer 创建一个新的服务器实例func NewImprovedServer(addr string) (*ImprovedServer, error) {    lis, err := net.Listen("tcp", addr)    if err != nil {        return nil, fmt.Errorf("failed to listen: %w", err)    }    return &ImprovedServer{        listener:  lis,        closeChan: make(chan struct{}),    }, nil}func (s *ImprovedServer) Serve() {    s.routines.Add(1)    defer s.routines.Done()    // 启动一个独立的goroutine来监听关闭信号并关闭listener    go func() {        <-s.closeChan // 阻塞直到接收到关闭信号        fmt.Println("Closing listener...")        s.listener.Close() // 关闭listener,这将使Accept()立即返回错误    }()    fmt.Printf("Server listening on %sn", s.listener.Addr())    for {        conn, err := s.listener.Accept()        if err != nil {            // 检查错误是否是由于listener关闭引起的            if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {                fmt.Println("Listener closed, exiting Serve routine.")                return // Listener已关闭,退出Serve协程            }            // 针对其他非关闭引起的错误,进行日志记录或处理            fmt.Printf("Error accepting connection: %vn", err)            // 根据实际情况,可能需要决定是继续循环还是退出            // 这里我们假设其他错误也应导致退出,或者在重试策略后退出            return         }        // 处理连接的逻辑,通常在一个新的goroutine中        s.routines.Add(1)        go func(conn net.Conn) {            defer s.routines.Done()            defer conn.Close()            // handle conn logic            fmt.Printf("Handling connection from %sn", conn.RemoteAddr())            time.Sleep(1 * time.Second) // 模拟处理        }(conn)    }}func (s *ImprovedServer) Close() {    s.closeOnce.Do(func() {        fmt.Println("Initiating server shutdown...")        close(s.closeChan) // 发送关闭信号给专门的goroutine        s.routines.Wait()  // 等待所有协程完成,包括Serve和所有连接处理协程        fmt.Println("Improved server gracefully shut down.")    })}func main() {    server, err := NewImprovedServer(":8080")    if err != nil {        fmt.Fatalf("Failed to create server: %v", err)    }    go server.Serve()    // 模拟服务器运行一段时间后关闭    time.Sleep(5 * time.Second)     server.Close()    // 确保main协程不会立即退出,以便观察输出    time.Sleep(1 * time.Second) }

在这个改进的模式中:

Serve()协程内部不再使用select语句和SetDeadline。它只专注于一个任务:不断地调用Accept()。我们启动了一个独立的goroutine,专门负责监听s.closeChan。一旦s.closeChan收到信号(通过close(s.closeChan)),这个goroutine就会执行s.listener.Close()。s.listener.Close()的调用会立即解除Serve()协程中阻塞的s.listener.Accept(),使其返回一个错误。Serve()协程在Accept()返回错误后,会检查错误类型。如果是由于listener关闭引起的错误,它就优雅地退出循环。

核心原理与优势

这种方法的优势在于:

零延迟关闭: 当Close()被调用时,listener.Close()会立即生效,Accept()会立即返回错误,Serve()协程可以瞬间响应并退出,避免了SetDeadline带来的额外等待时间。职责分离: Serve()协程只负责接受连接,而关闭逻辑则由另一个专门的协程或Close方法直接触发listener.Close()来完成。这种职责分离使得代码更清晰、更易于理解和维护。Go语言惯用表达: 利用了Go标准库net包的内置行为,是Go并发编程中处理资源关闭的常见且推荐模式。简洁性: Serve()循环内部不再需要复杂的select逻辑,使得核心逻辑更加简洁。

实现细节与注意事项

错误处理: 在Accept()返回错误时,务必检查错误类型。net.OpError是net包中常见的操作错误类型,可以通过其Err字段进一步判断具体的错误原因。当listener关闭时,常见的错误字符串是”use of closed network connection”或net.ErrClosed(虽然net.ErrClosed在net包中不是公开导出的,但其错误字符串通常可识别)。资源管理: sync.WaitGroup仍然是管理所有并发协程(包括Serve()协程和所有连接处理协程)生命周期的关键。在Close()方法中调用s.routines.Wait(),可以确保在程序完全退出前,所有活跃的连接处理和监听协程都已安全终止。sync.Once: 在Close()方法中使用sync.Once可以确保关闭逻辑只被执行一次,防止重复关闭导致的潜在问题。共享资源保护: 如果在关闭过程中,某些资源需要被保护以防止并发读写冲突(例如,在关闭前需要清空一个连接列表),则可能需要使用sync.Mutex。然而,在许多情况下,通过精心设计协程间的通信和资源所有权,可以避免显式使用互斥锁,从而降低复杂性并提高性能。例如,如果连接处理协程只操作其私有数据或通过通道与主协程通信,则通常不需要额外的锁。Close()函数的直接优化: 在某些非常简单的情况下,如果Close()函数除了关闭listener外没有其他复杂的资源清理工作,甚至可以直接在Close()方法中调用s.listener.Close(),而无需额外的closeChan和goroutine。但上述模式提供了更好的通用性和扩展性,适用于更复杂的关闭场景。

总结

在Go语言中,实现一个高效且优雅的事件监听器并支持快速关闭,应充分利用net.Listener.Close()方法在Accept()调用中触发错误返回的特性。通过将Accept()循环与关闭listener的逻辑分离到不同的协程中,可以实现零延迟的服务关闭,避免了传统方案中因SetDeadline带来的不必要等待。这种模式不仅符合Go的并发哲学,也使得代码更加简洁、健壮和易于维护。

以上就是Go语言中实现优雅且高效的事件监听与服务关闭的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1408374.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Go语言数值运算陷阱:理解整数除法与类型转换
上一篇 2025年12月16日 00:01:51
Go语言并发编程:实现高效事件监听与优雅关闭
下一篇 2025年12月16日 00:02:13

相关推荐

  • 利用蝴蝶号搭建多账号无人直播系统的完整方案

    利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案

    搭建多账号无人直播系统并非一键操作,而是通过“蝴蝶号”实现自动化流程。首先,“蝴蝶号”负责多账号的生命周期管理,包括登录、状态维护、ip代理分配和设备指纹模拟;其次,内容调度系统决定直播内容及播放时间,可为预录视频或动态生成流;再次,推流引擎将内容实时推送至平台,推荐使用ffmpeg结合python…

    2026年9月21日 用户投稿
    000
  • 锚定AI终端存储市场,康盈半导体连发三款新品

    锚定AI终端存储市场,康盈半导体连发三款新品锚定AI终端存储市场,康盈半导体连发三款新品锚定AI终端存储市场,康盈半导体连发三款新品锚定AI终端存储市场,康盈半导体连发三款新品

    ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 三款新品聚焦AI存储需求 在最新举行的产品发布会上,康盈半导体正式推出三款专为AI应用场景打造的全新存储解决方案,覆盖嵌入式存储与高性能固态硬盘等多个品类,旨在满足多样化AI终端对高效、紧凑、低…

    2026年9月21日 用户投稿
    100
  • linux内核定时器实验

    linux内核定时器实验linux内核定时器实验linux内核定时器实验linux内核定时器实验

    大家好,又见面了,我是你们的朋友全栈君。 文章目录一、linux时间管理和内核定时器简介1.内核时间管理简介2.内核定时器简介1.init_timer 函数2.add_timer 函数3.del_timer 函数4.del_timer_sync 函数5.mod_timer 函数3.linux内核短延…

    2026年9月21日 用户投稿
    000
  • WordPress插件定制:使用Filter Hook修改邮件通知接收者

    本教程将指导您如何在WordPress中利用Filter Hook定制插件行为,特别是修改第三方插件的邮件通知接收者。我们将详细讲解如何识别目标Filter、理解其参数,并正确编写回调函数来拦截或修改数据,以实现自定义的邮件发送逻辑,避免因参数不匹配导致的错误。 WordPress Hook机制概览…

    2026年9月21日
    100
  • 谷歌浏览器官方在线访问 最新版Chrome官网登录

    谷歌浏览器官方在线访问入口是https://www.google.cn/chrome/,提供简洁界面、跨设备同步、高效内核、安全防护和丰富扩展生态。 谷歌浏览器官方在线访问入口在哪里?这是不少网友都关注的,接下来由PHP小编为大家带来最新版Chrome官网登录地址,想要获取纯净浏览体验的网友一起随小…

    2026年9月21日
    200
  • Swoole如何实现一个UDP服务器

    答案:使用Swoole可轻松创建高性能UDP服务器。通过new SwooleServer()设置UDP套接字,监听Packet事件接收数据,利用sendto()回复客户端;结合set()配置worker_num等参数优化性能,配合PHP UDP客户端测试通信,适用于高并发、低延迟场景。 使用Swoo…

    2026年9月21日
    100
  • MySQL执行计划中的Extra字段代表什么_怎么看优化空间?

    MySQL执行计划中的Extra字段代表什么_怎么看优化空间?MySQL执行计划中的Extra字段代表什么_怎么看优化空间?MySQL执行计划中的Extra字段代表什么_怎么看优化空间?MySQL执行计划中的Extra字段代表什么_怎么看优化空间?

    在 mysql 查询优化中,执行计划的 extra 字段用于说明查询执行时的额外操作,常见的值包括:1. using filesort 表示需要额外排序,应尽量通过建立索引避免;2. using temporary 表示使用了临时表,常见于 group by 或复杂 join,需优化减少其使用;3.…

    2026年9月21日 用户投稿
    100
  • 如何通过tracert命令追踪数据包从本地到目标服务器的完整路径?

    打开命令提示符,输入cmd并回车;2. 执行tracert 目标地址命令追踪路径;3. 查看每跳响应时间与IP,分析延迟变化定位网络瓶颈;4. 注意部分节点可能因防火墙不响应导致超时。 使用 tracert(Windows 系统)命令可以追踪数据包从你的计算机到目标服务器所经过的每一跳网络节点,帮助…

    2026年9月21日
    1000
  • windows怎么清除dns缓存_dns缓存刷新命令详解

    1、刷新DNS缓存可解决网页无法加载或域名解析错误问题。2、通过命令提示符执行ipconfig /flushdns清除系统DNS缓存。3、以管理员身份运行命令提示符并重启DNS Client服务(net stop dnscache和net start dnscache)恢复服务功能。4、在Chrom…

    2026年9月21日
    100
  • 如何在Java中理解Java I/O与NIO机制

    传统I/O是阻塞式流模型,适用于低并发场景;NIO基于缓冲区与通道,支持非阻塞和多路复用,适合高并发网络应用,核心区别在于线程模型与资源利用率。 Java中的I/O(输入/输出)与NIO(New I/O)是处理数据读写的核心机制,理解它们的区别和使用场景对开发高性能应用至关重要。传统I/O基于流模型…

    2026年9月21日
    100
  • JavaScript中的尾调用优化(TCO)在ES6中如何工作?

    尾调用是指函数的最后一个动作调用另一个函数,ES6引入尾调用优化以重用栈帧、避免内存溢出,支持真正的尾递归,如阶乘函数通过累积参数实现。 尾调用优化(Tail Call Optimization, TCO)是ES6引入的一项语言特性,目的是在特定条件下重用函数调用栈帧,避免不必要的内存增长,从而支持…

    2026年9月21日
    200
  • 为什么iPhoneSE2022屏幕无响应如何强制重启?快速按音量键后长按电源键

    首先尝试强制重启,若无效则检查充电状态,最后可通过恢复模式重装系统。具体为:1. 按音量+、音量-后长按电源键10秒以上;2. 充电15分钟观察是否响应;3. 连电脑进入恢复模式恢复系统。 如果您尝试唤醒或操作您的iPhone SE(2022款),但屏幕无响应或显示黑屏,可能是系统临时卡死或软件冲突…

    2026年9月21日
    100
  • 抖音蝴蝶号无人直播带货操作流程及注意事项

    抖音蝴蝶号无人直播带货操作流程及注意事项抖音蝴蝶号无人直播带货操作流程及注意事项抖音蝴蝶号无人直播带货操作流程及注意事项抖音蝴蝶号无人直播带货操作流程及注意事项

    “抖音蝴蝶号无人直播带货”是一种通过自动化或半自动化技术实现的直播销售模式。①其核心在于摆脱真人主播限制,实现24小时不间断直播,提升效率与流量利用率;②关键步骤包括明确账号定位与商品选择、准备高质量且丰富的内容素材、利用虚拟人或预录内容实现直播推流、结合智能客服模拟评论区互动;③优势在于降低人力成…

    2026年9月21日 用户投稿
    600
  • 音乐文件占用空间太多怎么办_音乐文件占用空间太多如何整理详细指南

    解决音乐文件占空间问题的关键是压缩与整理:先用软件或在线工具降低比特率压缩体积,再按场景分类、利用元数据自动归集,并通过听歌片段和BPM判断保留内容,避免重复与误删。 音乐文件占空间太多,核心解决办法就两条:一是压缩单个文件体积,二是通过有效分类管理提升使用效率。直接删歌不是长久之计,学会整理和优化…

    2026年9月21日
    000
  • 升级X86架构性能大提升!极空间Z2 Ultra图赏

    升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏升级X86架构性能大提升!极空间Z2 Ultra图赏

    10月23日,极空间正式推出全新双盘位nas产品——极空间z2 ultra,官方售价为1899元,参与国家补贴后仅需1457元,性价比进一步提升。 此次发布的Z2 Ultra最大的亮点在于采用X86架构处理器,相较以往使用的ARM平台,性能实现飞跃式提升,运行速度显著加快。更重要的是,新架构对Doc…

    2026年9月21日 用户投稿
    300
  • 数据库分库分表(Sharding)策略

    在现代应用程序中,随着数据量的增长,单一数据库的性能和容量往往难以满足需求。这时,数据库分库分表(Sharding)策略就成了一个关键的解决方案。那么,如何设计和实现一个有效的分库分表策略呢?让我们深入探讨一下。 在我的职业生涯中,我曾多次参与大型项目的数据库优化,其中分库分表是常见的挑战之一。我记…

    2026年9月21日
    000
  • 如何在Java中实现个人财务管理工具

    首先设计Transaction、FinanceManager和Budget核心类,实现交易记录、统计分析与预算控制功能,通过ArrayList管理数据,使用LocalDate处理日期,结合ObjectOutputStream持久化存储,初期采用Scanner构建控制台菜单实现增删查改与报表展示,后期…

    2026年9月21日
    100
  • X旗下Grok上线即时语音搜索,挑战Google引领搜索新方向

    近日,x平台旗下的ai助手grok正式推出了“即时语音搜索”功能。用户现在可以通过语音直接提问,触发实时网页检索,并迅速获得整合后的精准答案。此举意在优化信息获取流程,推动人机交互向更自然、高效的方向演进。 该语音搜索模式实现了“即说即搜即答”的流畅体验。例如,当用户提出“星舰发射的具体时间是什么?…

    2026年9月21日
    200
  • Laravel应用的安全审计(Security Audit)方法

    进行安全审计对laravel应用至关重要,因为它能发现并修复安全漏洞,提升整体安全性和用户信任度。具体方法包括:1. 代码审查,确保无未过滤输入和弱密码;2. 配置文件安全性,保护敏感信息;3. 依赖管理,更新第三方包;4. 用户认证和授权,防止未授权访问;5. 日志和监控,检测异常行为。 在讨论L…

    2026年9月21日
    200
  • Laravel 8 登录后重定向到仪表盘的全面指南

    本文深入探讨了 Laravel 8 中用户登录后重定向到仪表盘的多种策略。我们将详细解析默认的重定向机制,包括 LoginController 和 RedirectIfAuthenticated 中间件,并重点介绍如何通过自定义登录逻辑实现精确的重定向控制,同时提供示例代码和常见问题排查建议,确保用…

    2026年9月21日
    100

发表回复

登录后才能评论
关注微信