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的二进制序…...
体育运动直播中的智能运动跟踪和动作识别系统 - 视频分析如何协助流媒体做出实时决策
AI-Powered Streaming Vision: Transforming Real-Time Decisions with Video Analytics 原著:弗朗西斯科冈萨雷斯|斯特朗(STRONG)公司首席ML科学家 翻译:数字化营销工兵 实时视频分析通过即时处理实时视频数据&…...
Avalon总线学习
Avalon总线学习 avalon总线可以分为: Avalon clock interface Avalon reset interface Avalon Memory mapped interface Avalon iterrupt interface Avalon streaming interface Avalon tri-state conduit interface Avalon conduit interface 1、Avalon c…...
Sentinel(熔断规则)
慢调用比例 慢调用比例( SLOM_REQUEST_RATTo ):选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,…...
Hive借助java反射解决User-agent编码乱码问题
一、需求背景 在截取到浏览器user-agent,并想保存入数据库中,经查询发现展示的为编码后的结果。 现需要经过url解码过程,将解码后的结果保存进数据库,那么有几种实现方式。 二、问题解决 1、百度:url在线解码工具 …...
Linux下安装Android Studio及创建桌面快捷方式
下载 官网地址:https://developer.android.com/studio?hlzh-cn点击下载最新版本即可 安装 将下载完成后文件,进行解压,然后进入android-studio-2023.2.1.23-linux/android-studio/bin目录下,启动studio.sh即可为了更加方便的使…...
【析】一类动态车辆路径问题模型和两阶段算法
一类动态车辆路径问题模型和两阶段算法 摘要 针对一类动态车辆路径问题,分析4种主要类型动态信息对传统车辆路径问题的本质影响,将动态车辆路径问题(Dynamic Vehicle Routing Problem, DVRP)转化为多个静态的多车型开放式车辆路径问题(The Fleet Size a…...
从基础入门到学穿C++
前言知识 C简介 C是一门什么样的语言,它与C语言有着什么样的关系? C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解…...
代码随想录算法训练营第二十四天|leetcode78、90、93题
一、leetcode第93题 class Solution { public:vector<string> restoreIpAddresses(string s) {int n s.size();vector<string> res;function<void(string, int, int)> dfs [&](string ss, int idx, int t) -> void {// 终止条件,枚举完&…...
Java学习笔记NO.20
Java流程控制 1. 用户交互 Scanner Java中的Scanner类用于获取用户输入,可以从标准输入(键盘)读取各种类型的数据。 import java.util.Scanner; public class UserInputExample { public static void main(String[] args) { Scanner sc…...
关系型数据库mysql(1)基础认知和安装
目录 一.数据库的基本概念 1.1数据 1.2表 1.3数据库 1.4 DBMS 数据库管理系统 1.4.1基本功能 1.4.2优点 1.4.3DBMS的工作模式 二.数据库的发展历史 2.1发展的三个阶段 第一代数据库 第二代数据库 第三代数据库 2.2mysql发展历史 三.主流数据库 四.关系型数据库和…...
wordpress算数验证/电脑版百度网盘
在OPC UA Server里,往往会有很多runtime信息,这些信息由底层的某种物理过程产生,如锅炉的温度值,是在锅炉运行过程中产生的,锅炉运行过程就可以看做是一个物理过程。 Server会提供一个变量,这个变量存放锅…...
wordpress火车头发布模板/aso优化工具
【计算机三级】 网络技术之快速求出IP地址块经聚合后的IP地址相信大家在备考计算机三级网络技术都会遇到求多个IP地址聚合后的地址是什么的题目,以下是一个小技巧(具体原理自己把十进制的IP地址和子网掩码换算成二进制理解一下就明白了,这里不…...
腾讯云可以用wordpress教程/上海优化外包公司排名
<题目链接> 题目大意: 有两个容量的空杯子,能够对这两个空杯子进行三种操作: 分别是fill(a),装满a杯子; drop(a),倒空a杯子; pour(a,b),将a杯子中的水倒入b杯子中;…...
网站公司模板/今日头条10大新闻
解题思路:此题因为涉及到的数字范围为-10000~10000,不用两重for循环暴力算法来解决,太费时,无法实现,将数乘积的选择用条件语句来进行选择 将极大程度地降低运算时间。注意事项:用Max,Min,Min1来分别存放输入的非0的最大数&#x…...
平面设计是做什么的工作/广州抖音seo
Android开发技术越来越成熟,Android开发工具当然也层出叠现。本文就向大家介绍今年最新的深受开发者喜爱的30个Android库。希望对你的Android开发工作能起到助力。 1.MaterialStepperView 它是用Material Design实现Steppers的。 目前,Stepper只有垂直视…...
做动态网站可以不写代码吗/百度提交入口网址截图
前言本文通过四种方式来告诉你如何使用,虽然有一种被放弃了。今日早读文章由老虎集团joking_zhang翻译授权分享。正文从这开始~~使用 React 时,我们的默认思维方式应该是 不会强制修改 DOM ,而是通过传入 props 重新渲…...