当前位置: 首页 > news >正文

Go语言中的锁与管道的运用

目录

1.前言

2.锁解决方案

3.管道解决方案

4.总结


1.前言

在写H5小游戏的时候,由于需要对多个WebSocket连接进行增、删、查的管理对已经建立连接的WebSocket通过服务端进行游戏数据交换的需求。于是定义了一个全局的map集合进行连接的管理,让所有的协程共享操作同一个map集合,进行各种WebSocket连接的操作。由于多个协程操作共享同一块内容,这时候就会遇到数据竞争和并发访问。

H5小游戏介绍:基于WebSocket通信的H5小游戏总结-CSDN博客

解决并发问题的常见方法有两种:

  1. 在结构体中增加 sync.RWMutex字段,每一个协程操作map集合的时候进行加锁操作,操作结束后进行解锁操作,保证同时只有一个协程操作map,避免并发问题。但是频繁的加锁和解锁操作会成为后期的性能瓶颈。
  2. 使用管道进行通信,由于管道本身就是线程安全的,所以我们在操作层面无需进行加锁和解锁操作,只需要另启一个协程进行管道的读取,如果有数据写入则进行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小游戏的时候&#xff0c;由于需要对多个WebSocket连接进行增、删、查的管理和对已经建立连接的WebSocket通过服务端进行游戏数据交换的需求。于是定义了一个全局的map集合进行连接的管理&#xff0c;让所有…...

前端 - 基础 表单标签 -- 表单元素( input - type属性) 文本框和密码框

表单元素 &#xff1a; 在表单域中可以定义各种表单元素&#xff0c;这些表单元素就是允许用户在表单中输入或选择 的内容控件。 表单元素的外观也各不一样&#xff0c;有小圆圈&#xff0c;有正方形&#xff0c;也有方框&#xff0c;乱七八糟的&#xff0c;各种各样&#xf…...

关于MySQL模糊搜索不区分大小写

在我们日常使用ORM框架进行模糊查询时&#xff0c;会发现&#xff0c;搜索的结果是不区分关键字的英文大小写的&#xff0c;那这是为什么呢&#xff1f; 原因是MySQL的like本就不区分大小写&#xff1b;如果在建表的时候&#xff0c;没有设置好字段区分大小 //包含j和J的都会被…...

论文阅读——MoCo

Momentum Contrast for Unsupervised Visual Representation Learning 动量在数学上理解为加权移动平均&#xff1a; yt-1是上一时刻输出&#xff0c;xt是当前时刻输入&#xff0c;m是动量&#xff0c;不想让当前时刻输出只依赖于当前时刻的输入&#xff0c;m很大时&#xff0…...

ARM 寄存器学习:(一)arm多种模式下得寄存器

一.ARM7种状态以及每种状态的寄存器&#xff1a; ARM 处理器共有 7 种不同的处理器模式&#xff0c;在每一种处理器模式中可见的寄存器包括 15 个通用寄存器( R0~R14)、一个或两个(User和Sys不是异常模式&#xff0c;没有spsr寄存器)状态寄存器&#xff08;cpsr和spsr&…...

【nfs报错】rpc mount export: RPC: Unable to receive; errno = No route to host

NFS错误 问题现象解决方法 写在前面 这两天搭建几台服务器&#xff0c;需要使用nfs服务&#xff0c;于是六台选其一做服务端&#xff0c;其余做客户端&#xff0c;搭建过程写在centos7离线搭建NFS共享文件&#xff0c;但是访问共享时出现报错&#xff1a;rpc mount export: RPC…...

备战蓝桥杯---牛客寒假训练营2VP

题挺好的&#xff0c;收获了许多 1.暴力枚举&#xff08;许多巧妙地处理细节方法&#xff09; n是1--9,于是我们可以直接暴力&#xff0c;对于1注意特判开头0但N&#xff01;1&#xff0c;对于情报4&#xff0c;我们可以把a,b,c,d的所有取值枚举一遍&#xff0c;那么如何判断有…...

QCustomPlot-绘制X轴为日期的折线图

主要代码如下&#xff1a; void Widget::InitQLineXDateAddData() {customPlot new QCustomPlot(this);// 创建日期时间类型的刻度生成器QSharedPointer<QCPAxisTickerDateTime> dateTimeTicker(new QCPAxisTickerDateTime);dateTimeTicker->setDateTimeFormat(&quo…...

