1、尽量减少锁的持有时间¶

尽量减少锁的持有时间,毕竟使用锁是有代价的,通过减少锁的持有时间来减轻这个代价:

  • 细化锁的粒度。通过细化锁的粒度来减少锁的持有时间以及避免在持有锁操作的时候做各种耗时的操作。
  • 不要在持有锁的时候做 IO
    操作。尽量只通过持有锁来保护 IO 操作需要的资源而不是 IO 操作本身:
func doSomething() {m.Lock()item := ...http.Get()  // 各种耗时的 IO 操作m.Unlock()
}// 改为
func doSomething() {m.Lock()item := ...m.Unlock()http.Get()
}

2、善用 defer 来确保在函数内正确释放了锁¶

尤其是在那些内部有好几个通过 if err != nil 判断来提前返回的函数中,通过defer 可以确保不会遗漏释放锁操作,避免出现死锁问题,以及避免函数内非预期的 panic 导致死锁的问题:

func doSomething() {m.Lock()defer m.Unlock()err := ...if err != nil {return}err = ...if err != nil {return}...return
}

不过使用 defer 的时候也要注意别因为习惯性的 defer m.Unlock() 导致无意中在持有锁的时候做了 IO 操作,出现了非预期的持有锁时间太长的问题。

// 非预期的在持有锁期间做 IO 操作
func doSomething() {m.Lock()defer m.Unlock()item := ...http.Get()  // 各种耗时的 IO 操作
}

defer 其实是有点 性能 消耗 的,需要取舍下酌情使用。

3、在适当时候使用 RWMutex¶

当确定操作不会修改保护的资源时,可以使用 RWMutex 来减少锁等待时间(不同的 goroutine 可以同时持有 RLock, 但是 Lock 限制了只能有一个 goroutine 持有 Lock):

func nickName() string {rw.RLock()defer rw.RUnlock()return name
}func SetName(s string) string {rw.Lock()defer rw.Unlock()name = s
}

4、copy 结构体操作可能导致非预期的死锁¶

copy 结构体时,如果结构体中有锁的话,记得重新初始化一个锁对象,否则会出现非预期的死锁:

