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的二进制序…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