腾讯春招后端一面(算法篇)

前言&#xff1a; 哈喽大家好&#xff0c;前段时间在小红书和牛客上发了面试的经验贴&#xff0c;很多同学留言问算法的具体解法&#xff0c;今天就详细写个帖子回复大家。 因为csdn是写的比较详细&#xff0c;所以更新比较慢&#xff0c;大家见谅~~ 就题目而言&#xff0c;…...

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语言中&#xff0c;枚举&#xff08;Enum&#xff09;是一种用户定义的数据类型&#xff0c;用于定义一组具名的整型常量。枚举常常用于提高代码的可读性和可维护性&#xff0c;使程序更易于理解。…...

【源码阅读】EVMⅢ

参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385 大致流程如下&#xff1a; 编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation 以太坊虚拟机的工作流程&#xff1a; 由solidity语言编…...

.Net Core 中间件验签

文章目录 为什么是用中间件而不是筛选器&#xff1f;代码实现技术要点context.Request.EnableBuffering()指针问题 小结 为什么是用中间件而不是筛选器&#xff1f; 为什么要用中间件验签&#xff0c;而不是筛选器去验签? 1、根据上图我们可以看到&#xff0c;中间件在筛选器之…...

Elasticsearch:从 Java High Level Rest Client 切换到新的 Java API Client

作者&#xff1a;David Pilato 我经常在讨论中看到与 Java API 客户端使用相关的问题。 为此&#xff0c;我在 2019 年启动了一个 GitHub 存储库&#xff0c;以提供一些实际有效的代码示例并回答社区提出的问题。 从那时起&#xff0c;高级 Rest 客户端 (High Level Rest Clie…...

七:分布式

一、Nginx nginx安装 【1】安装pcre依赖 1.下载压缩包&#xff1a;wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz 2.解压压缩包&#xff1a;tar -xvf pcre-8.37.tar.gz 3.安装gcc&#xff1a;yum install gcc 4.安装gcc&#xff1a;yum ins…...

1-postgresql数据库高可用脚本详解

问题&#xff1a; pgrep -f postgres > /dev/null && echo 0 || pkill keepalived 这是什么意思 建议换成 pgrep -f postmaster > /dev/null && echo 0 || pkill keepalived 回答 这条命令是一个复合命令&#xff0c;包含条件执行和重定向的元素。让我们…...

【亲测】Onlyfans年龄认证怎么办?Onlyfans需要年龄验证?

1. 引言 什么是OnlyFans&#xff1a;OnlyFans是一种内容订阅服务&#xff0c;成立于2016年&#xff0c;允许内容创作者从用户那里获得资金&#xff0c;用户需要支付订阅费用才能查看他们的内容。它在多个领域受到欢迎&#xff0c;包括音乐、健身、摄影&#xff0c;以及成人内容…...

ASP.NET Core新特性

1. ASP.NET Core2.1 ASP.NET Core 2.1于2018年5月30日发布。是ASP.NET Core框架的一个重要版本&#xff0c;带来了许多新功能和改进。以下是ASP.NET Core 2.1中一些主要的特性&#xff1a; SignalR&#xff1a;引入了 SignalR&#xff0c;这是一个实时通信库&#xff0c;使得构…...

26-Java访问者模式 ( Visitor Pattern )

Java访问者模式 摘要实现范例 访问者模式&#xff08;Visitor Pattern&#xff09;使用了一个访问者类&#xff0c;它改变了元素类的执行算法&#xff0c;通过这种方式&#xff0c;元素的执行算法可以随着访问者改变而改变访问者模式中&#xff0c;元素对象已接受访问者对象&a…...

电子科技大学链时代工作室招新题C语言部分---题号G

1. 题目 问题的第一段也是非常逆天&#xff0c;说实话&#xff0c;你编不出问题背景可以不编。 这道题的大概意思就是&#xff0c; Pia要去坐飞机&#xff0c;那么行李就有限重。这时Pia想到自己带了个硬盘&#xff0c;众所周知&#xff0c;硬盘上存储的数据就是0和1的二进制序…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...