Skip to content

Commit cac73a8

Browse files
committed
used condition compile to replace default sync package. add README file.
1 parent 5a3c195 commit cac73a8

7 files changed

Lines changed: 221 additions & 82 deletions

File tree

README.md

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
1-
介绍
2-
====
1+
[English](http://github.com/funny/sync/blob/master/README_EN.md)
2+
---------
33

4-
这个包用来在开发调试期,帮助排查程序中的死锁情况。
5-
6-
可以直接替代原生的`sync`包,在禁用死锁检查的时候,只会有非常小的性能差异,具体可以看Benchmark结果。
7-
8-
原理
9-
====
10-
11-
在开启死锁检查的时候,系统会维护一份全局的锁等待列表,其次每个锁都会有当前使用者的信息。
12-
13-
当一个goroutine要等待一个锁的时候,系统会到全局的等待列表里面查找当前这个锁的使用者,是否间接或直接的正在等待当前请求锁的这个goroutine。
14-
15-
死锁不一定只发生在两个goroutine之间,极端情况也可能是一个链条状的依赖关系,又或者可能出现自身重复加锁的死锁情况。
16-
17-
当出现死锁的时候,系统将提取死锁链上的所有goroutine的堆栈跟踪信息,方便排查故障原因。
18-
19-
因为需要维护一份全局的锁等待列表,所以这里会出现额外并且集中的一个全局锁开销,会导致明显的程序的并发性能下降。
20-
21-
全局锁的问题还会再继续研究和加以改进,但是目前这个包是不能用于生产环境的,只能用在开发和调试期作为死锁诊断的辅助工具。
4+
[中文说明](http://github.com/funny/sync/blob/master/README_CN.md)
5+
---------

README_CN.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
介绍
2+
====
3+
4+
这个包用来在开发调试期,帮助排查程序中的死锁情况。
5+
6+
用法
7+
====
8+
9+
通常我们项目中引用到原生`sync`包的代码会像这样:
10+
11+
```go
12+
package myapp
13+
14+
import "sync"
15+
16+
var MyLock sync.Mutex
17+
18+
func MyFunc() {
19+
MyLock.Lock()
20+
defer MyLock.Unlock()
21+
22+
// .......
23+
}
24+
```
25+
26+
只需要将原来引用`sync`的代码改为引用`github.com/funny/sync`包,不需要修改别的代码:
27+
28+
29+
```go
30+
package myapp
31+
32+
import "github.com/funny/sync"
33+
34+
var MyLock sync.Mutex
35+
36+
func MyFunc() {
37+
MyLock.Lock()
38+
defer MyLock.Unlock()
39+
40+
// .......
41+
}
42+
```
43+
44+
这时候死锁诊断还没有被启用,因为做了条件编译,所以锁的开销跟原生`sync`包是一样的。
45+
46+
当需要编译一个带死锁诊断的版本的时候,在`go build --tags`列表中加入`deadlock`标签。
47+
48+
例如这样:
49+
50+
```
51+
go build -tags 'deadlock' myproject
52+
```
53+
54+
同样这个标签也用于单元测试,否则默认的单元测试会死锁:
55+
56+
```
57+
go test -tags 'deadlock'
58+
```
59+
60+
61+
原理
62+
====
63+
64+
在开启死锁检查的时候,系统会维护一份全局的锁等待列表,其次每个锁都会有当前使用者的信息。
65+
66+
当一个goroutine要等待一个锁的时候,系统会到全局的等待列表里面查找当前这个锁的使用者,是否间接或直接的正在等待当前请求锁的这个goroutine。
67+
68+
死锁不一定只发生在两个goroutine之间,极端情况也可能是一个链条状的依赖关系,又或者可能出现自身重复加锁的死锁情况。
69+
70+
当出现死锁的时候,系统将提取死锁链上的所有goroutine的堆栈跟踪信息,方便排查故障原因。
71+
72+
因为需要维护一份全局的锁等待列表,所以这里会出现额外并且集中的一个全局锁开销,会导致明显的程序的并发性能下降。
73+
74+
全局锁的问题还会再继续研究和加以改进,但是目前这个包是不能用于生产环境的,只能用在开发和调试期作为死锁诊断的辅助工具。

README_EN.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
Introduction
2+
============
3+
4+
This package is used to detect deadlock in Go program.
5+
6+
Usage
7+
=====
8+
9+
Normally, we import default `sync` package in our project like this:
10+
11+
```go
12+
package myapp
13+
14+
import "sync"
15+
16+
var MyLock sync.Mutex
17+
18+
func MyFunc() {
19+
MyLock.Lock()
20+
defer MyLock.Unlock()
21+
22+
// .......
23+
}
24+
```
25+
26+
Just replace the default `sync` to `github.com/funny/sync`, no need to change others:
27+
28+
29+
```go
30+
package myapp
31+
32+
import "github.com/funny/sync"
33+
34+
var MyLock sync.Mutex
35+
36+
func MyFunc() {
37+
MyLock.Lock()
38+
defer MyLock.Unlock()
39+
40+
// .......
41+
}
42+
```
43+
44+
Currently, deadlock detection not yet enabled, the performance of `Mutext` and `RWMutex` just like default.
45+
46+
When you need to compile a deadlock detection enabled version. Just add `deadlock` tag into `go build --tags` command.
47+
48+
For example:
49+
50+
```
51+
go build -tags 'deadlock' myproject
52+
```
53+
54+
This tag used for the unit test too. Otherwise the default unit test will deadlock:
55+
56+
```
57+
go test -tags 'deadlock'
58+
```
59+
60+
How it works
61+
============
62+
63+
When deadlock detection enabled, system will maintain a global lock waiting list, and each `Mutex` and `RWMutex` will keep owner goroutine's information.
64+
65+
When a goroutine will waiting a lock, system will lookup the lock owner goroutine is whether waiting for the requester goroutine in directly or indirectly.
66+
67+
Deadlock not only happens between two goroutines, sometimes the deadlock is a link, and deadlock happens when a goroutine repeat lock a `Mutext` too.
68+
69+
When deadlock happens, system will dump stack trace of the gorotuines in the deadlock link.
70+
71+
Because we need a global lock waiting list, so the deadlock detection will drop the performance.
72+
73+
So, please don't use deadlock detection in production environment.

common.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package sync
2+
3+
import "sync"
4+
5+
type Cond sync.Cond
6+
7+
func NewCond(l Locker) *Cond {
8+
return (*Cond)(sync.NewCond(l))
9+
}
10+
11+
type Locker sync.Locker
12+
type Once sync.Once
13+
type Pool sync.Pool
14+
type WaitGroup sync.WaitGroup

dummy.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// +build !deadlock
2+
3+
package sync
4+
5+
import "sync"
6+
7+
type Mutex struct {
8+
sync.Mutex
9+
}
10+
11+
type RWMutex struct {
12+
sync.RWMutex
13+
}

sync.go

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build deadlock
2+
13
package sync
24

35
import (
@@ -11,7 +13,41 @@ import (
1113
"sync/atomic"
1214
)
1315

14-
var WatchDeadLock int32 = 1
16+
type Mutex struct {
17+
mutexInfo
18+
sync.Mutex
19+
}
20+
21+
func (m *Mutex) Lock() {
22+
holder, elem := m.mutexInfo.wait()
23+
m.Mutex.Lock()
24+
m.mutexInfo.using(holder, elem)
25+
}
26+
27+
func (m *Mutex) Unlock() {
28+
m.mutexInfo.release()
29+
m.Mutex.Unlock()
30+
}
31+
32+
type RWMutex struct {
33+
Mutex
34+
}
35+
36+
func (rw *RWMutex) Lock() {
37+
rw.Lock()
38+
}
39+
40+
func (rw *RWMutex) Unlock() {
41+
rw.Unlock()
42+
}
43+
44+
func (rw *RWMutex) RLock() {
45+
rw.Lock()
46+
}
47+
48+
func (rw *RWMutex) RUnlock() {
49+
rw.Unlock()
50+
}
1551

1652
var (
1753
lockMutex = new(sync.Mutex)
@@ -33,7 +69,6 @@ func goroutine(id int32, stack []byte) []byte {
3369
type mutexInfo struct {
3470
holder int32
3571
waiting *list.List
36-
watch bool
3772
}
3873

3974
func (lock *mutexInfo) wait() (int32, *list.Element) {
@@ -85,47 +120,3 @@ func (lock *mutexInfo) using(holder int32, elem *list.Element) {
85120
func (lock *mutexInfo) release() {
86121
atomic.StoreInt32(&lock.holder, 0)
87122
}
88-
89-
type Mutex struct {
90-
mutexInfo
91-
sync.Mutex
92-
}
93-
94-
func (m *Mutex) Lock() {
95-
m.mutexInfo.watch = atomic.LoadInt32(&WatchDeadLock) == 1
96-
97-
if m.mutexInfo.watch {
98-
holder, elem := m.mutexInfo.wait()
99-
m.Mutex.Lock()
100-
m.mutexInfo.using(holder, elem)
101-
} else {
102-
m.Mutex.Lock()
103-
}
104-
}
105-
106-
func (m *Mutex) Unlock() {
107-
if m.mutexInfo.watch {
108-
m.mutexInfo.release()
109-
}
110-
m.Mutex.Unlock()
111-
}
112-
113-
type RWMutex struct {
114-
Mutex
115-
}
116-
117-
func (rw *RWMutex) Lock() {
118-
rw.Lock()
119-
}
120-
121-
func (rw *RWMutex) Unlock() {
122-
rw.Unlock()
123-
}
124-
125-
func (rw *RWMutex) RLock() {
126-
rw.Lock()
127-
}
128-
129-
func (rw *RWMutex) RUnlock() {
130-
rw.Unlock()
131-
}

sync_test.go

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,8 @@ func Benchmark_Lock2(b *testing.B) {
2222
}
2323
}
2424

25-
func Benchmark_Lock3(b *testing.B) {
26-
WatchDeadLock = 0
27-
var mutex Mutex
28-
for i := 0; i < b.N; i++ {
29-
mutex.Lock()
30-
mutex.Unlock()
31-
}
32-
WatchDeadLock = 1
33-
}
34-
3525
func Test_DeadLock1(t *testing.T) {
36-
var testDone sync.WaitGroup
26+
var testDone WaitGroup
3727
testDone.Add(1)
3828

3929
go func() {
@@ -57,7 +47,7 @@ func Test_DeadLock1(t *testing.T) {
5747
}
5848

5949
func Test_DeadLock2(t *testing.T) {
60-
var testDone sync.WaitGroup
50+
var testDone WaitGroup
6151
testDone.Add(1)
6252

6353
go func() {
@@ -70,7 +60,7 @@ func Test_DeadLock2(t *testing.T) {
7060
mutex1 Mutex
7161
mutex2 Mutex
7262

73-
wait1 sync.WaitGroup
63+
wait1 WaitGroup
7464
)
7565

7666
wait1.Add(1)
@@ -94,7 +84,7 @@ func Test_DeadLock2(t *testing.T) {
9484
}
9585

9686
func Test_DeadLock3(t *testing.T) {
97-
var testDone sync.WaitGroup
87+
var testDone WaitGroup
9888
testDone.Add(1)
9989

10090
go func() {
@@ -108,8 +98,8 @@ func Test_DeadLock3(t *testing.T) {
10898
mutex2 Mutex
10999
mutex3 Mutex
110100

111-
wait1 sync.WaitGroup
112-
wait2 sync.WaitGroup
101+
wait1 WaitGroup
102+
wait2 WaitGroup
113103
)
114104

115105
wait1.Add(1)

0 commit comments

Comments
 (0)