Go死锁——当Channel遇上Mutex时
背景
用metux lock for循环,在for循环中又 向带缓冲的Channel 写数据时,千万要小心死锁!
死锁现象
- semacquire阻塞:有9261/2 个 routine
- chan send阻塞:有9处
启发
func (r *Room) Broadcast(msg string) {
r.membersMx.RLock()
defer r.membersMx.RUnlock()
for _, m := range r.members {
if err := s.Send(msg); err != nil { // ❶
log.Printf("Broadcast: %v: %v", r.instance, err)
}
}
}
请注意,我们等待❶,直到每个成员收到消息,然后再继续下一个成员。这很快就会成为问题。
func (r *Room) Add(s sockjs.Session) {
r.membersMx.Lock() // ❶
r.members = append(r.members, s)
r.membersMx.Unlock()
}
我们无法获得锁❶,因为我们的 Broadcast 函数仍在使用它来发送消息。
分析
func (ud *UserDevice) SendMsg(ctx context.Context, msg *InternalWebsocketMessage) {
// 注意,不是原生的Write
if err = ud.Conn.Write(data); err != nil {
ud.L.Debug("Write error", zap.Error(err))
}
}
func (c *connectionImpl) Write(data []byte) (err error) {
wsMsgData := &MsgData{
MessageType: websocket.BinaryMessage,
Data: data,
}
c.writer <- wsMsgData // 注意这里,writer是有缓冲的,数量目前是10,如果被写满,就会阻塞
return
}
func (m *userManager) BroadcastMsgToRoom(ctx context.Context, msg *InternalWebsocketMessage, roomId []int64) {
// 这里有互斥锁,确保map的遍历
m.RLock()
defer m.RUnlock()
// m.users 是一个 map[int64]User类型
for _, user := range m.users {
user.SendMsg(ctx, msg) // ❶
}
}
func (m *userManager) Add(device UserDeviceInterface) (User, int) {
uid := device.UID()
m.Lock() // ❶
defer m.Unlock()
user, ok := m.users[uid]
if !ok {
user = NewUser(uid, device.GetLogger())
m.users[uid] = user
}
remain := user.AddDevice(device)
return user, remain
}
func onWSUpgrade(ginCtx *gin.Context) {
// …
utils.GoSafe(ctx, func(ctx context.Context) {
// ❶
userDevice.User, remain = biz.DefaultUserManager.Add(userDevice)
}, logger)
}
func (c *connectionImpl) ExecuteLogic(ctx context.Context, device UserDeviceInterface) {
go func() {
for {
select {
case msg, ok := <-c.writer:
if !ok {
return
}
// 写超时5秒
_ = c.conn.SetWriteDeadline(time.Now().Add(types.KWriteWaitTime))
if err := c.conn.WriteMessage(msg.MessageType, msg.Data); err != nil {
c.conn.Close()
c.onWriteError(err, device.UserId(), device.UserId())
return
}
}
}
}()
}
这下就能解释的通了!
别人是如何解决的?
// Push server push message.
func (c *Channel) Push(p *protocol.Proto) (err error) {
select {
case c.signal <- p:
default:
err = errors.ErrSignalFullMsgDropped
}
return
}
func (c *connectionImpl) Write(data []byte) (err error) {
wsMsgData := &MsgData{
MessageType: websocket.BinaryMessage,
Data: data,
}
// if buffer full, return error immediate
select {
case c.writer <- wsMsgData:
default:
err = ErrWriteChannelFullMsgDropped
}
return
}
后记
func main() {
w := make(chan string, 2)
w <- "1"
fmt.Println("write 1")
w <- "2"
fmt.Println("write 2”)
w <- "3"
}
write 1
write 2
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]:
main.main()
/Users/xu/repo/github/01_struct_mutex/main.go:133 +0xdc
exit status 2
func main() {
w := make(chan string, 2)
w <- "1"
fmt.Println("write 1")
w <- "2"
fmt.Println("write 2")
select {
case w <- "3":
fmt.Println("write 3")
default:
fmt.Println("msg flll")
}
}
write 1
write 2
msg flll
总结
用metux lock for循环,在for循环中又 向带缓冲的Channel 写数据时,千万要小心死锁!
func (r *Room) Broadcast(msg string) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, m := range r.members {
r.writer <- msg // Bad
}
}
func (r *Room) Broadcast(msg string) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, m := range r.members {
// Good
select {
case c.writer <- wsMsgData:
default:
fmt.Println(“ErrWriteChannelFullMsgDropped”)
}
}
}
- 当 带缓冲的channel 被写满时,到底是应该阻塞好?还是丢弃立即返回错误好?
- 为什么不用 len(w) == cap(w) 判断channel是否写满呢?
——————传说中的分割线——————
大家好,我目前已从C++后端转型为Golang后端,可以订阅关注下《Go和分布式IM》公众号,获取一名转型萌新Gopher的心路成长历程和升级打怪技巧。
Go死锁——当Channel遇上Mutex时的更多相关文章
- 当 Go struct 遇上 Mutex
struct 是我们写 Go 必然会用到的关键字, 不过当 struct 遇上一些比较特殊类型的时候, 你注意过你的程序是否正常吗 ? 一段代码 type URL struct { Ip string ...
- SQL SERVER 2008 R2 SP1更新时,遇上共享功能更新失败解决方案
SQL SERVER 2008 R2 SP1更新时,遇上共享功能更新失败的问题,可作如下尝试: 更新失败后,在windows的[事件查看器→应用程序]中找到来源为MsiInstaller,事件ID为1 ...
- 当DataTable的列名遇上特殊字符"["和"]"时
刚才有看到一个问题http://bbs.csdn.net/topics/390781072.是在DataTable获取某列最小值,但是在动态生生DataTable时,列名有遇上特特殊字符"[ ...
- 敏捷遇上UML-需求分析及软件设计最佳实践(郑州站 2014-6-7)
邀请函: 尊敬的阁下:我们将在郑州为您奉献高端知识大餐,当敏捷遇上UML,会发生怎样的化学作用呢?首席专家张老师将会为您分享需求分析及软件设计方面的最佳实践,帮助您掌握敏捷.UML及两者相结合的实 ...
- 敏捷遇上UML—软创基地马年大会(广州站 2014-4-19)
我们将在广州为您奉献高端知识大餐,当敏捷遇上UML,会发生怎样的化学作用呢?首席专家张老师将会为您分享需求分析及软件设计方面的最佳实践,帮助您掌握敏捷.UML及两者相结合的实战技巧. 时间:2 ...
- 当创业遇上O2O,新一批死亡名单,看完震惊了!
当创业遇上O2O,故事就开始了,总投入1.6亿.半年开7家便利店.会员猛增至10万……2015半年过去后,很多故事在后面变成了一场创业“事故”,是模式错误还是烧钱过度?这些项目的失败能给国内创业者带来 ...
- LoadRunner - 当DiscuzNT遇上了Loadrunner(下) (转发)
当DiscuzNT遇上了Loadrunner(下) 在之前的两篇文章中,基本上介绍了如何录制脚本和生成并发用户,同时还对测试报告中的几个图表做了简单的说明.今天这篇文章做为这个系列的最后一篇,将会介绍 ...
- LoadRunner - 当DiscuzNT遇上了Loadrunner(中) (转发)
当DiscuzNT遇上了Loadrunner(中) 在上文中,介绍了如果录制脚本和设置脚本执行次数.如果经过调试脚本能够正常工作的话,就可以设置并发用户数并进行压力测试了. 首先我们通过脚本编辑界面上 ...
- 当KDS晶振遇上爱普生晶振国内生产厂家该如何抉择?
当KDS晶振遇上爱普生晶振国内生产厂家该如何抉择? 全球做晶振行业的公司有很多,单说深圳一个城市就有几十上百家正规的晶振厂家,深圳市金洛电子就是其中之一.我们不光代理日本和台湾多家排得上名 ...
随机推荐
- 企业实战|基于Cobbler实现多版本系统批量部署
前言 运维自动化在生产环境中占据着举足轻重的地位,尤其是面对几百台,几千台甚至几万台的服务器时,仅仅是安装操作系统,如果不通过自动化来完成,根本是不可想象的.记得前面我们探究了基于PXE实现系统全自动 ...
- 初学者都能懂得 Git 说明
初学者都能懂得 Git 说明 本文写于 2020 年 8 月 10 日 网上有很多非常优秀的 Git 教程,但是他们都是面向有一定基础的开发者的. 可是对于没什么基础的初学者甚至是偶尔操作代码的设计师 ...
- Dapr学习(2)之Rancher2.63(k8s&k3s)环境安装Dapr
前言:前面写过一篇关于dapr入门安装的文章,self-host模式,使用docker安装的本地调试环境,并进行了测试:本篇介绍k8s方式安装dapr,此文主要基于的环境是k3s,通过rancher2 ...
- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)
请点赞加关注,你的支持对我非常重要,满足下我的虚荣心. Hi,我是小彭.本文已收录到 GitHub · Android-NoteBook 中.这里有 Android 进阶成长知识体系,有志同道合的朋友 ...
- Docker部署mysql 5.7
Docker部署mysql 5.7 准备工作 在CentOS或者Linux创建部署目录,用于存放容器的配置和MySQL数据:目的是当重装或者升级容器时,配置文件和数据不会丢失.执行以下命令: a.创建 ...
- DEDECMS登录后台,无法连接数据库的原因
在CMS的网页模块中,当迁移网站出现后台无法登录的时候 最可能的情况有下列几种: 1. 数据库服务器宕机.如果是云上的数据库时,需要联系客服进行解决.是有自己的搭建的数据库,需要查看服务是否正常启动 ...
- linux篇-新建svn仓库
1昨天需要在服务器上新建一个仓库,解决方法是把已有的仓库拷贝出来,库删除在放进去 2然后今天想看看有没有命令的方法 find / -name project 首先查看一下项目的位置 3创建仓库 svn ...
- Net6 Xunit 集成测试
对于单元测试.集成测试大部分开发的朋友都懒得去写,因为这要耗费精力去设计去开发,做完项目模块直接postman 调用测试(当然这是一个选择,开发也中经常用到),但是如果测试需要多样化数据,各种场景模拟 ...
- 每天一个 HTTP 状态码 206
206 Partial Content 206 Partial Content 是当客户端请求时使用了 Range 头部,服务器端回复的响应,表示只响应一部分内容. 实例 请求: GET /favor ...
- SMFL 教程&个人笔记
本文大部分来自官方教程的Google翻译 但是加了一点点个人的理解和其他相关知识 转载请注明 原文链接 :https://www.cnblogs.com/Multya/p/16273753.html ...