Go语言中的锁与管道的运用
目录
1.前言
2.锁解决方案
3.管道解决方案
4.总结
1.前言
在写H5小游戏的时候,由于需要对多个WebSocket连接进行增、删、查的管理和对已经建立连接的WebSocket通过服务端进行游戏数据交换的需求。于是定义了一个全局的map集合进行连接的管理,让所有的协程共享操作同一个map集合,进行各种WebSocket连接的操作。由于多个协程操作共享同一块内容,这时候就会遇到数据竞争和并发访问。
H5小游戏介绍:基于WebSocket通信的H5小游戏总结-CSDN博客
解决并发问题的常见方法有两种:
- 在结构体中增加 sync.RWMutex字段,每一个协程操作map集合的时候进行加锁操作,操作结束后进行解锁操作,保证同时只有一个协程操作map,避免并发问题。但是频繁的加锁和解锁操作会成为后期的性能瓶颈。
- 使用管道进行通信,由于管道本身就是线程安全的,所以我们在操作层面无需进行加锁和解锁操作,只需要另启一个协程进行管道的读取,如果有数据写入则进行map操作。我们在需要对map进行操作的时候向管道中写入数据即可。
由于第一次在项目中遇到并发问题,一开始没有意识到多个协程对同一个map进行操作需要保证线程安全。在老师查看代码后,说出map是线程不安全的时候,才意识到需要进行加锁操作或者其他方案来保证线程安全。
2.锁解决方案
第一版本的代码——加锁,解锁保证线程安全
在结构体中的ClientsMap进行操作的时候进行加锁和解锁的操作,保证线程安全。
// HupCenter ---使用锁,操作一个多线程共享的Map---//
type HupCenter struct {//第一个string-roomId 第二个string-userIdClientsMap map[string]map[string]*Client `json:"-"` mutex sync.RWMutex
}// JoinHub 写操作 --将连接加入中心 前提RoomId不为空, 加入房间的时候需要检测当前房间里面的人数
func (h *HupCenter) JoinHub(c *Client) (flag bool) {h.mutex.Lock()defer h.mutex.Unlock()//先查询是否存在此一个roomId keyif myMap, ok := c.Hub.ClientsMap[c.User.RoomId]; ok { //有,加入房间//检测人数if len(myMap) == 1 {myMap[c.User.UserId] = cflag = true}} else { //没有,创建房间myMap := make(map[string]*Client)myMap[c.User.UserId] = c //userIdc.Hub.ClientsMap[c.User.RoomId] = myMap //roomIdflag = true}return
}// DeleteFromHub 写操作 --逻辑删除 将传入的参数c从hub连接池中删除
func (h *HupCenter) DeleteFromHub(c *Client) {h.mutex.Lock()defer h.mutex.Unlock()if c.User.RoomId == "" {return}if value, ok1 := c.Hub.ClientsMap[c.User.RoomId]; ok1 {if _, ok2 := value[c.User.UserId]; ok2 {delete(value, c.User.UserId)}}if len(c.Hub.ClientsMap[c.User.RoomId]) == 0 {delete(c.Hub.ClientsMap, c.User.RoomId)}
}// QueryOtherUser 读操作 -- 根据当前用户寻找另一位用户,返回user对象
func (h *HupCenter) QueryOtherUser(c *Client) *Client {if roomMap, ok := h.ClientsMap[c.User.RoomId]; ok { //roomfor userId, user := range roomMap {if userId != c.User.UserId {return user}}}return nil
}
3.管道解决方案
使用锁是能够基本解决问题的,但是对于读写较为频繁的场景,读写锁可能会成为性能瓶颈,再加上自己对管道的运用不是很熟练,就开始思考如何使用channel去解决这一个并发的问题,代码如下:

type HupCenter struct {ClientsMap map[string]map[string]*Client `json:"-"` //第一个string-roomId 第二个string-userIdRegister chan *ClientUnRegister chan *Client
}// NewHub 初始化一个hub
func NewHub() *HupCenter {return &HupCenter{ClientsMap: make(map[string]map[string]*Client),Register: make(chan *Client, 1),UnRegister: make(chan *Client, 1),}
}// Run 用户向hub中的逻辑注册、删除、心跳检测全方法,在代码执行后,开始协程去执行Run方法
func (h *HupCenter) Run() {checkTicker := time.NewTicker(time.Duration(pkg.HeartCheckSecond) * time.Second)defer checkTicker.Stop()for {select {case client := <-h.Register://先查询是否存在此一个roomId keyif myMap, ok := client.Hub.ClientsMap[client.User.RoomId]; ok { //有,加入房间//检测人数if len(myMap) == 1 {myMap[client.User.UserId] = client}} else { //没有,创建房间myMap := make(map[string]*Client)myMap[client.User.UserId] = client //userIdclient.Hub.ClientsMap[client.User.RoomId] = myMap //roomId}fmt.Println("有人加入房间:", client.Hub.ClientsMap)case client := <-h.UnRegister:client.User.Close()if value, ok1 := client.Hub.ClientsMap[client.User.RoomId]; ok1 {if _, ok2 := value[client.User.UserId]; ok2 {delete(value, client.User.UserId)}}if len(client.Hub.ClientsMap[client.User.RoomId]) == 0 {delete(client.Hub.ClientsMap, client.User.RoomId)}case <-checkTicker.C:for _, roomMap := range h.ClientsMap {for _, client := range roomMap {if client.User.HealthCheck.Before(time.Now()) {h.UnRegister <- client}}}fmt.Println(time.Now().Format(time.DateTime), h.ClientsMap)}}
}// QueryOtherUser 读操作 -- 根据当前用户寻找另一位用户,返回user对象
func (h *HupCenter) QueryOtherUser(c *Client) *Client {if roomMap, ok := h.ClientsMap[c.User.RoomId]; ok { //roomfor userId, user := range roomMap {if userId != c.User.UserId {return user}}}return nil
}
在代码中,我们在结构体中定义了两个管道,一个管道接收注册的客户端对象(原JoinHub方法),另一个管道接收注销的客户端对象(原DeleteFormHub方法);
在Run方法中,我们创建了一个10秒的ticker对象,来进行客户端连接的心跳检测。之后使用for循环来执行select来监听多个管道,并执行对应的分支操作。select会随机挑选一个可执行的case语句,如果没有可执行的case,则进行等待。在本代码中如果没有注册、注销的操作,会每隔10秒进行一次心跳检测,并打印当前存活的客户端对象集合。
4.总结
在使用锁解决并发问题的时候,一定要使用延迟函数解锁,防止出现死锁问题;
在使用管道解决并发问题的时候,设计好管道的缓冲区和管道的关闭操作,防止出现死锁和向已经关闭的管道中写入数据,发生panic异常。
结语:学会一个知识点最好的方法就是在项目、实战中去应用它。
相关文章:
Go语言中的锁与管道的运用
目录 1.前言 2.锁解决方案 3.管道解决方案 4.总结 1.前言 在写H5小游戏的时候,由于需要对多个WebSocket连接进行增、删、查的管理和对已经建立连接的WebSocket通过服务端进行游戏数据交换的需求。于是定义了一个全局的map集合进行连接的管理,让所有…...
前端 - 基础 表单标签 -- 表单元素( input - type属性) 文本框和密码框
表单元素 : 在表单域中可以定义各种表单元素,这些表单元素就是允许用户在表单中输入或选择 的内容控件。 表单元素的外观也各不一样,有小圆圈,有正方形,也有方框,乱七八糟的,各种各样…...
关于MySQL模糊搜索不区分大小写
在我们日常使用ORM框架进行模糊查询时,会发现,搜索的结果是不区分关键字的英文大小写的,那这是为什么呢? 原因是MySQL的like本就不区分大小写;如果在建表的时候,没有设置好字段区分大小 //包含j和J的都会被…...
论文阅读——MoCo
Momentum Contrast for Unsupervised Visual Representation Learning 动量在数学上理解为加权移动平均: yt-1是上一时刻输出,xt是当前时刻输入,m是动量,不想让当前时刻输出只依赖于当前时刻的输入,m很大时࿰…...
ARM 寄存器学习:(一)arm多种模式下得寄存器
一.ARM7种状态以及每种状态的寄存器: ARM 处理器共有 7 种不同的处理器模式,在每一种处理器模式中可见的寄存器包括 15 个通用寄存器( R0~R14)、一个或两个(User和Sys不是异常模式,没有spsr寄存器)状态寄存器(cpsr和spsr&…...
【nfs报错】rpc mount export: RPC: Unable to receive; errno = No route to host
NFS错误 问题现象解决方法 写在前面 这两天搭建几台服务器,需要使用nfs服务,于是六台选其一做服务端,其余做客户端,搭建过程写在centos7离线搭建NFS共享文件,但是访问共享时出现报错:rpc mount export: RPC…...
备战蓝桥杯---牛客寒假训练营2VP
题挺好的,收获了许多 1.暴力枚举(许多巧妙地处理细节方法) n是1--9,于是我们可以直接暴力,对于1注意特判开头0但N!1,对于情报4,我们可以把a,b,c,d的所有取值枚举一遍,那么如何判断有…...
QCustomPlot-绘制X轴为日期的折线图
主要代码如下: void Widget::InitQLineXDateAddData() {customPlot new QCustomPlot(this);// 创建日期时间类型的刻度生成器QSharedPointer<QCPAxisTickerDateTime> dateTimeTicker(new QCPAxisTickerDateTime);dateTimeTicker->setDateTimeFormat(&quo…...
腾讯春招后端一面(算法篇)
前言: 哈喽大家好,前段时间在小红书和牛客上发了面试的经验贴,很多同学留言问算法的具体解法,今天就详细写个帖子回复大家。 因为csdn是写的比较详细,所以更新比较慢,大家见谅~~ 就题目而言,…...
Filebeat rpm方式安装及配置
一、使用服务器root用户、filebeat8.11.1版本,rpm安装方式进行安装 curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.11.1-x86_64.rpm sudo rpm -vi filebeat-8.11.1-x86_64.rpm 二、配置核心的采集文件、使用inputs热更方式、配置filebeat本身…...
深入挖掘C语言之——枚举
目录 1. 枚举的定义 2. 枚举常量的赋值 3. 枚举的使用示例 4. 注意事项 在C语言中,枚举(Enum)是一种用户定义的数据类型,用于定义一组具名的整型常量。枚举常常用于提高代码的可读性和可维护性,使程序更易于理解。…...
【源码阅读】EVMⅢ
参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385 大致流程如下: 编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation 以太坊虚拟机的工作流程: 由solidity语言编…...
.Net Core 中间件验签
文章目录 为什么是用中间件而不是筛选器?代码实现技术要点context.Request.EnableBuffering()指针问题 小结 为什么是用中间件而不是筛选器? 为什么要用中间件验签,而不是筛选器去验签? 1、根据上图我们可以看到,中间件在筛选器之…...
Elasticsearch:从 Java High Level Rest Client 切换到新的 Java API Client
作者:David Pilato 我经常在讨论中看到与 Java API 客户端使用相关的问题。 为此,我在 2019 年启动了一个 GitHub 存储库,以提供一些实际有效的代码示例并回答社区提出的问题。 从那时起,高级 Rest 客户端 (High Level Rest Clie…...
七:分布式
一、Nginx nginx安装 【1】安装pcre依赖 1.下载压缩包:wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz 2.解压压缩包:tar -xvf pcre-8.37.tar.gz 3.安装gcc:yum install gcc 4.安装gcc:yum ins…...
1-postgresql数据库高可用脚本详解
问题: pgrep -f postgres > /dev/null && echo 0 || pkill keepalived 这是什么意思 建议换成 pgrep -f postmaster > /dev/null && echo 0 || pkill keepalived 回答 这条命令是一个复合命令,包含条件执行和重定向的元素。让我们…...
【亲测】Onlyfans年龄认证怎么办?Onlyfans需要年龄验证?
1. 引言 什么是OnlyFans:OnlyFans是一种内容订阅服务,成立于2016年,允许内容创作者从用户那里获得资金,用户需要支付订阅费用才能查看他们的内容。它在多个领域受到欢迎,包括音乐、健身、摄影,以及成人内容…...
ASP.NET Core新特性
1. ASP.NET Core2.1 ASP.NET Core 2.1于2018年5月30日发布。是ASP.NET Core框架的一个重要版本,带来了许多新功能和改进。以下是ASP.NET Core 2.1中一些主要的特性: SignalR:引入了 SignalR,这是一个实时通信库,使得构…...
26-Java访问者模式 ( Visitor Pattern )
Java访问者模式 摘要实现范例 访问者模式(Visitor Pattern)使用了一个访问者类,它改变了元素类的执行算法,通过这种方式,元素的执行算法可以随着访问者改变而改变访问者模式中,元素对象已接受访问者对象&a…...
电子科技大学链时代工作室招新题C语言部分---题号G
1. 题目 问题的第一段也是非常逆天,说实话,你编不出问题背景可以不编。 这道题的大概意思就是, Pia要去坐飞机,那么行李就有限重。这时Pia想到自己带了个硬盘,众所周知,硬盘上存储的数据就是0和1的二进制序…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
