共计 3764 个字符,预计需要花费 10 分钟才能阅读完成。
我是 HullQin,公众号 线下团聚游戏 的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者 HullQin 受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入 Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。
背景
在专栏《Go WebSocket》里,有一些前置文章:
《单房间的聊天室》,介绍了如何实现一个单房间的聊天室。
《多房间的聊天室(一)思考篇》,介绍了实现一个多房间的聊天室的思路。
《多房间的聊天室(二)代码实现》,介绍了实现一个多房间的聊天室的代码。
《多房间的聊天室(三)主动清理无人房间》,介绍了如何清理无人的房间,防止内存有限增长的问题。
《多房间的聊天室(四)黑天鹅事件》,介绍了如何防止并发导致的资源竞争的问题,是通过乐观锁解决的。
《多房间的聊天室(五)用多个小锁代替大锁,提高效率》,介绍了通过把一个全局大锁拆分成多个小锁,进步了并发效率。
舒适提醒:浏览本文前,最好浏览下下面的文章。因为这篇文章更简单,如果你不弄懂下面几篇,这篇可能跟不上节奏噢。
本文答复以下问题:
为什么肯定要加锁?加锁后,你的代码逻辑是对的吗?
换个形容办法,本文是论证了给代码逻辑加锁的 必要性 和 充分性。
思路
论证必要性
咱们只有把加锁相干代码删掉,模仿实在环境发高并发的申请,测试是否有 bug 呈现。一旦呈现 bug,就认为加锁是必要的。
论证充分性
咱们把锁的代码加上,模仿实在环境发高并发的申请,测试 bug 是否修复。并且认为批改代码,减少延时,扩充 bug 呈现可能性。如果最终没有任何 bug,表明当初服务器代码逻辑是正确的。
剖析验证的指标
如果把代码里没有锁,会有什么问题?
咱们把锁相干代码正文掉,剖析下代码:
建设连贯时,咱们依据 roomId,从 house 中获取了 room。
然而 goroutine 随时有可能被动让出执行权,去执行其它 goroutine。而且咱们晓得,这个 r.HandleFunc()
里的函数,是每有一个客户端申请连贯时,都是一个新的 goroutine。
如果同时有多个客户端申请连贯同一个房间,他们都刚好在 room, ok := house[roomId]
后,让出了执行权,那么他们都发现该房间都不存在,各自建设了新房间,导致他们尽管房间号雷同,然而房间不同,无奈互相通信。
怎么测试这个场景呢?
咱们同时在客户端启动多个 WebSocket 连同一个房间即可(假如启动 10 个连贯),而后等所有客户端连贯结束后,各自发一个音讯,看看每个客户端收到了多少音讯。
在同一个房间里,有 10 集体进来了,每人发了 1 个播送音讯,这个音讯应该会播送给 10 集体。有 10 集体发了音讯,所以预期每个人会收到 10 个音讯。一共就是 100 条音讯。
然而如果 Bug 产生,应该是少于 100 条音讯的,咱们验证一下!
开发测试脚本
因为咱们提供的 WebSocket 服务的客户端是在浏览器中,所以,为了模仿生产环境,咱们的测试脚本也用 html
和js
开发。
因为是高并发,所以一个页面能够开启多个 WebSocket 连贯,通过输出参数,能够调整房间数目、每个房间的客户端的数目。
交互如下:
代码如下:
<body>
<div>
<label>
Number of rooms: <input type="number" id="rooms" value="1">
</label>
</div>
<div>
<label>
Number of clients of per room: <input type="number" id="clients" value="11">
</label>
</div>
<div>
<button id="start">Start</button>
</div>
<script>
const startBtn = document.getElementById('start');
const roomsInput = document.getElementById('rooms');
const clientsInput = document.getElementById('clients');
const data = {
succ: 0,
fail: 0,
};
startBtn.addEventListener('click', () => {const rooms = parseInt(roomsInput.value);
const clients = parseInt(clientsInput.value);
if (!rooms || !clients || rooms < 0 || clients < 0) return;
for (let i = 0; i < rooms; i++) {for (let j = 0; j < clients; j++) {const ws = new WebSocket(`ws://127.0.0.1:8080/ws/room-${i}`);
let succ = false;
let received = 0;
const delay = [];
ws.onopen = () => {
succ = true;
data.succ += 1;
console.log(data);
setTimeout(() => {ws.send(new Date().getTime().toString());
}, 1000);
setTimeout(() => {console.log({ room: i, client: j, received, delay});
ws.close(1000);
}, 2000);
};
ws.onerror = () => {if (succ) data.succ -= 1;
data.fail += 1;
};
ws.onmessage = (e) => {e.data.split('\n').forEach((sendTime) => {delay.push(new Date().getTime() - Number(sendTime));
received += 1;
});
};
}
}
});
</script>
</body>
测试注意事项
留神控制变量
测试一共分 2 个步骤:
- 正文所有锁相干代码,执行测试,验证 Bug 会呈现
- 勾销正文所有锁相干代码,执行测试,验证 Bug 不再呈现
所以记得在 Go 代码里搜寻一下 Lock
、Unlock
关键词,3 个文件都有锁相干的代码,共计十多行代码。
此外,其它变量就不用批改了(例如客户端数目、房间数目)。这就是大家都晓得的 控制变量法。
查看是否有跨域问题
如果你客户端域名和服务的域名不同,咱们的 gorilla/websocket
是默认不反对跨域的。
如果你是间接批改了 chat-multi-rooms
文件夹外面的 home.html
文件,那么不会有跨域问题,你启动服务后拜访 localhost:8080
即可。
如果你是间接关上了 html 文件,域名跟服务器不同,就须要批改服务器代码,反对一下跨域。在 client.go
文件的第 35 行左近,批改为:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {return true},
}
这样针对任意起源 origin,都不会报跨域问题了。
扩充 Bug 产生概率
能够在建设房间前,应用函数 runtime.Gosched()
被动让出 goroutine 执行权、并且睡眠一点点工夫,扩充 Bug 产生概率。
抉择测试设施
务必抉择多种浏览器、多种设施来测试,因为我用 Google Chrome 测试时,就发现个坑。
Google Chrome 有个特点:如果你同时建设多个 WebSocket 连贯,之后一个一个建设。等前一个建设连贯胜利,后一个才有序的开始建设连贯。因而这样就导致服务器不会呈现 Bug(我甚至一度狐疑我后端代码不须要加锁,狐疑是 Go 外部的代码主动加锁了,然而看了半天 Go 源码,也没发现底层有主动加锁逻辑)。
起初我用 safari 浏览器测试,就很容易复现 Bug 了。
测试后果
删掉锁相干代码
在 MacOS 的 safari 浏览器下,设置房间数 =1,客户端连接数 =11。
浏览器输入如下:
其中 received 字段就示意各个客户端收到了多少条音讯,delay 是收到每条音讯的毫秒提早(用收到工夫 - 发送工夫)。
Go 服务输入如下:
能够看到输入了 6 个create
,意思是尽管 11 个客户端连贯同一个房间,然而却创立了 6 次房间。必然导致每个客户端收到的音讯小于 11 条。
加上锁相干代码
在 MacOS 的 safari 浏览器下,设置房间数 =1,客户端连接数 =11。
浏览器输入如下:
Go 服务输入如下:
能够看到,加锁后,十分完满,只有第一个连贯建设时产生了 create
房间,其它都失常退出了。而且每个客户端都收到了 11 条申请,合乎预期。
写在最初
我是 HullQin,公众号 线下团聚游戏 的作者(欢送关注公众号,发送加微信,交个敌人),转发本文前需取得作者 HullQin 受权。我独立开发了《联机桌游合集》,是个网页,能够很不便的跟敌人联机玩斗地主、五子棋等游戏,不免费没广告。还开发了《Dice Crush》加入 Game Jam 2022。喜爱能够关注我 HullQin 噢~我有空了会分享做游戏的相干技术。