终于遇到goroutine死锁的BUG了

今天测试用Go语言写的角色服务器,发现在模拟大量客户端获取角色列表的时候会卡住,但是服务器程序的CPU占用率为零。分析并经过代码检查确认是goroutine死锁。

此问题涉及到的代码主要是一个database类,封装了角色数据的一些数据库操作。虽然sql.DB后台支持连接缓冲池,但是为了限制资源的使用,我仍然在database类里做了并发数的限制,角色数据的每个操作必须先获得令牌才能进行后续操作,操作完成后归还令牌。代码主要结构如下所示:

import "database/sql"

const DBCONN_MAXCOUNT = 128

type database struct {

db *sql.DB

tokens chan int

}

func (p *database) Open(source string) error {

db, err := sql.Open("mysql", source) if err != nil {

return err

} p.db = db p.tokens = make(chan int, DBCONN_MAXCOUNT) for i := 0; i < DBCONN_MAXCOUNT; i++ {

p.tokens <- 1

} return nil

}

func (p *database) Close() {

p.db.Close()

}

func (p *database) GetRoleData(name string) ([]byte, error) {

token := <-p.tokens defer func() {p.tokens<-token}() //相关数据库操作...

}

func (p *database) DeleteRole(name string) error {

token := <-p.tokens defer func() {p.tokens<-token}() //相关数据库操作...

}

BUG的原因是在DeleteRole函数里其实进行了一些数据库操作,包括为了检查角色数据是否需要放到回收库需要先获取角色数据检查角色的等级等。其中获取角色数据就调用到了GetRoleData函数。这就导致一种可能性:如果所有客户端都执行到DeleteRole函数里,准备执行GetRoleData函数时没有可用令牌,就会集体陷入死锁。

解决办法是定义了一个不需要更底层的函数getRoleData,供GetRoleData和DeleteRole函数调用,从而不需要申请多个令牌。

func (p *database) getRoleData(name string) ([]byte, error) {

//相关数据库操作...

}

func (p *database GetRoleData(name string) ([]byte, error) {

token := <-p.tokens defer func() {p.tokens<-token}() return p.getRoleData(name)

}

func (p *database) DeleteRole(name string) error {

token := <-p.tokens defer func() {p.tokens<-token}() data, err := p.getRoleData(name) if err != nil {

return err

} //其他操作...

}

总结:

1.最好不要设计成做一件事情需要获取N个资源,然后依次去申请。如果不能避免申请多个资源,也应该按照固定次序去申请,否则会导致死锁(本例中是不小心导致的,因为申请令牌是后加的)。这是所有编程语言设计并发程序的通用规则。

2.函数分级。一个类(结构)的暴露给类(结构)外部使用的函数最好不要相互调用。如果有共用代码,可以提取为更底层的一个内部函数,供给多个上一级的函数使用。这个不处理好容易导致以后调整功能需要修改代码时出问题。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-09-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

用gcc编译c语言程序以及其编译过程

对于初学c语言编程的我们来说,学会如何使用gcc编译器工具,对理解c语言的执行过程,加深对c语言的理解很重要!!!

16810
来自专栏python3

python shutil模块

和copyfileobj是类似的功能,不同的是,copyfile不需要打开文件,底层做好了。

8710
来自专栏Linyb极客之路

RPC框架设计和调用详解

RPC是远程调用过程的简写,是一个协议,处于网络通信协议的第五层:会话层,其下就是TCP/IP协议,在建立在其基础上的通信会话协议。RPC定义了交互的模式,而...

23820
来自专栏架构师之路

浅谈CAS在分布式ID生成方案上的应用 | 架构师之路

近几篇文章聊CAS被骂得较多,今天还是聊CAS,谈谈CAS在一种“分布式ID生成方案”上的应用。 所谓“分布式ID生成方案”,是指在分布式环境下,生成全局唯一I...

45040
来自专栏pangguoming

Windows下RabbitMQ安装及入门

1.Windows下安装RabbitMQ需要以下几个步骤    (1):下载erlang,原因在于RabbitMQ服务端代码是使用并发式语言erlang编写的,...

36070
来自专栏用户2442861的专栏

HTTP POST GET 本质区别详解

    一般在浏览器中输入网址访问资源都是通过GET方式;在FORM提交中,可以通过Method指定提交方式为GET或者POST,默认为GET提交

31220
来自专栏Java进阶之路

nginx配置自定义变量实现日志动态分发

Nginx是一个异步框架的 Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存。下载地址:www.nginx.org。

46820
来自专栏散尽浮华

nginx负载均衡(5种方式)、rewrite重写规则及多server反代配置梳理

Nginx除了可以用作web服务器外,他还可以用来做高性能的反向代理服务器,它能提供稳定高效的负载均衡解决方案。nginx可以用轮询、IP哈希、URL哈希等方式...

81860
来自专栏我是攻城师

使用shell分页读取600万+的MySQL数据脚本

42650
来自专栏程序员同行者

django权限管理(Permission)

1.3K40

扫码关注云+社区

领取腾讯云代金券