package mainimport ("fmt""sync")type User struct {sync.Mutexname string}func main() {u1 := &User{name: "test"}u1.Lock()defer u1.Unlock()tmp := *u1u2 := &tmp// u2.Mutex = sync.Mutex{} // 没有这一行就会死锁fmt.Printf("%#p\n", u1)fmt.Printf("%#p\n", u2)u2.Lock()defer u2.Unlock()}

5、使用 go vet 工具检查代码中锁的使用问题¶

可以通过 vet 这个命令行来检查上面的锁 copy 的问题。比如上面的例子的检查结果如下::

$ go vet main.go
# command-line-arguments
./main.go:19:9: assignment copies lock value to tmp: command-line-arguments.User

可以看到 vet 提示 19 行那里的 copy 操作中 copy 了一个锁。

BTW,使用 go vet 命令对整个项目进行检查时,可以通过 go vet $(go list ./… | grep -v /vendor/) 这个命令忽略掉 vendor 下的包。

6、build/test 时使用 -race 参数以便运行时检测数据竞争问题¶

可以在执行 go build 或 go test 时增加一个 -race 参数来开启数据竞争检测功能,通过这种方式来实现在本地开发环境/CI/测试环境阶段发现程序中可能存在的数据竞争问题:

package mainimport ("fmt""sync"
)type Todo struct {sync.Mutextasks []string
}func (t *Todo) do() {for _, task := range t.tasks {fmt.Println(task)}
}func (t *Todo) Add(task string) {t.Lock()defer t.Unlock()t.tasks = append(t.tasks, task)
}func main() {t := &Todo{}for i := 0; i < 2; i++ {go t.Add(fmt.Sprintf("%d", i))}for i := 0; i < 2; i++ {t.do()}
}

-race 参数可以开启数据竞争检测

$ go build -race -o main .

7、使用 go-deadlock 检测死锁或锁等待问题¶

上面说的在持有锁的时候做 IO 操作或其他非预期的耗时超时的问题,一方面需要在写程序的时候注意一下,另一方面也有可能是无意中代入进去的(比如上面提到的习惯性 defer 导致的)。对于那些无意中代入进去的锁等待的问题人为的去 review 的话通常很难发现,此时就需要用工具来检测了。恰好有一个叫go-deadlock 的工具可以实现这个功能。

package mainimport ("net/http""time"sync "github.com/sasha-s/go-deadlock"
)var mu sync.Mutex
var url = "http://baidu.com:90"func do() {mu.Lock()defer mu.Unlock()u := urlhttp.Get(u)  // 非预期的在持有锁期间做 IO 操作,导致锁等待时间变长
}func main() {// 检测超过 100 ms 的锁等待sync.Opts.DeadlockTimeout = time.Millisecond * 100var wg sync.WaitGroupfor i := 0; i < 3; i++ {wg.Add(1)go func() {defer wg.Done()do()}()}wg.Wait()
}

执行结果:

$ go run main.go
POTENTIAL DEADLOCK:
Previous place where the lock was grabbed
goroutine 36 lock 0x1483b90
main.go:14 main.do { mu.Lock() } <<<<<
main.go:30 main.main.func1 { do() }Have been trying to lock it again for more than 100ms
goroutine 35 lock 0x1483b90
main.go:14 main.do { mu.Lock() } <<<<<
main.go:30 main.main.func1 { do() }Here is what goroutine 36 doing now
goroutine 36 [select]:

通过上面的输出可以知道 goroutine 36 持有锁的时间过长导致其他 goroutine 获取锁的等待时间超过了 100 ms ,并且 goroutine 36 在持有锁期间正在做 18 行的 http 操作。可以看到 go-deadlock 在优化锁等待时间方面有很大的帮助,可以帮助我们及时发现异常的锁使用姿势。

参考资料

  • Go: 关于锁(mutex)的一些使用注意事项

Go: 关于锁(mutex)的一些使用注意事项相关推荐

  1. 互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

    摘要:本文带领大家一起剖析鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体.互斥锁池初始化.互斥锁创建删除.申请释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十 互斥锁Mutex& ...

  2. 一文带你剖析LiteOS互斥锁Mutex源代码

    本文分享自华为云社区<LiteOS内核源码分析系列七 互斥锁Mutex>,原文作者:zhushy. 多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能 ...

  3. 互斥锁(mutex lock)

    互斥锁 解决临界区最简单的工具就是互斥锁(mutex lock) 一个进程在进入临界区的时候获得锁(函数acquire) 在退出临界区时释放锁(函数release) 每个互斥锁都有一个布尔变量avai ...

  4. 互斥锁(mutex)

    原文地址:https://blog.csdn.net/qq_39736982/article/details/82348672 Linux中提供一把互斥锁mutex(也称之为互斥量). 每个线程在对资 ...

  5. android 线程互斥锁,线程锁(互斥锁Mutex)及递归锁

    一.线程锁(互斥锁) 在一个程序内,主进程可以启动很多个线程,这些线程都可以访问主进程的内存空间,在Python中虽然有了GIL,同一时间只有一个线程在运行,可是这些线程的调度都归系统,操作系统有自身 ...

  6. windows多线程之读写同步(线程锁Mutex + 信号量Semaphore )

    第一次使用博客,把自己学习的心得记录下来,与大家分享,有什么不足请指正,共同学习! 本文记录的是线程同步的一个经典问题,读写问题.这个场景在实际的应用中很常见,多线程中同时对文件进行读写很容易出问题, ...

  7. java判断线程是否死锁_c++多线程锁 Mutex  自动判断死锁

    c++多线程锁可以使用absl::Mutex  std::mutex这两种,下面是demo代码. 使用absl:Mutex的时候打印: [mutex.cc : 1338] RAW: Cycle: [m ...

  8. 互斥锁mutex的使用方法

    在线程实际运行过程中,我们经常需要多个线程保持同步.这时可以用互斥锁来完成任务:互斥锁的使用过程中,主要有pthread_mutex_init,pthread_mutex_destory,pthrea ...

  9. php mutex,go互斥锁Mutex

    go mutex是互斥锁,只有Lock和Unlock两个方法,在这两个方法之间的代码不能被多个goroutins同时调用到. 看代码: package main import ( "fmt& ...

  10. 多线程之互斥锁(mutex)的使用方法

    关于读写锁可查看:多线程之读写锁(unique_lock与shared_lock) 多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁). 引用 cpprefere ...

最新文章

  1. MySQL学习总结(三)索引
  2. 面试官问: 如何保证 MQ消息是有序的?
  3. $Django 中间件 csrf
  4. 如何分析apache日志[access_log(访问日志)和error_log(错误日志)]
  5. 深入了解以太坊虚拟机第3部分——动态数据类型的表示方法
  6. 光纤以太网交换机产品性能介绍
  7. 软件测试行业用mac好还是win好,业余RMAA测试 同样的硬件下OS X果然比Windows声音好...
  8. hnu暑期实训之487-3279 字符串处理
  9. 兴业太阳能:将“绿色能源”带到国外
  10. viewDidUnload方法的离去和替代
  11. 推荐阅读20100708
  12. 一个微信投票小程序防止刷票的想法
  13. 华为U8500在USB调试模式下LOGCAT无打印信息的解决方法
  14. 阿里云存储oss依赖报错解决方法
  15. java--案例:[Random]随机100-200的数、班级点名器、控制台输入三个数,输出最大值,或最小值?
  16. jadx-gui-1.4.4 反编译工具使用教程
  17. 又一所985大学全面改考408!厦门大学计算机考研
  18. 浅析FPC软性线路板补强
  19. Ubuntu、Linux、Unix的联系与区别
  20. 公钥私钥及ssh公钥无密码登录

热门文章

  1. DAS\NAS\SAN\IPSAN区别
  2. 动物派对怎么修改服务器,动物派对登录失败怎么办 Party Animals玩法进入游戏解决方法...
  3. 一著名软件公司的java笔试算法题的答案
  4. Java实现文件下载
  5. IE8不支持jquery , xmlhttp.open() IE8 拒绝访问
  6. 中国智能制造2025及工业物联网
  7. A. Liserious战队
  8. plotly使用指南
  9. JZOJ 6287. 2019.08.09【NOIP提高组A】扭动的树
  10. 洛谷 P4324 [JSOI2016]扭动的回文串 manacher+字符串hash