├── .DS_Store ├── 14-SingleFlight ├── go.mod ├── go.sum ├── source.go ├── example.go └── example2.go ├── 13-Semaphore ├── go.mod ├── go.sum ├── example.go └── source.go ├── 15-CyclicBarrier ├── go.mod ├── go.sum ├── source.go ├── demo.go └── example.go ├── 08-Map ├── error02.go ├── error01.go ├── 2-resval.go ├── 3-mapConcurrent.go ├── error03.go ├── 1-struct.go ├── 4-safeMap.go ├── source-orcaman.go └── source.go ├── 16-group ├── go.mod ├── example_retry.go ├── example_schedgroup.go ├── example_SizedGroup.go ├── example_all.go ├── example_errgroup.go ├── example_errgroup_cancel.go ├── example_errgroup_return_result.go ├── go.sum ├── pipeline_source.go ├── pipeline.go └── source.go ├── 02-Mutex ├── 1-unlock.go ├── 3-reentry.go ├── 2-copy.go ├── 4-deadlock.go └── 3-1-goroutine_id.go ├── 12-channel ├── 8-happened-before.go ├── 9-closed-read.go ├── 2-timer2.go ├── 4-contorlConcurrent.go ├── 5-simple-check.go ├── 2-timer.go ├── 3-uncoupled.go ├── source.go ├── 6-Nsender-receiver.go ├── source-root.go ├── source-create.go ├── 7-Nsender-Mreceiver.go ├── source-close.go ├── source-send.go └── source-receive.go ├── 05-WaitGroup ├── question-3.go ├── question-1.go ├── 1-counter.go ├── question-2.go └── source.go ├── 04-RWMutex ├── reentry.go ├── 1-Writer-nReader.go └── source.go ├── 07-Once ├── error01.go ├── 3-used.go ├── error02.go ├── 1-init.go ├── 2-init.go └── source.go ├── 09-Pool ├── bufpool.go ├── 1-usage.go ├── ginpool.go └── source.go ├── 01-Mutex ├── counter1.go ├── counter.go ├── 04-counter.go └── counter2.go ├── 03-Mutex ├── 3-queue.go ├── 1-tryLock.go └── 2-stateMetrics.go ├── 10-Context ├── 1-withValue.go ├── 2-withTimeOut.go ├── 1-withValue-http.go ├── 3-avoid.go └── source.go ├── 06-Cond ├── 1-athlete.go ├── question-2.go ├── source.go └── question-1.go ├── 11-atomic ├── 1-usage.go └── source.go └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etmorefish/go-concurrent-programming/HEAD/.DS_Store -------------------------------------------------------------------------------- /14-SingleFlight/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.19 4 | 5 | require golang.org/x/sync v0.1.0 6 | -------------------------------------------------------------------------------- /13-Semaphore/go.mod: -------------------------------------------------------------------------------- 1 | module sema 2 | 3 | go 1.19 4 | 5 | require golang.org/x/sync v0.1.0 // indirect 6 | -------------------------------------------------------------------------------- /15-CyclicBarrier/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/marusama/cyclicbarrier v1.1.0 7 | golang.org/x/sync v0.1.0 8 | ) 9 | -------------------------------------------------------------------------------- /13-Semaphore/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 2 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /14-SingleFlight/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 2 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 3 | -------------------------------------------------------------------------------- /08-Map/error02.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var m map[int]int 7 | fmt.Println(m[100]) 8 | } 9 | 10 | // 从一个 nil 的 map 对象中获取值不会 panic,而是会得到零值,所以代码不会报错 11 | -------------------------------------------------------------------------------- /08-Map/error01.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // var m map[int]int 5 | m := make(map[int]int) 6 | m[100] = 100 7 | } 8 | 9 | // 未初始化 10 | // 解决办法就是在第 4 行初始化这个实例 m := make(map[int]int) 11 | -------------------------------------------------------------------------------- /16-group/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-pkgz/syncs v1.2.0 7 | github.com/mdlayher/schedgroup v1.0.0 8 | github.com/vardius/gollback v1.1.1 9 | golang.org/x/sync v0.1.0 10 | ) 11 | -------------------------------------------------------------------------------- /02-Mutex/1-unlock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | foo() 10 | } 11 | func foo() { 12 | var mu sync.Mutex 13 | defer mu.Unlock() 14 | fmt.Println("hello world!") 15 | } 16 | -------------------------------------------------------------------------------- /12-channel/8-happened-before.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var done = make(chan bool) 4 | var msg string 5 | 6 | func aGoroutine() { 7 | // msg = "hello, world" 8 | done <- true 9 | msg = "hello, world" 10 | 11 | } 12 | 13 | func main() { 14 | go aGoroutine() 15 | <-done 16 | println(msg) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /12-channel/9-closed-read.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int, 5) 7 | ch <- 18 8 | close(ch) 9 | x, ok := <-ch 10 | if ok { 11 | fmt.Println("received: ", x) 12 | } 13 | 14 | x, ok = <-ch 15 | if !ok { 16 | fmt.Println("channel closed, data invalid.") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /05-WaitGroup/question-3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // 常见问题三:前一个 Wait 还没结束就重用 WaitGroup 9 | func main() { 10 | var wg sync.WaitGroup 11 | wg.Add(1) 12 | go func() { 13 | time.Sleep(time.Millisecond) 14 | wg.Done() // 计数器减1 15 | wg.Add(1) // 计数值加1 16 | }() 17 | wg.Wait() // 主goroutine等待,有可能和第7行并发执行 18 | } 19 | -------------------------------------------------------------------------------- /15-CyclicBarrier/go.sum: -------------------------------------------------------------------------------- 1 | github.com/marusama/cyclicbarrier v1.1.0 h1:ol/AG+sjvh5yz832avbNjaowoerBuD3AgozxL+aD9u0= 2 | github.com/marusama/cyclicbarrier v1.1.0/go.mod h1:5u93l83cy51YXdz6eKq6kO9+9mGAooB6DHMAxcSuWwQ= 3 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 4 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 5 | -------------------------------------------------------------------------------- /02-Mutex/3-reentry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func foo(l sync.Locker) { 9 | fmt.Println("in foo") 10 | l.Lock() //1 11 | bar(l) 12 | l.Unlock() 13 | } 14 | 15 | func bar(l sync.Locker) { 16 | l.Lock() //2 17 | fmt.Println("in bar") 18 | l.Unlock() 19 | } 20 | 21 | func main() { 22 | l := &sync.Mutex{} 23 | foo(l) 24 | } 25 | -------------------------------------------------------------------------------- /04-RWMutex/reentry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func foo(l *sync.RWMutex) { 9 | fmt.Println("in foo") 10 | l.Lock() 11 | bar(l) 12 | l.Unlock() 13 | } 14 | 15 | func bar(l *sync.RWMutex) { 16 | l.Lock() 17 | fmt.Println("in bar") 18 | l.Unlock() 19 | } 20 | 21 | func main() { 22 | l := &sync.RWMutex{} 23 | foo(l) 24 | } 25 | -------------------------------------------------------------------------------- /12-channel/2-timer2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | worker() 10 | } 11 | 12 | func worker() { 13 | ticker := time.Tick(1 * time.Second) 14 | for { 15 | select { 16 | case <-ticker: 17 | // 执行定时任务 18 | // dosomething 19 | fmt.Println("执行 1s 定时任务") 20 | } 21 | } 22 | } 23 | 24 | /* Analysis: 定时任务 25 | 每隔 1 秒种,执行一次定时任务。 26 | */ 27 | -------------------------------------------------------------------------------- /07-Once/error01.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var once sync.Once 10 | // once.Do(func() { 11 | once.Do(func() { 12 | fmt.Println("初始化") 13 | }) 14 | // }) 15 | } 16 | 17 | // 1. 死锁 18 | // Do 方法会执行一次 f,但是如果 f 中再次调用这个 Once 的 Do 方法的话, 19 | // 就会导致死锁的情况出现。这还不是无限递归的情况,而是的的确确的 Lock 的递归调用导致的死锁。 20 | // 想要避免这种情况的出现,就不要在 f 参数中调用当前的这个 Once,不管是直接的还是间接的。 21 | -------------------------------------------------------------------------------- /07-Once/3-used.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var once sync.Once 10 | 11 | // 第一个初始化函数 12 | f1 := func() { 13 | fmt.Println("in f1") 14 | } 15 | once.Do(f1) // 打印出 in f1 16 | 17 | // 第二个初始化函数 18 | f2 := func() { 19 | fmt.Println("in f2") 20 | } 21 | once.Do(f2) // 无输出 22 | } 23 | 24 | /* 25 | 因为这里的 f 参数是一个无参数无返回的函数,所以你可能会通过闭包的方式引用外面的参数 26 | */ 27 | -------------------------------------------------------------------------------- /09-Pool/bufpool.go: -------------------------------------------------------------------------------- 1 | /* bufpool: https://github.com/gohugoio/hugo/blob/master/bufferpool/bufpool.go 2 | 著名的静态网站生成工具 Hugo 3 | 4 | */ 5 | 6 | var buffers = sync.Pool{ 7 | New: func() interface{} { 8 | return new(bytes.Buffer) 9 | }, 10 | } 11 | 12 | func GetBuffer() *bytes.Buffer { 13 | return buffers.Get().(*bytes.Buffer) 14 | } 15 | 16 | func PutBuffer(buf *bytes.Buffer) { 17 | buf.Reset() 18 | buffers.Put(buf) 19 | } -------------------------------------------------------------------------------- /08-Map/2-resval.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var m = make(map[string]int) 7 | m["a"] = 0 8 | fmt.Printf("a=%d; b=%d\n", m["a"], m["b"]) 9 | 10 | av, aexisted := m["a"] 11 | bv, bexisted := m["b"] 12 | fmt.Printf("a=%d, existed: %t; b=%d, existed: %t\n", av, aexisted, bv, bexisted) 13 | } 14 | 15 | /* Analysis of causes: 16 | 如果获取一个不存在的 key 对应的值时,会返回零值。 17 | 为了区分真正的零值和 key 不存在这两种情况,可以根据第二个返回值来区分 18 | */ 19 | -------------------------------------------------------------------------------- /08-Map/3-mapConcurrent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | var m = make(map[int]int, 10) // 初始化一个map 5 | go func() { 6 | for { 7 | m[1] = 1 //设置key 8 | } 9 | }() 10 | 11 | go func() { 12 | for { 13 | _ = m[2] //访问这个map 14 | } 15 | }() 16 | select {} 17 | } 18 | 19 | /* Analysis: 20 | 虽然这段代码看起来是读写 goroutine 各自操作不同的元素, 21 | 貌似 map 也没有扩容的问题,但是运行时检测到同时对 map 22 | 对象有并发访问,就会直接 panic。panic 信息会告诉我们代 23 | 码中哪一行有读写问题,根据这个错误信息你就能快速定位出来 24 | 是哪一个 map 对象在哪里出的问题了。 25 | */ 26 | -------------------------------------------------------------------------------- /02-Mutex/2-copy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Counter struct { 9 | sync.Mutex 10 | Count int 11 | } 12 | 13 | func main() { 14 | var c Counter 15 | c.Lock() 16 | defer c.Unlock() 17 | c.Count++ 18 | foo(c) // 复制锁 19 | } 20 | 21 | // 这里Counter的参数是通过复制的方式传入的 22 | func foo(c Counter) { 23 | c.Lock() 24 | defer c.Unlock() 25 | fmt.Println("in foo") 26 | } 27 | 28 | 29 | // src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go -------------------------------------------------------------------------------- /01-Mutex/counter1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var counter Counter1 10 | var wg sync.WaitGroup 11 | wg.Add(10) 12 | for i := 0; i < 10; i++ { 13 | go func() { 14 | defer wg.Done() 15 | for j := 0; j < 100000; j++ { 16 | counter.Lock() 17 | counter.Count++ 18 | counter.Unlock() 19 | } 20 | }() 21 | } 22 | wg.Wait() 23 | fmt.Println(counter.Count) 24 | } 25 | 26 | type Counter1 struct { 27 | sync.Mutex 28 | Count uint64 29 | } 30 | -------------------------------------------------------------------------------- /16-group/example_retry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/vardius/gollback" 10 | ) 11 | 12 | func main() { 13 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 14 | defer cancel() 15 | 16 | // 尝试5次,或者超时返回 17 | res, err := gollback.Retry(ctx, 5, func(ctx context.Context) (interface{}, error) { 18 | return nil, errors.New("failed") 19 | }) 20 | 21 | fmt.Println(res) // 输出结果 22 | fmt.Println(err) // 输出错误信息 23 | } 24 | -------------------------------------------------------------------------------- /15-CyclicBarrier/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "context" 4 | 5 | type CyclicBarrier interface { 6 | // 等待所有的参与者到达,如果被ctx.Done()中断,会返回ErrBrokenBarrier 7 | Await(ctx context.Context) error 8 | // 重置循环栅栏到初始化状态。如果当前有等待者,那么它们会返回ErrBrokenBarrier 9 | Reset() 10 | // 返回当前等待者的数量 11 | GetNumberWaiting() int 12 | // 参与者的数量 13 | GetParties() int 14 | // 循环栅栏是否处于中断状态 15 | IsBroken() bool 16 | } 17 | 18 | /* 19 | 循环栅栏的使用也很简单。循环栅栏的参与者只需调用 Await 等待,等所有的参与者都到达后,再执行下一步。 20 | 当执行下一步的时候,循环栅栏的状态又恢复到初始的状态了,可以迎接下一轮同样多的参与者。 21 | */ 22 | -------------------------------------------------------------------------------- /08-Map/error03.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Counter struct { 9 | Website string 10 | Start time.Time 11 | PageCounters map[string]int 12 | } 13 | 14 | func main() { 15 | var c Counter 16 | c.Website = "baidu.com" 17 | 18 | // c.PageCounters["/"]++ 19 | m := map[string]int{"p1": 1, "p2": 2} 20 | c.PageCounters = m 21 | 22 | fmt.Printf("%+v", c) 23 | } 24 | 25 | // panic: assignment to entry in nil map 26 | // map 的初始化问题。 27 | // 有时候 map 作为一个 struct 字段的时候,很容易忘记初始化 28 | -------------------------------------------------------------------------------- /01-Mutex/counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var mu sync.Mutex 10 | var count = 0 11 | // 使用WaitGroup等待10个goroutine完成 12 | var wg sync.WaitGroup 13 | 14 | wg.Add(10) 15 | for i := 0; i < 10; i++ { 16 | go func() { 17 | defer wg.Done() 18 | // 对变量count执行10次加1 19 | for j := 0; j < 100000; j++ { 20 | mu.Lock() 21 | count++ //不是一个原子操作 22 | mu.Unlock() 23 | } 24 | }() 25 | } 26 | // 等待10个goroutine完成 27 | wg.Wait() 28 | fmt.Println(count) //1000000 29 | } 30 | -------------------------------------------------------------------------------- /16-group/example_schedgroup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/mdlayher/schedgroup" 9 | ) 10 | 11 | func main() { 12 | 13 | sg := schedgroup.New(context.Background()) 14 | 15 | // 设置子任务分别在100、200、300之后执行 16 | for i := 0; i < 3; i++ { 17 | n := i + 1 18 | sg.Delay(time.Duration(n)*100*time.Millisecond, func() { 19 | log.Println(n) //输出任务编号 20 | }) 21 | } 22 | 23 | // 等待所有的子任务都完成 24 | if err := sg.Wait(); err != nil { 25 | log.Fatalf("failed to wait: %v", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /05-WaitGroup/question-1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // 常见问题一:计数器设置为负值 6 | 7 | // 第一种方法是:调用 Add 的时候传递一个负数 8 | func main() { 9 | var wg sync.WaitGroup 10 | wg.Add(10) 11 | 12 | wg.Add(-10) //将-10作为参数调用Add,计数值被设置为0 13 | 14 | wg.Add(-1) //将-1作为参数调用Add,如果加上-1计数值就会变为负数。这是不对的,所以会触发panic 15 | } 16 | 17 | // 2 调用 Done 方法的次数过多,超过了 WaitGroup 的计数值。 18 | // 使用 WaitGroup 的正确姿势是,预先确定好 WaitGroup 的计数值,然后调用相同次数的 Done 完成相应的任务。 19 | func main() { 20 | var wg sync.WaitGroup 21 | wg.Add(1) 22 | 23 | wg.Done() 24 | 25 | wg.Done() // 1-1-1=-1 26 | } 27 | -------------------------------------------------------------------------------- /12-channel/4-contorlConcurrent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var limit = make(chan int, 3) 4 | 5 | func main() { 6 | // ………… 7 | for _, w := range work { 8 | go func() { 9 | limit <- 1 10 | w() // if panic: use defer 11 | <-limit 12 | }() 13 | } 14 | // ………… 15 | } 16 | 17 | /* Analysis: 18 | 构建一个缓冲型的 channel,容量为 3。接着遍历任务列表,每个任务启动一个 goroutine 去完成。 19 | 真正执行任务,访问第三方的动作在 w() 中完成,在执行 w() 之前, 20 | 先要从 limit 中拿“许可证”,拿到许可证之后,才能执行 w(), 21 | 并且在执行完任务,要将“许可证”归还。这样就可以控制同时运行的 goroutine 数。 22 | 23 | limit <- 1, 如果在外层,就是控制系统 goroutine 的数量,可能会阻塞 for 循环,影响业务逻辑。 24 | */ 25 | -------------------------------------------------------------------------------- /08-Map/1-struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type mapKey struct { 6 | key int 7 | } 8 | 9 | func main() { 10 | var m = make(map[mapKey]string) 11 | var key = mapKey{10} 12 | 13 | m[key] = "hello" 14 | fmt.Printf("m[key]=%s\n", m[key]) 15 | 16 | // 修改key的字段的值后再次查询map,无法获取刚才add进去的值 17 | key.key = 100 18 | fmt.Printf("再次查询m[key]=%s\n", m[key]) 19 | } 20 | 21 | /* Notice: 22 | 这里有一点需要注意,如果使用 struct 类型做 key 其实是有坑的, 23 | 因为如果 struct 的某个字段值修改了,查询 map 时无法获取它 add 进去的值, 24 | 25 | Solve: 26 | 如果要使用 struct 作为 key,我们要保证 struct 对象在逻辑上是不可变的, 27 | 这样才会保证 map 的逻辑没有问题。 28 | */ 29 | -------------------------------------------------------------------------------- /12-channel/5-simple-check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 一个比较粗糙的检查channel 关闭的方式 6 | func IsClosed(ch <-chan any) bool { 7 | select { 8 | case <-ch: 9 | return true 10 | default: 11 | } 12 | return false 13 | } 14 | func main() { 15 | c := make(chan any) 16 | fmt.Println(IsClosed(c)) //false 17 | close(c) 18 | fmt.Println(IsClosed(c)) //true 19 | } 20 | 21 | 22 | /* Analysis: 23 | 看一下代码,其实存在很多问题。 24 | 首先,IsClosed 函数是一个有副作用的函数。每调用一次,都会读出 channel 里的一个元素,改变了 channel 的状态。 25 | 26 | 其次,IsClosed 函数返回的结果仅代表调用那个瞬间,并不能保证调用之后会不会有其他 goroutine 对它进行了一些操作, 27 | 改变了它的这种状态。 28 | */ -------------------------------------------------------------------------------- /16-group/example_SizedGroup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/go-pkgz/syncs" 10 | ) 11 | 12 | func main() { 13 | // 设置goroutine数是10 14 | swg := syncs.NewSizedGroup(10) 15 | // swg := syncs.NewSizedGroup(10, syncs.Preemptive) 16 | var c uint32 17 | 18 | // 执行1000个子任务,只会有10个goroutine去执行 19 | for i := 0; i < 1000; i++ { 20 | swg.Go(func(ctx context.Context) { 21 | time.Sleep(5 * time.Millisecond) 22 | atomic.AddUint32(&c, 1) 23 | }) 24 | } 25 | 26 | // 等待任务完成 27 | swg.Wait() 28 | // 输出结果 29 | fmt.Println(c) 30 | } 31 | -------------------------------------------------------------------------------- /07-Once/error02.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | func main() { 11 | var once sync.Once 12 | var googleConn net.Conn // 到Google网站的一个连接 13 | 14 | once.Do(func() { 15 | // 建立到google.com的连接,有可能因为网络的原因,googleConn并没有建立成功,此时它的值为nil 16 | googleConn, _ = net.Dial("tcp", "google.com:80") 17 | }) 18 | // 发送http请求 19 | googleConn.Write([]byte("GET / HTTP/1.1\r\nHost: google.com\r\n Accept: */*\r\n\r\n")) 20 | io.Copy(os.Stdout, googleConn) 21 | } 22 | 23 | /* 24 | 如果 f 方法执行的时候 panic,或者 f 执行初始化资源的时候失败了, 25 | 这个时候,Once 还是会认为初次执行已经成功了,即使再次调用 Do 方法,也不会再次执行 f。 26 | */ 27 | -------------------------------------------------------------------------------- /07-Once/1-init.go: -------------------------------------------------------------------------------- 1 | // 1. 比如定义 package 级别的变量,这样程序在启动的时候就可以初始化: 2 | 3 | package abc 4 | 5 | import time 6 | 7 | var startTime = time.Now() 8 | 9 | // --------------------------------- 10 | 11 | // 2. 或者在 init 函数中进行初始化: 12 | package abc 13 | 14 | var startTime time.Time 15 | 16 | func init() { 17 | startTime = time.Now() 18 | } 19 | 20 | // --------------------------------- 21 | // 3. 或者在 main 函数开始执行的时候,执行一个初始化的函数 22 | package abc 23 | 24 | var startTime time.Tim 25 | 26 | func initApp() { 27 | startTime = time.Now() 28 | } 29 | func main() { 30 | initApp() 31 | } 32 | 33 | // 这三种方法都是线程安全的,并且后两种方法还可以根据传入的参数实现定制化的初始化操作。 -------------------------------------------------------------------------------- /03-Mutex/3-queue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type SliceQueue struct { 6 | data []interface{} 7 | mu sync.Mutex 8 | } 9 | 10 | func NewSliceQueue(n int) (q *SliceQueue) { 11 | return &SliceQueue{data: make([]interface{}, 0, n)} 12 | } 13 | 14 | // Enqueue 把值放在队尾 15 | func (q *SliceQueue) Enqueue(v interface{}) { 16 | q.mu.Lock() 17 | q.data = append(q.data, v) 18 | q.mu.Unlock() 19 | } 20 | 21 | // Dequeue 移去队头并返回 22 | func (q *SliceQueue) Dequeue() interface{} { 23 | q.mu.Lock() 24 | if len(q.data) == 0 { 25 | q.mu.Unlock() 26 | return nil 27 | } 28 | v := q.data[0] 29 | q.data = q.data[1:] 30 | q.mu.Unlock() 31 | return v 32 | } 33 | -------------------------------------------------------------------------------- /10-Context/1-withValue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | ctx := context.Background() 10 | process(ctx) 11 | 12 | ctx = context.WithValue(ctx, "traceId", "xxml-2022") 13 | process(ctx) 14 | } 15 | 16 | func process(ctx context.Context) { 17 | traceId, ok := ctx.Value("traceId").(string) 18 | if ok { 19 | fmt.Printf("process over. trace_id=%s\n", traceId) 20 | } else { 21 | fmt.Printf("process over. no trace_id\n") 22 | } 23 | } 24 | 25 | // 传递共享的数据 26 | /* Analysis: 27 | 第一次调用 process 函数时,ctx 是一个空的 context,自然取不出来 traceId。 28 | 第二次,通过 WithValue 函数创建了一个 context,并赋上了 traceId 这个 key, 29 | 自然就能取出来传入的 value 值。 30 | 31 | */ 32 | -------------------------------------------------------------------------------- /07-Once/2-init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // 使用互斥锁保证线程(goroutine)安全 10 | var connMu sync.Mutex 11 | var conn net.Conn 12 | 13 | func getConn() net.Conn { 14 | connMu.Lock() 15 | defer connMu.Unlock() 16 | 17 | // 返回已创建好的连接 18 | if conn != nil { 19 | return conn 20 | } 21 | 22 | // 创建连接 23 | conn, _ = net.DialTimeout("tcp", "baidu.com:80", 10*time.Second) 24 | return conn 25 | } 26 | 27 | // 使用连接 28 | func main() { 29 | conn := getConn() 30 | if conn == nil { 31 | panic("conn is nil") 32 | } 33 | } 34 | 35 | /* 问题: 36 | 这种方式虽然实现起来简单,但是有性能问题。 37 | 一旦连接创建好,每次请求的时候还是得竞争锁才能读取到这个连接, 38 | 这是比较浪费资源的,因为连接如果创建好之后,其实就不需要锁的保护了。 39 | */ 40 | -------------------------------------------------------------------------------- /12-channel/2-timer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int) 10 | quit := make(chan bool) 11 | 12 | go func() { 13 | for { 14 | select { 15 | case num := <-ch: //如果有数据,下面打印。但是有可能ch一直没数据 16 | fmt.Println("received num = ", num) 17 | case <-time.After(3 * time.Second): //上面的ch如果一直没数据会阻塞,那么select也会检测其他case条件,检测到后3秒超时 18 | fmt.Println("TimeOut") 19 | quit <- true 20 | } 21 | } 22 | }() 23 | for i := 0; i < 3; i++ { 24 | ch <- i 25 | time.Sleep(time.Second) 26 | } 27 | <-quit //这里暂时阻塞,直到可读 28 | fmt.Println("Over") 29 | } 30 | 31 | /* Analysis:实现超时控制 32 | 等待若干秒后,如果 ch 还没有读出数据或者被关闭,就直接结束 33 | 34 | */ 35 | -------------------------------------------------------------------------------- /16-group/example_all.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/vardius/gollback" 10 | ) 11 | 12 | func main() { 13 | rs, errs := gollback.All( // 调用All方法 14 | context.Background(), 15 | func(ctx context.Context) (interface{}, error) { 16 | time.Sleep(3 * time.Second) 17 | return 1, nil // 第一个任务没有错误,返回1 18 | }, 19 | func(ctx context.Context) (interface{}, error) { 20 | return nil, errors.New("failed") // 第二个任务返回一个错误 21 | }, 22 | func(ctx context.Context) (interface{}, error) { 23 | return 3, nil // 第三个任务没有错误,返回3 24 | }, 25 | ) 26 | 27 | fmt.Println(rs) // 输出子任务的结果 28 | fmt.Println(errs) // 输出子任务的错误信息 29 | } 30 | -------------------------------------------------------------------------------- /06-Cond/1-athlete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | c := sync.NewCond(&sync.Mutex{}) 12 | var ready int 13 | 14 | for i := 0; i < 10; i++ { 15 | go func(i int) { 16 | time.Sleep(time.Duration(rand.Int63n(10)) * time.Second) 17 | 18 | // 加锁更改等待条件 19 | c.L.Lock() 20 | ready++ 21 | c.L.Unlock() 22 | 23 | log.Printf("运动员#%d 已准备就绪\n", i) 24 | // 广播唤醒所有的等待者 25 | c.Broadcast() 26 | }(i) 27 | } 28 | 29 | c.L.Lock() 30 | for ready != 10 { 31 | c.Wait() 32 | log.Println("裁判员被唤醒一次") 33 | } 34 | c.L.Unlock() 35 | 36 | //所有的运动员是否就绪 37 | log.Println("所有运动员都准备就绪。比赛开始,3,2,1, ......") 38 | } 39 | -------------------------------------------------------------------------------- /01-Mutex/04-counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | var counter Counter3 10 | for i := 0; i < 10; i++ { 11 | go func() { 12 | for { 13 | counter.Count() 14 | time.Sleep(time.Millisecond) 15 | } 16 | }() 17 | } 18 | for { 19 | counter.Incr() 20 | time.Sleep(time.Second) 21 | } 22 | } 23 | 24 | // 线程安全的计数器类型 25 | type Counter3 struct { 26 | mu sync.RWMutex 27 | count uint64 28 | } 29 | 30 | // +1 的方法,内部使用互斥锁保护 31 | func (c *Counter3) Incr() { 32 | c.mu.Lock() 33 | c.count++ 34 | c.mu.Unlock() 35 | } 36 | 37 | // 计数器得到的值 需要保护 38 | func (c *Counter3) Count() uint64 { 39 | c.mu.RLock() 40 | defer c.mu.RUnlock() 41 | return c.count 42 | } 43 | -------------------------------------------------------------------------------- /04-RWMutex/1-Writer-nReader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | var counter Counter 10 | for i := 0; i < 10; i++ { // 10个reader 11 | go func() { 12 | for { 13 | counter.Count() // 计数器读操作 14 | time.Sleep(time.Millisecond) 15 | } 16 | }() 17 | } 18 | 19 | for { // 一个writer 20 | 21 | counter.Incr() // 计数器写操作 22 | time.Sleep(time.Second) 23 | } 24 | } 25 | 26 | // 一个线程安全的计数器 27 | type Counter struct { 28 | mu sync.RWMutex 29 | count uint64 30 | } 31 | 32 | // 使用写锁保护 33 | func (c *Counter) Incr() { 34 | c.mu.Lock() 35 | c.count++ 36 | c.mu.Unlock() 37 | } 38 | 39 | // 使用读锁保护 40 | func (c *Counter) Count() uint64 { 41 | c.mu.RLock() 42 | defer c.mu.RUnlock() 43 | return c.count 44 | } 45 | -------------------------------------------------------------------------------- /01-Mutex/counter2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | //封装好计数器 10 | var counter Counter2 11 | 12 | // 并发控制任务编排 13 | var wg sync.WaitGroup 14 | 15 | wg.Add(10) 16 | //启动10个goroutine 17 | for i := 0; i < 10; i++ { 18 | go func() { 19 | defer wg.Done() 20 | for j := 0; j < 100000; j++ { 21 | counter.Incr() 22 | 23 | } 24 | }() 25 | } 26 | wg.Wait() 27 | fmt.Println(counter.Count()) 28 | } 29 | 30 | // 线程安全计数器类型 31 | type Counter2 struct { 32 | mu sync.Mutex 33 | count uint64 34 | } 35 | 36 | // +1 方法 37 | func (c *Counter2) Incr() { 38 | c.mu.Lock() 39 | c.count++ 40 | c.mu.Unlock() 41 | } 42 | 43 | // 得到计数器的值,也需要锁保护 44 | func (c *Counter2) Count() uint64 { 45 | c.mu.Lock() 46 | defer c.mu.Unlock() 47 | return c.count 48 | } 49 | -------------------------------------------------------------------------------- /12-channel/3-uncoupled.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | taskCh := make(chan int, 100) 10 | go worker(taskCh) 11 | 12 | // 塞任务 13 | for i := 0; i < 10; i++ { 14 | taskCh <- i 15 | } 16 | 17 | // 等待 1 小时 18 | select { 19 | case <-time.After(time.Hour): 20 | } 21 | } 22 | 23 | func worker(taskCh <-chan int) { 24 | const N = 5 25 | // 启动 5 个工作协程 26 | for i := 0; i < N; i++ { 27 | go func(id int) { 28 | for { 29 | task := <-taskCh 30 | fmt.Printf("finish task: %d by worker %d\n", task, id) 31 | time.Sleep(time.Second) 32 | } 33 | }(i) 34 | } 35 | } 36 | 37 | /*Analysis: 解耦生产方和消费方 38 | 服务启动时,启动 n 个 worker,作为工作协程池, 39 | 这些协程工作在一个 for {} 无限循环里,从某个 channel 消费工作任务并执行: 40 | 5 个工作协程在不断地从工作队列里取任务,生产方只管往 channel 发送任务即可,解耦生产方和消费方。 41 | */ 42 | -------------------------------------------------------------------------------- /10-Context/2-withTimeOut.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) // 修改此处超时时间,可打印出不同结果 11 | defer cancel() // 避免其他地方忘记cancel,且重复调用不影响 12 | 13 | ids := fetchWebData(ctx) 14 | 15 | fmt.Println(ids) 16 | } 17 | 18 | func fetchWebData(ctx context.Context) (res []int64) { 19 | select { 20 | case <-time.After(3 * time.Second): 21 | return []int64{100, 200, 300} 22 | case <-ctx.Done(): 23 | return []int64{1, 2, 3} 24 | } 25 | } 26 | 27 | // 定时取消 28 | /* Analysis: 29 | 30 | 注意一个细节,WithTimeOut 函数返回的 context 和 cancelFun 是分开的。 31 | context 本身并没有取消函数,这样做的原因是取消函数只能由外层函数调用, 32 | 防止子节点 context 调用取消函数,从而严格控制信息的流向:由父节点 context 流向子节点 context。 33 | */ 34 | -------------------------------------------------------------------------------- /06-Cond/question-2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | c := sync.NewCond(&sync.Mutex{}) 12 | var ready int 13 | 14 | for i := 0; i < 10; i++ { 15 | go func(i int) { 16 | time.Sleep(time.Duration(rand.Int63n(10)) * time.Second) 17 | 18 | // 加锁更改等待条件 19 | c.L.Lock() 20 | ready++ 21 | c.L.Unlock() 22 | 23 | log.Printf("运动员#%d 已准备就绪\n", i) 24 | // 广播唤醒所有的等待者 25 | c.Broadcast() 26 | }(i) 27 | } 28 | 29 | c.L.Lock() 30 | // for ready != 10 { 31 | c.Wait() 32 | log.Println("裁判员被唤醒一次") 33 | // } 34 | c.L.Unlock() 35 | 36 | //所有的运动员是否就绪 37 | log.Println("所有运动员都准备就绪。比赛开始,3,2,1, ......") 38 | } 39 | 40 | /* 41 | 原因在于,每一个运动员准备好之后都会唤醒所有的等待者, 42 | 也就是这里的裁判员,比如第一个运动员准备好后就唤醒了裁判员, 43 | 结果这个裁判员傻傻地没做任何检查,以为所有的运动员都准备好了,就继续执行了。 44 | */ 45 | -------------------------------------------------------------------------------- /15-CyclicBarrier/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/marusama/cyclicbarrier" 10 | ) 11 | 12 | // 创建一个只允许10个参与者通过的障碍物,每次当所有进程达到障碍物时,该动作将会被执行。 13 | func main() { 14 | cnt := 0 15 | b := cyclicbarrier.NewWithAction(10, func() error { 16 | cnt++ 17 | return nil 18 | }) 19 | 20 | wg := sync.WaitGroup{} 21 | for i := 0; i < 10; i++ { // 创建10个goroutine,其数量与障碍物中的参与者数量相同。 22 | wg.Add(1) 23 | go func() { 24 | for j := 0; j < 5; j++ { 25 | 26 | // 做一些复杂的任务5次。 27 | time.Sleep(100 * time.Millisecond) 28 | 29 | err := b.Await(context.TODO()) // 等待障碍物中其他参与者完成,然后执行障碍物的动作,将所有其他进程传递给下一轮。 30 | if err != nil { 31 | panic(err) 32 | } 33 | } 34 | wg.Done() 35 | }() 36 | } 37 | 38 | wg.Wait() 39 | fmt.Println(cnt) // cnt=5,它表示障碍物被推倒了5次。 40 | } 41 | -------------------------------------------------------------------------------- /07-Once/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync/atomic" 4 | 5 | func main() { 6 | // 源码地址:src/sync/once.go 7 | } 8 | 9 | type Once struct { 10 | done uint32 11 | m Mutex 12 | } 13 | 14 | func (o *Once) Do(f func()) { 15 | if atomic.LoadUint32(&o.done) == 0 { 16 | o.doSlow(f) 17 | } 18 | } 19 | 20 | func (o *Once) doSlow(f func()) { 21 | o.m.Lock() 22 | defer o.m.Unlock() 23 | // 双检查 24 | if o.done == 0 { 25 | defer atomic.StoreUint32(&o.done, 1) 26 | f() 27 | } 28 | } 29 | 30 | /* 31 | 一个正确的 Once 实现要使用一个互斥锁,这样初始化的时候 32 | 如果有并发的 goroutine,就会进入doSlow 方法。 33 | 互斥锁的机制保证只有一个 goroutine 进行初始化, 34 | 同时利用双检查的机制(double-checking),再次判断 o.done 35 | 是否为 0,如果为 0,则是第一次执行,执行完毕后, 36 | 就将 o.done 设置为 1,然后释放锁。即使此时有多个 goroutine 37 | 同时进入了 doSlow 方法,因为双检查的机制,后续的 goroutine 38 | 会看到 o.done 的值为 1,也不会再次执行 f。这样既保证了并发的 39 | goroutine 会等待 f 完成,而且还不会多次执行 f。 40 | */ 41 | -------------------------------------------------------------------------------- /06-Cond/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // 源码地址: src/sync/cond.go 5 | } 6 | 7 | //Cond 的实现非常简单,或者说复杂的逻辑已经被 Locker 或者 runtime 的等待队列实现了。 8 | 9 | type Cond struct { 10 | noCopy noCopy 11 | 12 | // 当观察或者修改等待条件的时候需要加锁 13 | L Locker 14 | 15 | // 等待队列 16 | notify notifyList 17 | checker copyChecker 18 | } 19 | 20 | func NewCond(l Locker) *Cond { 21 | return &Cond{L: l} 22 | } 23 | 24 | func (c *Cond) Wait() { 25 | c.checker.check() 26 | // 增加到等待队列中 27 | t := runtime_notifyListAdd(&c.notify) 28 | c.L.Unlock() 29 | // 阻塞休眠直到被唤醒 30 | runtime_notifyListWait(&c.notify, t) 31 | c.L.Lock() 32 | } 33 | 34 | func (c *Cond) Signal() { 35 | c.checker.check() 36 | runtime_notifyListNotifyOne(&c.notify) 37 | } 38 | 39 | func (c *Cond) Broadcast() { 40 | c.checker.check() 41 | runtime_notifyListNotifyAll(&c.notify) 42 | } -------------------------------------------------------------------------------- /12-channel/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "unsafe" 4 | 5 | func main() { 6 | // source: src/runtime/chan.go 7 | } 8 | 9 | // channel 底层数据结构 10 | type hchan struct { 11 | // chan 里元素数量 12 | qcount uint 13 | // chan 底层循环数组的长度 14 | dataqsiz uint 15 | // 指向底层循环数组的指针 16 | // 只针对有缓冲的 channel 17 | buf unsafe.Pointer 18 | // chan 中元素大小 19 | elemsize uint16 20 | // chan 是否被关闭的标志 21 | closed uint32 22 | // chan 中元素类型 23 | elemtype *_type // element type 24 | // 已发送元素在循环数组中的索引 25 | sendx uint // send index 26 | // 已接收元素在循环数组中的索引 27 | recvx uint // receive index 28 | // 等待接收的 goroutine 队列 29 | recvq waitq // list of recv waiters 30 | // 等待发送的 goroutine 队列 31 | sendq waitq // list of send waiters 32 | 33 | // 保护 hchan 中所有字段 34 | lock mutex 35 | } 36 | 37 | // waitq 是 sudog 的一个双向链表,而 sudog 实际上是对 goroutine 的一个封装: 38 | type waitq struct { 39 | first *sudog 40 | last *sudog 41 | } 42 | -------------------------------------------------------------------------------- /05-WaitGroup/1-counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // 线程安全的计数器 10 | type Counter struct { 11 | mu sync.Mutex 12 | count uint64 13 | } 14 | 15 | // 对计数值加一 16 | func (c *Counter) Incr() { 17 | c.mu.Lock() 18 | c.count++ 19 | c.mu.Unlock() 20 | } 21 | 22 | // 获取当前的计数值 23 | func (c *Counter) Count() uint64 { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | return c.count 27 | } 28 | 29 | // sleep 1秒,然后计数值加1 30 | func worker(c *Counter, wg *sync.WaitGroup) { 31 | defer wg.Done() 32 | time.Sleep(time.Second) 33 | c.Incr() 34 | } 35 | 36 | func main() { 37 | var counter Counter 38 | 39 | var wg sync.WaitGroup 40 | wg.Add(10) // WaitGroup的值设置为10 41 | 42 | for i := 0; i < 10; i++ { // 启动10个goroutine执行加1任务 43 | go worker(&counter, &wg) 44 | } 45 | // 检查点,等待goroutine都完成任务 46 | wg.Wait() 47 | // 输出当前计数器的值 48 | fmt.Println(counter.Count()) 49 | } 50 | -------------------------------------------------------------------------------- /16-group/example_errgroup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | func main() { 12 | var g errgroup.Group 13 | 14 | // 启动第一个子任务,它执行成功 15 | g.Go(func() error { 16 | time.Sleep(5 * time.Second) 17 | fmt.Println("exec #1") 18 | return nil 19 | }) 20 | // 启动第二个子任务,它执行失败 21 | g.Go(func() error { 22 | time.Sleep(10 * time.Second) 23 | fmt.Println("exec #2") 24 | return errors.New("failed to exec #2") 25 | }) 26 | 27 | // 启动第三个子任务,它执行成功 28 | g.Go(func() error { 29 | time.Sleep(15 * time.Second) 30 | fmt.Println("exec #3") 31 | return nil 32 | }) 33 | // 等待三个任务都完成 34 | if err := g.Wait(); err == nil { 35 | fmt.Println("Successfully exec all") 36 | } else { 37 | fmt.Println("failed:", err) 38 | } 39 | } 40 | 41 | /* 42 | 在这个例子中,启动了三个子任务,其中,子任务 2 会返回执行失败,其它两个执行成功。 43 | 在三个子任务都执行后,group.Wait 才会返回第 2 个子任务的错误。 44 | */ 45 | -------------------------------------------------------------------------------- /06-Cond/question-1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | c := sync.NewCond(&sync.Mutex{}) 12 | var ready int 13 | 14 | for i := 0; i < 10; i++ { 15 | go func(i int) { 16 | time.Sleep(time.Duration(rand.Int63n(10)) * time.Second) 17 | 18 | // 加锁更改等待条件 19 | c.L.Lock() 20 | ready++ 21 | c.L.Unlock() 22 | 23 | log.Printf("运动员#%d 已准备就绪\n", i) 24 | // 广播唤醒所有的等待者 25 | c.Broadcast() 26 | }(i) 27 | } 28 | 29 | // c.L.Lock() 30 | for ready != 10 { 31 | c.Wait() 32 | log.Println("裁判员被唤醒一次") 33 | } 34 | // c.L.Unlock() 35 | 36 | //所有的运动员是否就绪 37 | log.Println("所有运动员都准备就绪。比赛开始,3,2,1, ......") 38 | } 39 | 40 | /* 41 | 出现这个问题的原因在于,cond.Wait 方法的实现是, 42 | 把当前调用者加入到 notify 队列之中后会释放锁(如果不释放锁, 43 | 其他 Wait 的调用者就没有机会加入到 notify 队列中了),然后一直等待; 44 | 等调用者被唤醒之后,又会去争抢这把锁。如果调用 Wait 之前不加锁的话, 45 | 就有可能 Unlock 一个未加锁的 Locker。所以切记,调用 cond.Wait 方法之前一定要加锁。 46 | */ 47 | -------------------------------------------------------------------------------- /10-Context/1-withValue-http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const requestIDKey int = 0 4 | 5 | func WithRequestID(next http.Handler) http.Handler { 6 | return http.HandlerFunc( 7 | func(rw http.ResponseWriter, req *http.Request) { 8 | // 从 header 中提取 request-id 9 | reqID := req.Header.Get("X-Request-ID") 10 | // 创建 valueCtx。使用自定义的类型,不容易冲突 11 | ctx := context.WithValue( 12 | req.Context(), requestIDKey, reqID) 13 | 14 | // 创建新的请求 15 | req = req.WithContext(ctx) 16 | 17 | // 调用 HTTP 处理函数 18 | next.ServeHTTP(rw, req) 19 | } 20 | ) 21 | } 22 | 23 | // 获取 request-id 24 | func GetRequestID(ctx context.Context) string { 25 | ctx.Value(requestIDKey).(string) 26 | } 27 | 28 | func Handle(rw http.ResponseWriter, req *http.Request) { 29 | // 拿到 reqId,后面可以记录日志等等 30 | reqID := GetRequestID(req.Context()) 31 | ... 32 | } 33 | 34 | func main() { 35 | handler := WithRequestID(http.HandlerFunc(Handle)) 36 | http.ListenAndServe("/", handler) 37 | } -------------------------------------------------------------------------------- /16-group/example_errgroup_cancel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | type Data struct { 12 | } 13 | 14 | func getData() (*Data, error) { 15 | time.Sleep(3 * time.Second) 16 | return &Data{}, nil 17 | } 18 | 19 | func main() { 20 | c, cancel := context.WithCancel(context.Background()) 21 | defer cancel() 22 | g, ctx := errgroup.WithContext(c) 23 | 24 | datas := make(chan *Data, 10) 25 | 26 | g.Go(func() error { 27 | // 业务逻辑 28 | data, err := getData() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | select { 34 | case <-ctx.Done(): 35 | return ctx.Err() 36 | default: 37 | } 38 | datas <- data 39 | return nil 40 | }) 41 | 42 | go func() { 43 | time.Sleep(1 * time.Second) 44 | cancel() 45 | }() 46 | 47 | err := g.Wait() 48 | if err == nil { 49 | fmt.Println("success") 50 | return 51 | } 52 | fmt.Println("fail", err) 53 | } 54 | -------------------------------------------------------------------------------- /11-atomic/1-usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | type Config struct { 12 | NodeName string 13 | Addr string 14 | Count int32 15 | } 16 | 17 | func loadNewConfig() Config { 18 | return Config{ 19 | NodeName: "本地", 20 | Addr: "127.0.0.1", 21 | Count: rand.Int31(), 22 | } 23 | } 24 | func main() { 25 | var config atomic.Value 26 | config.Store(loadNewConfig()) 27 | var cond = sync.NewCond(&sync.Mutex{}) 28 | 29 | // 设置新的config 30 | go func() { 31 | for { 32 | time.Sleep(time.Duration(5+rand.Int63n(5)) * time.Second) 33 | config.Store(loadNewConfig()) 34 | cond.Broadcast() // 通知等待着配置已变更 35 | } 36 | }() 37 | 38 | go func() { 39 | for { 40 | cond.L.Lock() 41 | cond.Wait() // 等待变更信号 42 | c := config.Load().(Config) // 读取新的配置 43 | fmt.Printf("new config: %+v\n", c) 44 | cond.L.Unlock() 45 | } 46 | }() 47 | 48 | select {} 49 | } 50 | -------------------------------------------------------------------------------- /09-Pool/1-usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | var pool *sync.Pool 9 | 10 | type Person struct { 11 | Name string 12 | } 13 | 14 | func initPool() { 15 | pool = &sync.Pool{ 16 | New: func() interface{} { 17 | fmt.Println("Creating a new Person...") 18 | return new(Person) 19 | }, 20 | } 21 | } 22 | 23 | func main() { 24 | initPool() 25 | 26 | p := pool.Get().(*Person) 27 | fmt.Println("首次从 pool 里获取: ", p) 28 | 29 | p.Name = "first" 30 | fmt.Printf("设置 p.Name = %s\n", p.Name) 31 | 32 | pool.Put(p) 33 | 34 | fmt.Println("Pool 里已有一个对象: &{first}, 调用 Get: ", pool.Get().(*Person)) 35 | fmt.Println("Pool 没有对象了:, 调用 Get: ", pool.Get().(*Person)) 36 | 37 | } 38 | 39 | /* Analysis: 40 | 首先,需要初始化 Pool,唯一需要做的就是设置好 New 函数。 41 | 当调用 Get 方法时,如果池子里缓存了对象,就直接返回缓存的对象。 42 | 如果没有“存货”,则调用 New 两数创建一个新的对象。 43 | 44 | 另外,Get 方法取出来的对象和上次Put 进去的对象实际上是同一个, 45 | Pool 没有做任何“清空”的处理。但不应当对此有任何假设, 46 | 因为在实际的并发使用场景中,无法保证这种顺序,最好的做法是在_Put 前,将对象清空。 47 | 48 | */ 49 | -------------------------------------------------------------------------------- /09-Pool/ginpool.go: -------------------------------------------------------------------------------- 1 | /* source: https://github.com/gin-gonic/gin/blob/55e27f12465e058058180280d5f0bdc473eb3302/gin.go#L205 2 | gin框架,对context的取用也使用了 sync.pool 3 | 4 | 5 | */ 6 | 7 | // 设置 New 函数 8 | engine.pool.New = func() any { 9 | return engine.allocateContext(engine.maxParams) 10 | } 11 | 12 | func (engine *Engine) allocateContext(maxParams uint16) *Context { 13 | v := make(Params, 0, maxParams) 14 | skippedNodes := make([]skippedNode, 0, engine.maxSections) 15 | return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} 16 | } 17 | 18 | // 使用 19 | // ServeHTTP conforms to the http.Handler interface. 20 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 21 | c := engine.pool.Get().(*Context) 22 | c.writermem.reset(w) 23 | c.Request = req 24 | c.reset() 25 | 26 | engine.handleHTTPRequest(c) 27 | 28 | engine.pool.Put(c) 29 | } 30 | 31 | // 先调用 Get 取出来缓存的对象,然后会做一些reset操作, 32 | // 再执行 handleHTTPRequest,最后再 Put 回 Pool。 33 | // 另外,Echo框架也使用了 sync.Pool 来管理 context,并且几乎达到了零堆内存分配。 -------------------------------------------------------------------------------- /02-Mutex/4-deadlock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | 10 | func main() { 11 | // 派出所证明 12 | var psCertificate sync.Mutex 13 | // 物业证明 14 | var propertyCertificate sync.Mutex 15 | 16 | 17 | var wg sync.WaitGroup 18 | wg.Add(2) // 需要派出所和物业都处理 19 | 20 | 21 | // 派出所处理goroutine 22 | go func() { 23 | defer wg.Done() // 派出所处理完成 24 | 25 | 26 | psCertificate.Lock() 27 | defer psCertificate.Unlock() 28 | 29 | 30 | // 检查材料 31 | time.Sleep(5 * time.Second) 32 | // 请求物业的证明 33 | propertyCertificate.Lock() 34 | propertyCertificate.Unlock() 35 | }() 36 | 37 | 38 | // 物业处理goroutine 39 | go func() { 40 | defer wg.Done() // 物业处理完成 41 | 42 | 43 | propertyCertificate.Lock() 44 | defer propertyCertificate.Unlock() 45 | 46 | 47 | // 检查材料 48 | time.Sleep(5 * time.Second) 49 | // 请求派出所的证明 50 | psCertificate.Lock() 51 | psCertificate.Unlock() 52 | }() 53 | 54 | 55 | wg.Wait() 56 | fmt.Println("成功完成") 57 | } -------------------------------------------------------------------------------- /12-channel/6-Nsender-receiver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | rand.Seed(time.Now().UnixNano()) 11 | 12 | const Max = 100000 13 | const NumSenders = 10 14 | 15 | dataCh := make(chan int, 100) 16 | stopCh := make(chan struct{}) 17 | 18 | // senders 19 | for i := 0; i < NumSenders; i++ { 20 | go func() { 21 | for { 22 | select { 23 | case <-stopCh: 24 | return 25 | case dataCh <- rand.Intn(Max): 26 | } 27 | } 28 | }() 29 | } 30 | 31 | // the receiver 32 | go func() { 33 | for value := range dataCh { 34 | if value == Max-1 { 35 | fmt.Println("send stop signal to senders.") 36 | close(stopCh) 37 | return 38 | } 39 | 40 | fmt.Println(value) 41 | } 42 | }() 43 | 44 | select { 45 | case <-time.After(time.Second * 10): 46 | } 47 | } 48 | 49 | /*Analysis: 50 | 这里的 stopCh 就是信号 channel,它本身只有一个 sender,因此可以直接关闭它。 51 | senders 收到了关闭信号后,select 分支 “case <- stopCh” 被选中,退出函数,不再发送数据。 52 | 53 | 需要说明的是,上面的代码并没有明确关闭 dataCh。 54 | 在 Go 语言中,对于一个 channel,如果最终没有任何 goroutine 引用它, 55 | 不管 channel 有没有被关闭,最终都会被 gc 回收。所以,在这种情形下,不关闭 channel,让 gc 代劳。 56 | */ 57 | -------------------------------------------------------------------------------- /14-SingleFlight/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // 结构 6 | 7 | type Group struct { // singleflight实体 8 | mu sync.Mutex // 互斥锁 9 | m map[string]*call // 懒加载 10 | } 11 | 12 | type call struct { 13 | wg sync.WaitGroup 14 | // 存储 调用singleflight.Do()方法返回的结果 15 | val interface{} 16 | err error 17 | 18 | // 调用singleflight.Forget(key)时将对应的key从Group.m中删除 19 | forgotten bool 20 | 21 | // 通俗的理解成singleflight合并的并发请求数 22 | dups int 23 | // 存储 调用singleflight.DoChan()方法返回的结果 24 | chans []chan<- Result 25 | } 26 | 27 | type Result struct { 28 | Val interface{} 29 | Err error 30 | Shared bool 31 | } 32 | 33 | // 对外暴露的方法 34 | // 这个方法比较灵性,通过两个 defer 巧妙的区分了到底是发生了 panic 还是用户主动调用了 runtime.Goexit,逻辑还是比较复杂 35 | func Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 36 | 37 | // 和 do 唯一的区别是 go g.doCall(c, key, fn),但对起了一个 goroutine 来执行, 38 | // 并通过 channel 来返回数据,这样外部可以自定义超时逻辑,防止因为 fn 的阻塞,导致大量请求都被阻塞。 39 | func DoChan(key string, fn func() (interface{}, error)) <-chan Result 40 | 41 | // 手动移除某个 key,让后续请求能走 doCall 的逻辑,而不是直接阻塞。 42 | func Forget(key string) 43 | 44 | // DoChan()和Do()最大的区别是DoChan()属于异步调用,返回一个channel,解决同步调用时的阻塞问题 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-concurrent-programming 2 | 3 | ## Go语言并发编程实战 4 | 〉思维导图: 链接:https://www.zhixi.com/view/71545034密码:9960 5 | 6 |
7 | B站视频目录 8 | 9 | - [Mutex: 如何解决资源并发访问问题-01](https://www.bilibili.com/video/BV1qe4y127TV/) 10 | 11 | - [Mutex:常见的 4 种错误场景 02](https://www.bilibili.com/video/BV16e4y1y7Kb/) 12 | 13 | - [Mutex:如何拓展额外功能? 03](https://www.bilibili.com/video/BV1Sg411B75U/) 14 | 15 | - [RWMutex:读写锁的实现原理及避坑指南 04](https://www.bilibili.com/video/BV1rg411B71e/) 16 | 17 | - [WaitGroup:协同等待,任务编排 05](https://www.bilibili.com/video/BV1gm4y1F7su/) 18 | 19 | - [Cond:条件变量的实现机制及避坑 06](https://www.bilibili.com/video/BV11d4y1r7wE/) 20 | 21 | - [Once:见名知其意的并发原语 07](https://www.bilibili.com/video/BV1fe4y1W7Yb/) 22 | 23 | - [map:如何实现线程安全的map类型 08](https://www.bilibili.com/video/BV1Xg411i75P/) 24 | 25 | - [pool: 性能提升操作 09](https://www.bilibili.com/video/BV1324y117PL/) 26 | 27 | - [Context:Goroutine上下文 10](https://www.bilibili.com/video/BV1Vt4y1N7MD/) 28 | 29 | - [atomic:原子操作 11](https://www.bilibili.com/video/BV1xv4y127MP/) 30 | 31 |
32 | 33 | ## 关于 34 | 35 | Go语言并发编程实战由本人倾情打造。 36 | 37 | 同时还提供了视频讲解 - [B站主页](https://space.bilibili.com/270149833), 适合从入门到精通所有阶段的学习,欢迎大家阅读使用。 38 | 39 | 40 | -------------------------------------------------------------------------------- /08-Map/4-safeMap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type RWMap struct { // 一个读写锁保护的线程安全的map 6 | sync.RWMutex // 读写锁保护下面的map字段 7 | m map[int]int 8 | } 9 | 10 | // 新建一个RWMap 11 | func NewRWMap(n int) *RWMap { 12 | return &RWMap{ 13 | m: make(map[int]int, n), 14 | } 15 | } 16 | func (m *RWMap) Get(k int) (int, bool) { //从map中读取一个值 17 | m.RLock() 18 | defer m.RUnlock() 19 | v, existed := m.m[k] // 在锁的保护下从map中读取 20 | return v, existed 21 | } 22 | 23 | func (m *RWMap) Set(k int, v int) { // 设置一个键值对 24 | m.Lock() // 锁保护 25 | defer m.Unlock() 26 | m.m[k] = v 27 | } 28 | 29 | func (m *RWMap) Delete(k int) { //删除一个键 30 | m.Lock() // 锁保护 31 | defer m.Unlock() 32 | delete(m.m, k) 33 | } 34 | 35 | func (m *RWMap) Len() int { // map的长度 36 | m.RLock() // 锁保护 37 | defer m.RUnlock() 38 | return len(m.m) 39 | } 40 | 41 | func (m *RWMap) Each(f func(k, v int) bool) { // 遍历map 42 | m.RLock() //遍历期间一直持有读锁 43 | defer m.RUnlock() 44 | 45 | for k, v := range m.m { 46 | if !f(k, v) { 47 | return 48 | } 49 | } 50 | } 51 | 52 | /* Analysis: 53 | 上面实现线程安全的 map[int]int 类型 54 | 对 map 对象的操作,无非就是增删改查和遍历等几种常见操作。 55 | 我们可以把这些操作分为读和写两类,其中, 56 | 查询和遍历可以看做读操作,增加、修改和删除可以看做写操作。 57 | 如此,我们可以通过读写锁对相应的操作进行保护。 58 | */ 59 | -------------------------------------------------------------------------------- /12-channel/source-root.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type user struct { 9 | name string 10 | age int8 11 | } 12 | 13 | var u = user{name: "Messi", age: 35} 14 | var g = &u 15 | 16 | func modifyUser(pu *user) { 17 | fmt.Println("modifyUser Received Vaule", pu) 18 | pu.name = "Cristiano Ronaldo" 19 | } 20 | 21 | func printUser(u <-chan *user) { 22 | time.Sleep(2 * time.Second) 23 | fmt.Println("printUser goRoutine called", <-u) 24 | } 25 | 26 | func main() { 27 | c := make(chan *user, 5) 28 | c <- g 29 | fmt.Println(g) 30 | // modify g 31 | g = &user{name: "Neymar", age: 30} 32 | go printUser(c) 33 | go modifyUser(g) 34 | time.Sleep(5 * time.Second) 35 | fmt.Println(g) 36 | } 37 | 38 | /*Analysis: 39 | 一开始构造一个结构体 u,接着把 &u 赋值给指针 g,它的内容就是一个地址,指向 u。 40 | main 程序里,先把 g 发送到 c,根据 copy value 的本质,进入到 chan buf 里的就是 u的地址, 41 | 它是指针 g 的值(不是它指向的内容),所以打印从 channel 接收到的元素时, 42 | 它就是 &{Messi 35}。因此,这里并不是将指针 g “发送” 到了 channel 里,只是拷贝它的值而已。 43 | */ 44 | 45 | /*Channel 发送和接收元素的本质是什么? 46 | 47 | All transfer of value on the go channels happens with the copy of value. 48 | 49 | 就是说 channel 的发送和接收操作本质上都是 “值的拷贝”,无论是从 sender goroutine 50 | 的栈到 chan buf,还是从 chan buf 到 receiver goroutine, 51 | 或者是直接从 sender goroutine 到 receiver goroutine。 52 | */ 53 | -------------------------------------------------------------------------------- /10-Context/3-avoid.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | 11 | // 这是一个可以生成无限整数的协程,但如果我只需要它产生的前 5 个数,那么就会发生 goroutine 泄漏: 12 | for n := range gen() { 13 | fmt.Println(n) 14 | if n == 5 { 15 | break 16 | // 当 n == 5 的时候,直接 break 掉。 17 | // 那么 gen 函数的协程就会执行无限循环,永远不会停下来。 18 | // 发生了 goroutine 泄漏。 19 | } 20 | } 21 | // …… 22 | 23 | /* 24 | // 增加一个 context,在 break 前调用 cancel 函数,取消 goroutine。 25 | // gen 函数在接收到取消信号后,直接退出,系统回收资源。 26 | ctx, cancel := context.WithCancel(context.Background()) 27 | defer cancel() // 避免其他地方忘记 cancel,且重复调用不影响 28 | 29 | for n := range gen2(ctx) { 30 | fmt.Println(n) 31 | if n == 5 { 32 | cancel() 33 | break 34 | } 35 | } 36 | // …… 37 | */ 38 | } 39 | 40 | func gen() <-chan int { 41 | ch := make(chan int) 42 | go func() { 43 | var n int 44 | for { 45 | ch <- n 46 | n++ 47 | time.Sleep(time.Second) 48 | } 49 | }() 50 | return ch 51 | } 52 | 53 | func gen2(ctx context.Context) <-chan int { 54 | ch := make(chan int) 55 | go func() { 56 | var n int 57 | for { 58 | select { 59 | case <-ctx.Done(): 60 | return 61 | case ch <- n: 62 | n++ 63 | time.Sleep(time.Second) 64 | } 65 | } 66 | }() 67 | return ch 68 | } 69 | -------------------------------------------------------------------------------- /16-group/example_errgroup_return_result.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | /* 12 | 返回所有子任务的错误 13 | 14 | Group 只能返回子任务的第一个错误,后续的错误都会被丢弃。 15 | 但是,有时候我们需要知道每个任务的执行情况。怎么办呢? 16 | 这个时候,我们就可以用稍微有点曲折的方式去实现。 17 | 我们使用一个 result slice 保存子任务的执行结果, 18 | 这样,通过查询 result,就可以知道每一个子任务的结果了。 19 | */ 20 | func main() { 21 | var g errgroup.Group 22 | var result = make([]error, 3) 23 | 24 | // 启动第一个子任务,它执行成功 25 | g.Go(func() error { 26 | time.Sleep(5 * time.Second) 27 | fmt.Println("exec #1") 28 | result[0] = nil // 保存成功或者失败的结果 29 | return nil 30 | }) 31 | 32 | // 启动第二个子任务,它执行失败 33 | g.Go(func() error { 34 | time.Sleep(10 * time.Second) 35 | fmt.Println("exec #2") 36 | 37 | result[1] = errors.New("failed to exec #2") // 保存成功或者失败的结果 38 | return result[1] 39 | }) 40 | 41 | // 启动第三个子任务,它执行成功 42 | g.Go(func() error { 43 | time.Sleep(15 * time.Second) 44 | fmt.Println("exec #3") 45 | result[2] = nil // 保存成功或者失败的结果 46 | return nil 47 | }) 48 | 49 | if err := g.Wait(); err == nil { 50 | fmt.Printf("Successfully exec all. result: %v\n", result) 51 | } else { 52 | fmt.Printf("failed: %v\n", result) 53 | } 54 | } 55 | 56 | /* 57 | 就是使用 result 记录每个子任务成功或失败的结果。 58 | 其实,你不仅可以使用 result 记录 error 信息,还可以用它记录计算结果 59 | */ 60 | -------------------------------------------------------------------------------- /13-Semaphore/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "runtime" 8 | "time" 9 | 10 | "golang.org/x/sync/semaphore" 11 | ) 12 | 13 | var ( 14 | maxWorkers = runtime.GOMAXPROCS(0) // worker数量 15 | sema = semaphore.NewWeighted(int64(maxWorkers)) //信号量 16 | task = make([]int, maxWorkers*4) // 任务数,是worker的四倍 17 | ) 18 | 19 | func main() { 20 | 21 | ctx := context.Background() 22 | 23 | for i := range task { 24 | // 如果没有worker可用,会阻塞在这里,直到某个worker被释放 25 | if err := sema.Acquire(ctx, 1); err != nil { 26 | break 27 | } 28 | 29 | // 启动worker goroutine 30 | go func(i int) { 31 | defer sema.Release(1) 32 | time.Sleep(100 * time.Millisecond) // 模拟一个耗时操作 33 | task[i] = i + 1 34 | }(i) 35 | } 36 | 37 | // 请求所有的worker,这样能确保前面的worker都执行完 38 | if err := sema.Acquire(ctx, int64(maxWorkers)); err != nil { 39 | log.Printf("获取所有的worker失败: %v", err) 40 | } 41 | 42 | fmt.Println(task) 43 | } 44 | 45 | /*Analysis: 46 | 我们创建和 CPU 核数一样多的 Worker,让它们去处理一个 4 倍数量的整数 slice。 47 | 每个 Worker 一次只能处理一个整数,处理完之后,才能处理下一个。 48 | 49 | 在这段代码中,main goroutine 相当于一个 dispatcher,负责任务的分发。 50 | 它先请求信号量,如果获取成功,就会启动一个 goroutine 去处理计算,然后, 51 | 这个 goroutine 会释放这个信号量(信号量的获取是在 main goroutine, 52 | 信号量的释放是在 worker goroutine 中),如果获取不成功,就等到有信号 53 | 量可以使用的时候,再去获取。 54 | */ 55 | -------------------------------------------------------------------------------- /08-Map/source-orcaman.go: -------------------------------------------------------------------------------- 1 | // 知名分片并发 map:https://github.com/orcaman/concurrent-map 2 | 3 | // 它默认采用 32 个分片,GetShard 是一个关键的方法,能够根据 key 计算出分片索引。 4 | var SHARD_COUNT = 32 5 | 6 | // 分成SHARD_COUNT个分片的map 7 | type ConcurrentMap []*ConcurrentMapShared 8 | 9 | // 通过RWMutex保护的线程安全的分片,包含一个map 10 | type ConcurrentMapShared struct { 11 | items map[string]interface{} 12 | sync.RWMutex // Read Write mutex, guards access to internal map. 13 | } 14 | 15 | // 创建并发map 16 | func New() ConcurrentMap { 17 | m := make(ConcurrentMap, SHARD_COUNT) 18 | for i := 0; i < SHARD_COUNT; i++ { 19 | m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} 20 | } 21 | return m 22 | } 23 | 24 | // 根据key计算分片索引 25 | func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared { 26 | return m[uint(fnv32(key))%uint(SHARD_COUNT)] 27 | } 28 | 29 | //---------------------------------------------------------------- 30 | // 增加或者查询的时候,首先根据分片索引得到分片对象,然后对分片对象加锁进行操作: 31 | 32 | func (m ConcurrentMap) Set(key string, value interface{}) { 33 | // 根据key计算出对应的分片 34 | shard := m.GetShard(key) 35 | shard.Lock() //对这个分片加锁,执行业务操作 36 | shard.items[key] = value 37 | shard.Unlock() 38 | } 39 | 40 | func (m ConcurrentMap) Get(key string) (interface{}, bool) { 41 | // 根据key计算出对应的分片 42 | shard := m.GetShard(key) 43 | shard.RLock() 44 | // 从这个分片读取key的值 45 | val, ok := shard.items[key] 46 | shard.RUnlock() 47 | return val, ok 48 | } 49 | 50 | // oncurrentMap 还提供了很多其他的方法。 51 | // 这些方法都是通过计算相应的分片实现的,目的是保证把锁的粒度限制在分片上。 -------------------------------------------------------------------------------- /03-Mutex/1-tryLock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | "unsafe" 10 | ) 11 | 12 | func main() { 13 | // test 14 | try() 15 | } 16 | 17 | // 复制Mutex定义的常量 18 | const ( 19 | mutexLocked = 1 << iota // 加锁标识位置 20 | mutexWoken // 唤醒标识位置 21 | mutexStarving // 锁饥饿标识位置 22 | mutexWaiterShift = iota // 标识waiter的起始bit位置 23 | ) 24 | 25 | // 扩展一个Mutex结构 26 | type Mutex struct { 27 | sync.Mutex 28 | } 29 | 30 | // 尝试获取锁 31 | func (m *Mutex) TryLock() bool { 32 | // 如果能成功抢到锁 33 | if atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), 0, mutexLocked) { 34 | return true 35 | } 36 | 37 | // 如果处于唤醒、加锁或者饥饿状态,这次请求就不参与竞争了,返回false 38 | old := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex))) 39 | if old&(mutexLocked|mutexStarving|mutexWoken) != 0 { 40 | return false 41 | } 42 | 43 | // 尝试在竞争的状态下请求锁 44 | new := old | mutexLocked 45 | return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), old, new) 46 | } 47 | 48 | func try() { 49 | var mu Mutex 50 | go func() { // 启动一个goroutine持有一段时间的锁 51 | mu.Lock() 52 | time.Sleep(time.Duration(rand.Intn(2)) * time.Second) 53 | mu.Unlock() 54 | }() 55 | 56 | time.Sleep(time.Second) 57 | 58 | ok := mu.TryLock() // 尝试获取到锁 59 | if ok { // 获取成功 60 | fmt.Println("got the lock") 61 | // do something 62 | mu.Unlock() 63 | return 64 | } 65 | 66 | // 没有获取到 67 | fmt.Println("can't get the lock") 68 | } 69 | -------------------------------------------------------------------------------- /12-channel/source-create.go: -------------------------------------------------------------------------------- 1 | 2 | // 使用 make 创建一个能收能发的通道: 3 | 4 | // 无缓冲通道 5 | ch1 := make(chan int) 6 | // 有缓冲通道 7 | ch2 := make(chan int, 10) 8 | 9 | 10 | // 从函数原型来看,创建的 chan 是一个指针。 11 | // 所以我们能在函数间直接传递 channel,而不用传递 channel 的指针。 12 | 13 | const hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1)) 14 | 15 | func makechan(t *chantype, size int64) *hchan { 16 | elem := t.elem 17 | 18 | // 省略了检查 channel size,align 的代码 19 | // …… 20 | 21 | var c *hchan 22 | // 如果元素类型不含指针 或者 size 大小为 0(无缓冲类型) 23 | // 只进行一次内存分配 24 | if elem.kind&kindNoPointers != 0 || size == 0 { 25 | // 如果 hchan 结构体中不含指针,GC 就不会扫描 chan 中的元素 26 | // 只分配 "hchan 结构体大小 + 元素大小*个数" 的内存 27 | c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true)) 28 | // 如果是缓冲型 channel 且元素大小不等于 0(大小等于 0的元素类型:struct{}) 29 | if size > 0 && elem.size != 0 { 30 | c.buf = add(unsafe.Pointer(c), hchanSize) 31 | } else { 32 | // race detector uses this location for synchronization 33 | // Also prevents us from pointing beyond the allocation (see issue 9401). 34 | // 1. 非缓冲型的,buf 没用,直接指向 chan 起始地址处 35 | // 2. 缓冲型的,能进入到这里,说明元素无指针且元素类型为 struct{},也无影响 36 | // 因为只会用到接收和发送游标,不会真正拷贝东西到 c.buf 处(这会覆盖 chan的内容) 37 | c.buf = unsafe.Pointer(c) 38 | } 39 | } else { 40 | // 进行两次内存分配操作 41 | c = new(hchan) 42 | c.buf = newarray(elem, int(size)) 43 | } 44 | c.elemsize = uint16(elem.size) 45 | c.elemtype = elem 46 | // 循环数组长度 47 | c.dataqsiz = uint(size) 48 | 49 | // 返回 hchan 指针 50 | return c 51 | } -------------------------------------------------------------------------------- /14-SingleFlight/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "sync" 9 | "time" 10 | 11 | "golang.org/x/sync/singleflight" 12 | ) 13 | 14 | var ( 15 | g singleflight.Group 16 | ErrCacheMiss = errors.New("cache miss") 17 | ) 18 | 19 | func main() { 20 | var wg sync.WaitGroup 21 | wg.Add(10) 22 | 23 | // 模拟10个并发 24 | for i := 0; i < 10; i++ { 25 | go func() { 26 | defer wg.Done() 27 | data, err := load("key") 28 | if err != nil { 29 | log.Print(err) 30 | return 31 | } 32 | log.Println(data) 33 | }() 34 | } 35 | wg.Wait() 36 | } 37 | 38 | // 获取数据 39 | func load(key string) (string, error) { 40 | data, err := loadFromCache(key) 41 | if err != nil && err == ErrCacheMiss { 42 | // 利用 singleflight 来归并请求 43 | v, err, _ := g.Do(key, func() (interface{}, error) { 44 | data, err := loadFromDB(key) 45 | if err != nil { 46 | return nil, err 47 | } 48 | setCache(key, data) 49 | return data, nil 50 | }) 51 | if err != nil { 52 | log.Println(err) 53 | return "", err 54 | } 55 | data = v.(string) 56 | } 57 | return data, nil 58 | } 59 | 60 | // getDataFromCache 模拟从cache中获取值 cache miss 61 | func loadFromCache(key string) (string, error) { 62 | return "", ErrCacheMiss 63 | } 64 | 65 | // setCache 写入缓存 66 | func setCache(key, data string) {} 67 | 68 | // getDataFromDB 模拟从数据库中获取值 69 | func loadFromDB(key string) (string, error) { 70 | fmt.Println("query db") 71 | unix := strconv.Itoa(int(time.Now().UnixNano())) 72 | return unix, nil 73 | } 74 | -------------------------------------------------------------------------------- /03-Mutex/2-stateMetrics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | "unsafe" 9 | ) 10 | 11 | /* Mutex 的数据结构 12 | type Mutex struct { 13 | state int32 14 | sema uint32 15 | } 16 | */ 17 | 18 | func main() { 19 | count() 20 | } 21 | 22 | const ( 23 | mutexLocked = 1 << iota // mutex is locked 24 | mutexWoken 25 | mutexStarving 26 | mutexWaiterShift = iota 27 | ) 28 | 29 | type Mutex struct { 30 | sync.Mutex 31 | } 32 | 33 | func (m *Mutex) Count() int { 34 | // 获取state字段的值 35 | v := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex))) 36 | v = v>>mutexWaiterShift + (v & mutexLocked) 37 | return int(v) 38 | } 39 | 40 | // 锁是否被持有 41 | func (m *Mutex) IsLocked() bool { 42 | state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex))) 43 | return state&mutexLocked == mutexLocked 44 | } 45 | 46 | // 是否有等待者被唤醒 47 | func (m *Mutex) IsWoken() bool { 48 | state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex))) 49 | return state&mutexWoken == mutexWoken 50 | } 51 | 52 | // 锁是否处于饥饿状态 53 | func (m *Mutex) IsStarving() bool { 54 | state := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.Mutex))) 55 | return state&mutexStarving == mutexStarving 56 | } 57 | 58 | func count() { 59 | var mu Mutex 60 | for i := 0; i < 1000; i++ { // 启动1000个goroutine 61 | go func() { 62 | mu.Lock() 63 | time.Sleep(time.Second) 64 | mu.Unlock() 65 | }() 66 | } 67 | 68 | time.Sleep(time.Second) 69 | // 输出锁的信息 70 | fmt.Printf("waitings: %d, isLocked: %t, woken: %t, starving: %t\n", mu.Count(), mu.IsLocked(), mu.IsWoken(), mu.IsStarving()) 71 | } 72 | -------------------------------------------------------------------------------- /16-group/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-pkgz/syncs v1.2.0 h1:aiizQFILlMZ4KtRNaYLcDffRbUQZH9fclsgr5KybWyY= 4 | github.com/go-pkgz/syncs v1.2.0/go.mod h1:fjThZdM2FkC/oSeiqBTOZOtHpbrCh4HuHbipB5qZJJM= 5 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 6 | github.com/mdlayher/schedgroup v1.0.0 h1:MJS37Rkver2jHaRV5WE5xszN56xZt5yQlqNjtBI7Hsc= 7 | github.com/mdlayher/schedgroup v1.0.0/go.mod h1:1i5jZ9Z+r9TUTWtmaXbeOX/4ek5HXqgAdREZJGP5Ry4= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 12 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | github.com/vardius/gollback v1.1.1 h1:sHe+Bnmp87h3w6kBnXY8ATMuWeF4ZcBhAmALx6GQM/I= 14 | github.com/vardius/gollback v1.1.1/go.mod h1:MeIopn8KzLR11VlCuds97h/g0FwgnjloNolBsyTl0OM= 15 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 16 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /04-RWMutex/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync/atomic" 4 | 5 | func main() { 6 | // 源码地址: src/sync/rwmutex.go 7 | } 8 | 9 | type Mutex struct { 10 | state int32 11 | sema uint32 12 | } 13 | 14 | type RWMutex struct { 15 | w Mutex // 互斥锁解决多个writer的竞争 16 | writerSem uint32 // writer信号量 17 | readerSem uint32 // reader信号量 18 | readerCount int32 // reader的数量 19 | readerWait int32 // writer等待完成的reader的数量 20 | } 21 | 22 | const rwmutexMaxReaders = 1 << 30 // 定义了最大的 reader 数量 23 | 24 | // RLock/RUnlock 的实现,移除了 race 等无关紧要的代码 25 | func (rw *RWMutex) RLock() { 26 | if atomic.AddInt32(&rw.readerCount, 1) < 0 { 27 | // rw.readerCount是负值的时候,意味着此时有writer等待请求锁,因为writer优先级高,所以把后来的reader阻塞休眠 28 | runtime_SemacquireMutex(&rw.readerSem, false, 0) 29 | } 30 | } 31 | func (rw *RWMutex) RUnlock() { 32 | if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { 33 | rw.rUnlockSlow(r) // 有等待的writer 34 | } 35 | } 36 | func (rw *RWMutex) rUnlockSlow(r int32) { 37 | if atomic.AddInt32(&rw.readerWait, -1) == 0 { 38 | // 最后一个reader了,writer终于有机会获得锁了 39 | runtime_Semrelease(&rw.writerSem, false, 1) 40 | } 41 | } 42 | 43 | // ---------------------------------------------------------------- 44 | func (rw *RWMutex) Lock() { 45 | // 首先解决其他writer竞争问题 46 | rw.w.Lock() 47 | // 反转readerCount,告诉reader有writer竞争锁 48 | r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders 49 | // 如果当前有reader持有锁,那么需要等待 50 | if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { 51 | runtime_SemacquireMutex(&rw.writerSem, false, 0) 52 | } 53 | } 54 | 55 | func (rw *RWMutex) Unlock() { 56 | // 告诉reader没有活跃的writer了 57 | r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) 58 | 59 | // 唤醒阻塞的reader们 60 | for i := 0; i < int(r); i++ { 61 | runtime_Semrelease(&rw.readerSem, false, 0) 62 | } 63 | // 释放内部的互斥锁 64 | rw.w.Unlock() 65 | } 66 | -------------------------------------------------------------------------------- /16-group/pipeline_source.go: -------------------------------------------------------------------------------- 1 | 2 | package main 3 | 4 | import ( 5 | 6 | "golang.org/x/sync/errgroup" 7 | ) 8 | /* 任务执行流水线 Pipeline 9 | Go 官方文档中还提供了一个 pipeline 的例子。 10 | 这个例子是说,由一个子任务遍历文件夹下的文件, 11 | 然后把遍历出的文件交给 20 个 goroutine, 12 | 让这些 goroutine 并行计算文件的 md5。 13 | 14 | 我来把这个例子简化一下 15 | */ 16 | // 一个多阶段的pipeline.使用有限的goroutine计算每个文件的md5值. 17 | func main() { 18 | m, err := MD5All(context.Background(), ".") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | for k, sum := range m { 24 | fmt.Printf("%s:\t%x\n", k, sum) 25 | } 26 | } 27 | 28 | type result struct { 29 | path string 30 | sum [md5.Size]byte 31 | } 32 | 33 | // 遍历根目录下所有的文件和子文件夹,计算它们的md5的值. 34 | func MD5All(ctx context.Context, root string) (map[string][md5.Size]byte, error) { 35 | g, ctx := errgroup.WithContext(ctx) 36 | paths := make(chan string) // 文件路径channel 37 | 38 | g.Go(func() error { 39 | defer close(paths) // 遍历完关闭paths chan 40 | return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 41 | ...... //将文件路径放入到paths 42 | return nil 43 | }) 44 | }) 45 | 46 | // 启动20个goroutine执行计算md5的任务,计算的文件由上一阶段的文件遍历子任务生成. 47 | c := make(chan result) 48 | const numDigesters = 20 49 | for i := 0; i < numDigesters; i++ { 50 | g.Go(func() error { 51 | for path := range paths { // 遍历直到paths chan被关闭 52 | ...... // 计算path的md5值,放入到c中 53 | } 54 | return nil 55 | }) 56 | } 57 | go func() { 58 | g.Wait() // 20个goroutine以及遍历文件的goroutine都执行完 59 | close(c) // 关闭收集结果的chan 60 | }() 61 | 62 | 63 | m := make(map[string][md5.Size]byte) 64 | for r := range c { // 将md5结果从chan中读取到map中,直到c被关闭才退出 65 | m[r.path] = r.sum 66 | } 67 | 68 | // 再次调用Wait,依然可以得到group的error信息 69 | if err := g.Wait(); err != nil { 70 | return nil, err 71 | } 72 | return m, nil 73 | } -------------------------------------------------------------------------------- /05-WaitGroup/question-2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // 常见问题二:不期望的 Add 时机 10 | func main() { 11 | var wg sync.WaitGroup 12 | go dosomething(100, &wg) // 启动第一个goroutine 13 | go dosomething(110, &wg) // 启动第二个goroutine 14 | go dosomething(120, &wg) // 启动第三个goroutine 15 | go dosomething(130, &wg) // 启动第四个goroutine 16 | 17 | wg.Wait() // 主goroutine等待完成 18 | fmt.Println("Done") 19 | } 20 | 21 | func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { 22 | duration := millisecs * time.Millisecond 23 | time.Sleep(duration) // 故意sleep一段时间 24 | 25 | wg.Add(1) 26 | fmt.Println("后台执行, duration:", duration) 27 | wg.Done() 28 | } 29 | 30 | // 解决方案 31 | /* 预先设置计数值: 32 | 33 | func main() { 34 | var wg sync.WaitGroup 35 | wg.Add(4) // 预先设定WaitGroup的计数值 36 | 37 | go dosomething(100, &wg) // 启动第一个goroutine 38 | go dosomething(110, &wg) // 启动第二个goroutine 39 | go dosomething(120, &wg) // 启动第三个goroutine 40 | go dosomething(130, &wg) // 启动第四个goroutine 41 | 42 | wg.Wait() // 主goroutine等待 43 | fmt.Println("Done") 44 | } 45 | 46 | func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { 47 | duration := millisecs * time.Millisecond 48 | time.Sleep(duration) 49 | 50 | fmt.Println("后台执行, duration:", duration) 51 | wg.Done() 52 | } 53 | 54 | */ 55 | 56 | /* 在启动子 goroutine 之前才调用 Add: 57 | 58 | func main() { 59 | var wg sync.WaitGroup 60 | 61 | dosomething(100, &wg) // 调用方法,把计数值加1,并启动任务goroutine 62 | dosomething(110, &wg) // 调用方法,把计数值加1,并启动任务goroutine 63 | dosomething(120, &wg) // 调用方法,把计数值加1,并启动任务goroutine 64 | dosomething(130, &wg) // 调用方法,把计数值加1,并启动任务goroutine 65 | 66 | wg.Wait() // 主goroutine等待,代码逻辑保证了四次Add(1)都已经执行完了 67 | fmt.Println("Done") 68 | } 69 | 70 | func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { 71 | wg.Add(1) // 计数值加1,再启动goroutine 72 | 73 | go func() { 74 | duration := millisecs * time.Millisecond 75 | time.Sleep(duration) 76 | fmt.Println("后台执行, duration:", duration) 77 | wg.Done() 78 | }() 79 | } 80 | 81 | */ 82 | -------------------------------------------------------------------------------- /11-atomic/source.go: -------------------------------------------------------------------------------- 1 | // source: src/sync/atomic/doc.go 2 | 3 | // Add 4 | // atomic 的 Add 是针对 int 和 uint 进行原子加值的: 5 | func AddInt32(addr *int32, delta int32) (new int32) 6 | func AddUint32(addr *uint32, delta uint32) (new uint32) 7 | func AddInt64(addr *int64, delta int64) (new int64) 8 | func AddUint64(addr *uint64, delta uint64) (new uint64) 9 | func AddUintptr(addr *uintptr, delta uintptr) (new uintptr) 10 | 11 | 12 | // CompareAndSwap 13 | // 比较并交换方法实现了类似乐观锁的功能,只有原来的值和传入的 old 值一样,才会去修改: 14 | // CompareAndSwap 有可能产生 ABA 现象发生。也就是原来的值是 A,后面被修改 B, 15 | // 再后面修改为 A。在这种情况下也符合了 CompareAndSwap 规则,即使中途有被改动过。 16 | func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) 17 | func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) 18 | func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) 19 | func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) 20 | func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) 21 | func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) 22 | 23 | // Load 24 | // Load 方法是为了防止在读取过程中,有其他协程发起修改动作,影响了读取结果,常用于配置项的整个读取。 25 | func LoadInt32(addr *int32) (val int32) 26 | func LoadInt64(addr *int64) (val int64) 27 | func LoadUint32(addr *uint32) (val uint32) 28 | func LoadUint64(addr *uint64) (val uint64) 29 | func LoadUintptr(addr *uintptr) (val uintptr) 30 | func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) 31 | 32 | // Store 33 | // 有原子读取,就有原子修改值,前面提到过的 Add 只适用于 int、uint 类型的增减, 34 | // 并没有其他类型的修改,而 Sotre 方法通过 unsafe.Pointer 指针原子修改,来达到了对其他类型的修改。 35 | func StoreInt32(addr *int32, val int32) 36 | func StoreInt64(addr *int64, val int64) 37 | func StoreUint32(addr *uint32, val uint32) 38 | func StoreUint64(addr *uint64, val uint64) 39 | func StoreUintptr(addr *uintptr, val uintptr) 40 | func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer) 41 | 42 | 43 | // Swap 44 | // Swap 方法实现了对值的原子交换,不仅 int,uint 可以交换,指针也可以。 45 | 46 | func SwapInt32(addr *int32, new int32) (old int32) 47 | func SwapInt64(addr *int64, new int64) (old int64) 48 | func SwapUint32(addr *uint32, new uint32) (old uint32) 49 | func SwapUint64(addr *uint64, new uint64) (old uint64) 50 | func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) 51 | func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer) 52 | -------------------------------------------------------------------------------- /12-channel/7-Nsender-Mreceiver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | rand.Seed(time.Now().UnixNano()) 12 | 13 | const Max = 100 14 | const NumReceivers = 10 15 | const NumSenders = 1000 16 | 17 | dataCh := make(chan int, 100) 18 | stopCh := make(chan struct{}) 19 | 20 | // It must be a buffered channel. 21 | // toStop := make(chan string, 1) 22 | toStop := make(chan string, NumReceivers+NumSenders) 23 | 24 | var stoppedBy string 25 | 26 | // moderator 27 | go func() { 28 | stoppedBy = <-toStop 29 | fmt.Printf("stoppedBy: %s\n", stoppedBy) 30 | close(stopCh) 31 | }() 32 | 33 | // senders 34 | for i := 0; i < NumSenders; i++ { 35 | go func(id string) { 36 | for { 37 | // value := rand.Intn(Max) 38 | // if value == 0 { 39 | // select { 40 | // case toStop <- "sender#" + id: 41 | // default: 42 | // } 43 | // return 44 | // } 45 | 46 | value := rand.Intn(Max) 47 | if value == 0 { 48 | toStop <- "sender#" + id 49 | return 50 | } 51 | 52 | select { 53 | case <-stopCh: 54 | return 55 | case dataCh <- value: 56 | } 57 | } 58 | }(strconv.Itoa(i)) 59 | } 60 | 61 | // receivers 62 | for i := 0; i < NumReceivers; i++ { 63 | go func(id string) { 64 | for { 65 | select { 66 | case <-stopCh: 67 | return 68 | case value := <-dataCh: 69 | // if value == Max-1 { 70 | // select { 71 | // case toStop <- "receiver#" + id: 72 | // default: 73 | // } 74 | // return 75 | // } 76 | if value == Max-1 { 77 | toStop <- "receiver#" + id 78 | return 79 | } 80 | 81 | fmt.Println(value) 82 | } 83 | } 84 | }(strconv.Itoa(i)) 85 | } 86 | 87 | select { 88 | case <-time.After(time.Second * 10): 89 | } 90 | 91 | } 92 | 93 | /*Analysis: 94 | 代码里 toStop 就是中间人的角色,使用它来接收 senders 和 receivers 发送过来的关闭 dataCh 请求。 95 | 这里将 toStop 声明成了一个 缓冲型的 channel。 96 | 假设 toStop 声明的是一个非缓冲型的 channel,那么第一个发送的关闭 dataCh 请求可能会丢失。 97 | 因为无论是 sender 还是 receiver 都是通过 select 语句来发送请求, 98 | 如果中间人所在的 goroutine 没有准备好,那 select 语句就不会选中, 99 | 直接走 default 选项,什么也不做。这样,第一个关闭 dataCh 的请求就会丢失。 100 | 101 | 把 toStop 的容量声明成 Num(senders) + Num(receivers) 102 | 直接向 toStop 发送请求,因为 toStop 容量足够大,所以不用担心阻塞, 103 | 自然也就不用 select 语句再加一个 default case 来避免阻塞。 104 | */ 105 | -------------------------------------------------------------------------------- /12-channel/source-close.go: -------------------------------------------------------------------------------- 1 | // 关闭某个 channel,会执行函数 closechan: 2 | 3 | func closechan(c *hchan) { 4 | // 关闭一个 nil channel,panic 5 | if c == nil { 6 | panic(plainError("close of nil channel")) 7 | } 8 | 9 | // 上锁 10 | lock(&c.lock) 11 | // 如果 channel 已经关闭 12 | if c.closed != 0 { 13 | unlock(&c.lock) 14 | // panic 15 | panic(plainError("close of closed channel")) 16 | } 17 | 18 | // ………… 19 | 20 | // 修改关闭状态 21 | c.closed = 1 22 | 23 | var glist *g 24 | 25 | // 将 channel 所有等待接收队列的里 sudog 释放 26 | for { 27 | // 从接收队列里出队一个 sudog 28 | sg := c.recvq.dequeue() 29 | // 出队完毕,跳出循环 30 | if sg == nil { 31 | break 32 | } 33 | 34 | // 如果 elem 不为空,说明此 receiver 未忽略接收数据 35 | // 给它赋一个相应类型的零值 36 | if sg.elem != nil { 37 | typedmemclr(c.elemtype, sg.elem) 38 | sg.elem = nil 39 | } 40 | if sg.releasetime != 0 { 41 | sg.releasetime = cputicks() 42 | } 43 | // 取出 goroutine 44 | gp := sg.g 45 | gp.param = nil 46 | if raceenabled { 47 | raceacquireg(gp, unsafe.Pointer(c)) 48 | } 49 | // 相连,形成链表 50 | gp.schedlink.set(glist) 51 | glist = gp 52 | } 53 | 54 | // 将 channel 等待发送队列里的 sudog 释放 55 | // 如果存在,这些 goroutine 将会 panic 56 | for { 57 | // 从发送队列里出队一个 sudog 58 | sg := c.sendq.dequeue() 59 | if sg == nil { 60 | break 61 | } 62 | 63 | // 发送者会 panic 64 | sg.elem = nil 65 | if sg.releasetime != 0 { 66 | sg.releasetime = cputicks() 67 | } 68 | gp := sg.g 69 | gp.param = nil 70 | if raceenabled { 71 | raceacquireg(gp, unsafe.Pointer(c)) 72 | } 73 | // 形成链表 74 | gp.schedlink.set(glist) 75 | glist = gp 76 | } 77 | // 解锁 78 | unlock(&c.lock) 79 | 80 | // Ready all Gs now that we've dropped the channel lock. 81 | // 遍历链表 82 | for glist != nil { 83 | // 取最后一个 84 | gp := glist 85 | // 向前走一步,下一个唤醒的 g 86 | glist = glist.schedlink.ptr() 87 | gp.schedlink = 0 88 | // 唤醒相应 goroutine 89 | goready(gp, 3) 90 | } 91 | } 92 | 93 | /* 94 | 1.close 逻辑比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。 95 | 关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值。对于等待发送者,会直接 panic。 96 | 所以,在不了解 channel 还有没有接收者的情况下,不能贸然关闭 channel。 97 | 98 | 2.close 函数先上一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 99 | 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。 100 | 唤醒之后,sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码, 101 | 很不幸,检测到 channel 已经关闭了,panic。 102 | receiver 则比较幸运,进行一些扫尾工作后,返回。 103 | 这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。 104 | 如果 channel 关闭,received 为 false,否则为 true。这我们分析的这种情况下,received 返回 false。 105 | */ -------------------------------------------------------------------------------- /14-SingleFlight/example2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | 8 | "golang.org/x/sync/singleflight" 9 | ) 10 | 11 | var ( 12 | sfKey1 = "key1" 13 | wg *sync.WaitGroup 14 | sf singleflight.Group 15 | nums = 10 16 | ) 17 | 18 | func main() { 19 | getValueService("key") 20 | } 21 | 22 | func getValueService(key string) { //service 23 | var val string 24 | wg = &sync.WaitGroup{} 25 | wg.Add(nums) 26 | for idx := 0; idx < nums; idx++ { // 模拟多协程同时请求 27 | go func(idx int) { // 注意for的一个小坑 28 | defer wg.Done() 29 | value, _ := getValueBySingleflight(idx, key) //简化代码,不处理error 30 | log.Printf("request %v get value: %v", idx, value) 31 | val = value 32 | }(idx) 33 | } 34 | wg.Wait() 35 | log.Println("val: ", val) 36 | return 37 | } 38 | 39 | // getValueBySingleflight 使用singleflight取cacheKey对应的value值 40 | func getValueBySingleflight(idx int, cacheKey string) (string, error) { 41 | log.Printf("idx %v into-cache...", idx) 42 | // 调用singleflight的Do()方法 43 | value, _, _ := sf.Do(cacheKey, func() (ret interface{}, err error) { 44 | log.Printf("idx %v is-setting-cache", idx) 45 | // 休眠0.1s以捕获并发的相同请求 46 | time.Sleep(100 * time.Millisecond) 47 | log.Printf("idx %v set-cache-success!", idx) 48 | return "myValue", nil 49 | }) 50 | return value.(string), nil 51 | } 52 | 53 | /* 54 | out: 55 | go run example2.go 56 | 2023/03/19 16:31:31 idx 1 into-cache... 57 | 2023/03/19 16:31:31 idx 1 is-setting-cache 58 | 2023/03/19 16:31:31 idx 2 into-cache... 59 | 2023/03/19 16:31:31 idx 9 into-cache... 60 | 2023/03/19 16:31:31 idx 8 into-cache... 61 | 2023/03/19 16:31:31 idx 7 into-cache... 62 | 2023/03/19 16:31:31 idx 5 into-cache... 63 | 2023/03/19 16:31:31 idx 0 into-cache... 64 | 2023/03/19 16:31:31 idx 6 into-cache... 65 | 2023/03/19 16:31:31 idx 3 into-cache... 66 | 2023/03/19 16:31:31 idx 4 into-cache... 67 | 2023/03/19 16:31:32 idx 1 set-cache-success! 68 | 2023/03/19 16:31:32 request 1 get value: myValue 69 | 2023/03/19 16:31:32 request 7 get value: myValue 70 | 2023/03/19 16:31:32 request 4 get value: myValue 71 | 2023/03/19 16:31:32 request 8 get value: myValue 72 | 2023/03/19 16:31:32 request 6 get value: myValue 73 | 2023/03/19 16:31:32 request 5 get value: myValue 74 | 2023/03/19 16:31:32 request 0 get value: myValue 75 | 2023/03/19 16:31:32 request 9 get value: myValue 76 | 2023/03/19 16:31:32 request 2 get value: myValue 77 | 2023/03/19 16:31:32 request 3 get value: myValue 78 | 2023/03/19 16:31:32 val: myValue 79 | 80 | 由结果可以看到,索引=1的协程第一个进入了Do()方法,其他协程则阻塞住,等到idx=1的协程拿到执行结果后,协程以乱序的形式返回执行结果。 81 | 相同key的情况下,singleflight将我们的多个请求合并成1个请求。由1个请求去执行对共享资源的操作。 82 | */ 83 | -------------------------------------------------------------------------------- /16-group/pipeline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/md5" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | 12 | "golang.org/x/sync/errgroup" 13 | ) 14 | 15 | // Pipeline demonstrates the use of a Group to implement a multi-stage 16 | // pipeline: a version of the MD5All function with bounded parallelism from 17 | // https://blog.golang.org/pipelines. 18 | func main() { 19 | m, err := MD5All(context.Background(), ".") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | for k, sum := range m { 25 | fmt.Printf("%s:\t%x\n", k, sum) 26 | } 27 | } 28 | 29 | type result struct { 30 | path string 31 | sum [md5.Size]byte 32 | } 33 | 34 | // MD5All reads all the files in the file tree rooted at root and returns a map 35 | // from file path to the MD5 sum of the file's contents. If the directory walk 36 | // fails or any read operation fails, MD5All returns an error. 37 | func MD5All(ctx context.Context, root string) (map[string][md5.Size]byte, error) { 38 | // ctx is canceled when g.Wait() returns. When this version of MD5All returns 39 | // - even in case of error! - we know that all of the goroutines have finished 40 | // and the memory they were using can be garbage-collected. 41 | g, ctx := errgroup.WithContext(ctx) 42 | paths := make(chan string) 43 | 44 | g.Go(func() error { 45 | defer close(paths) 46 | return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 47 | if err != nil { 48 | return err 49 | } 50 | if !info.Mode().IsRegular() { 51 | return nil 52 | } 53 | select { 54 | case paths <- path: 55 | case <-ctx.Done(): 56 | return ctx.Err() 57 | } 58 | return nil 59 | }) 60 | }) 61 | 62 | // Start a fixed number of goroutines to read and digest files. 63 | c := make(chan result) 64 | const numDigesters = 20 65 | for i := 0; i < numDigesters; i++ { 66 | g.Go(func() error { 67 | for path := range paths { 68 | data, err := ioutil.ReadFile(path) 69 | if err != nil { 70 | return err 71 | } 72 | select { 73 | case c <- result{path, md5.Sum(data)}: 74 | case <-ctx.Done(): 75 | return ctx.Err() 76 | } 77 | } 78 | return nil 79 | }) 80 | } 81 | go func() { 82 | g.Wait() 83 | close(c) 84 | }() 85 | 86 | m := make(map[string][md5.Size]byte) 87 | for r := range c { 88 | m[r.path] = r.sum 89 | } 90 | // Check whether any of the goroutines failed. Since g is accumulating the 91 | // errors, we don't need to send them (or check for them) in the individual 92 | // results sent on the channel. 93 | if err := g.Wait(); err != nil { 94 | return nil, err 95 | } 96 | return m, nil 97 | } 98 | -------------------------------------------------------------------------------- /05-WaitGroup/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | func main() { 9 | // 源码地址: src/sync/waitgroup.go 10 | } 11 | 12 | type WaitGroup struct { 13 | noCopy noCopy 14 | 15 | // 64位数值:高32位是计数器,低32位是服务器计数。 16 | // 64位原子操作需要64位对齐,但32位编译器只保证64位字段是32位对齐的。 17 | // 由于这个原因,在32位架构上我们需要在state()中检查state1是否对齐, 18 | // 并在需要时动态地 "交换 "字段的顺序。 19 | state1 uint64 20 | state2 uint32 21 | } 22 | 23 | /* 24 | noCopy 字段意义: 25 | 26 | 通过给 WaitGroup 添加一个 noCopy 字段,我们就可以为 WaitGroup 实现 Locker 接口, 27 | 这样 vet 工具就可以做复制检查了。而且因为 noCopy 字段是未输出类型, 28 | 所以 WaitGroup 不会暴露 Lock/Unlock 方法。 29 | */ 30 | type noCopy struct{} 31 | 32 | // Lock is a no-op used by -copylocks checker from `go vet`. 33 | func (*noCopy) Lock() {} 34 | func (*noCopy) Unlock() {} 35 | 36 | // state返回存储在wg.state*中的状态和sema字段的指针。 37 | func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { 38 | if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 { 39 | // state1是64位对齐的:没什么可做的。 40 | return &wg.state1, &wg.state2 41 | } else { 42 | // state1是32位对齐的,但不是64位对齐的:这意味着 (&state1)+4是64位对齐的。 43 | state := (*[3]uint32)(unsafe.Pointer(&wg.state1)) 44 | return (*uint64)(unsafe.Pointer(&state[1])), &state[0] 45 | } 46 | } 47 | 48 | /* 49 | Add 方法的逻辑: 50 | 51 | Add 方法主要操作的是 state 的计数部分。 52 | 你可以为计数值增加一个 delta 值,内部通过原子操作把这个值加到计数值上。 53 | 需要注意的是,这个 delta 也可以是个负数,相当于为计数值减去一个值, 54 | Done 方法内部其实就是通过 Add(-1) 实现的。 55 | */ 56 | func (wg *WaitGroup) Add(delta int) { 57 | statep, semap := wg.state() 58 | // 高32bit是计数值v,所以把delta左移32,增加到计数上 59 | state := atomic.AddUint64(statep, uint64(delta)<<32) 60 | v := int32(state >> 32) // 当前计数值 61 | w := uint32(state) // waiter count 62 | 63 | if v > 0 || w == 0 { 64 | return 65 | } 66 | 67 | // 如果计数值v为0并且waiter的数量w不为0,那么state的值就是waiter的数量 68 | // 将waiter的数量设置为0,因为计数值v也是0,所以它们俩的组合*statep直接设置为0即可。此时需要并唤醒所有的waiter 69 | *statep = 0 70 | for ; w != 0; w-- { 71 | runtime_Semrelease(semap, false, 0) 72 | } 73 | } 74 | 75 | // Done方法实际就是计数器减1 76 | func (wg *WaitGroup) Done() { 77 | wg.Add(-1) 78 | } 79 | 80 | /* 81 | Wait 方法的实现逻辑是: 82 | 83 | 不断检查 state 的值。如果其中的计数值变为了 0,那么说明所有的任务已完成, 84 | 调用者不必再等待,直接返回。如果计数值大于 0,说明此时还有任务没完成, 85 | 那么调用者就变成了等待者,需要加入 waiter 队列,并且阻塞住自己。 86 | */ 87 | func (wg *WaitGroup) Wait() { 88 | statep, semap := wg.state() 89 | 90 | for { 91 | state := atomic.LoadUint64(statep) 92 | v := int32(state >> 32) // 当前计数值 93 | w := uint32(state) // waiter的数量 94 | if v == 0 { 95 | // 如果计数值为0, 调用这个方法的goroutine不必再等待,继续执行它后面的逻辑即可 96 | return 97 | } 98 | // 否则把waiter数量加1。期间可能有并发调用Wait的情况,所以最外层使用了一个for循环 99 | if atomic.CompareAndSwapUint64(statep, state, state+1) { 100 | // 阻塞休眠等待 101 | runtime_Semacquire(semap) 102 | // 被唤醒,不再阻塞,返回 103 | return 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /02-Mutex/3-1-goroutine_id.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | "sync/atomic" 10 | ) 11 | 12 | func main() { 13 | 14 | } 15 | 16 | // 方案一:goroutine id 17 | // 这个方案的关键第一步是获取 goroutine id,方式有两种,分别是简单方式和 hacker 方式。 18 | 19 | /* 通过 runtime.Stack 方法获取栈帧信息,栈帧信息里包含 goroutine id 20 | goroutine 1 [running]: 21 | main.main() 22 | ....../main.go:19 +0xb1 23 | */ 24 | 25 | // 只要解析出这个 id 即可 26 | func GoID() int { 27 | var buf [64]byte 28 | n := runtime.Stack(buf[:], false) 29 | // 得到id字符串 30 | idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] 31 | id, err := strconv.Atoi(idField) 32 | if err != nil { 33 | panic(fmt.Sprintf("cannot get goroutine id: %v", err)) 34 | } 35 | return id 36 | } 37 | 38 | /* hacker 的方式 39 | 第一步:我们先获取到 TLS 对象; 40 | 第二步:再从 TLS 中获取 goroutine 结构的 g 指针; 41 | 第三步:再从 g 指针中取出 goroutine id。 42 | */ 43 | 44 | // 获取 goroutine id,我们就实现一个可以使用的可重入锁 45 | // RecursiveMutex 包装一个Mutex,实现可重入 46 | type RecursiveMutex struct { 47 | sync.Mutex 48 | owner int64 // 当前持有锁的goroutine id 49 | recursion int32 // 这个goroutine 重入的次数 50 | } 51 | 52 | func (m *RecursiveMutex) Lock() { 53 | gid := goid.Get() 54 | // 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入 55 | if atomic.LoadInt64(&m.owner) == gid { 56 | m.recursion++ 57 | return 58 | } 59 | m.Mutex.Lock() 60 | // 获得锁的goroutine第一次调用,记录下它的goroutine id,调用次数加1 61 | atomic.StoreInt64(&m.owner, gid) 62 | m.recursion = 1 63 | } 64 | 65 | func (m *RecursiveMutex) Unlock() { 66 | gid := goid.Get() 67 | // 非持有锁的goroutine尝试释放锁,错误的使用 68 | if atomic.LoadInt64(&m.owner) != gid { 69 | panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid)) 70 | } 71 | // 调用次数减1 72 | m.recursion-- 73 | if m.recursion != 0 { // 如果这个goroutine还没有完全释放,则直接返回 74 | return 75 | } 76 | // 此goroutine最后一次调用,需要释放锁 77 | atomic.StoreInt64(&m.owner, -1) 78 | m.Mutex.Unlock() 79 | } 80 | 81 | 82 | 83 | // 方案二:token 84 | // Token方式的递归锁 85 | type TokenRecursiveMutex struct { 86 | sync.Mutex 87 | token int64 88 | recursion int32 89 | } 90 | 91 | // 请求锁,需要传入token 92 | func (m *TokenRecursiveMutex) Lock(token int64) { 93 | if atomic.LoadInt64(&m.token) == token { //如果传入的token和持有锁的token一致,说明是递归调用 94 | m.recursion++ 95 | return 96 | } 97 | m.Mutex.Lock() // 传入的token不一致,说明不是递归调用 98 | // 抢到锁之后记录这个token 99 | atomic.StoreInt64(&m.token, token) 100 | m.recursion = 1 101 | } 102 | 103 | // 释放锁 104 | func (m *TokenRecursiveMutex) Unlock(token int64) { 105 | if atomic.LoadInt64(&m.token) != token { // 释放其它token持有的锁 106 | panic(fmt.Sprintf("wrong the owner(%d): %d!", m.token, token)) 107 | } 108 | m.recursion-- // 当前持有这个锁的token释放锁 109 | if m.recursion != 0 { // 还没有回退到最初的递归调用 110 | return 111 | } 112 | atomic.StoreInt64(&m.token, 0) // 没有递归调用了,释放锁 113 | m.Mutex.Unlock() 114 | } 115 | -------------------------------------------------------------------------------- /15-CyclicBarrier/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 题目:一氧化二氢制造工厂 4 | 制造厂生产H2O,需要两个氢原子、一个氧原子,2N条生产线生产氢原子、N条生产线生产氧原子, 5 | 只有生产出两个氢原子、一个氧原子组合成一个H2O分子所有生产线才能继续执行。 6 | 7 | 这些生产线会通过一个栅栏,只有一个氧原子生产线和两个氢原子生产线都准备好,才能生成出一个水分子, 8 | 否则所有的生产线都会处于等待状态。也就是说,一个水分子必须由三个不同的生产线提供原子, 9 | 而且水分子是一个一个按照顺序产生的,每生产一个水分子,就会打印出 HHO、HOH、OHH 三种形式的其中一种。 10 | HHH、OOH、OHO、HOO、OOO 都是不允许的。生产线中氢原子的生产线为 2N 条,氧原子的生产线为 N 条。 11 | */ 12 | import ( 13 | "context" 14 | "log" 15 | "math/rand" 16 | "sort" 17 | "sync" 18 | "time" 19 | 20 | "github.com/marusama/cyclicbarrier" 21 | "golang.org/x/sync/semaphore" 22 | ) 23 | 24 | // 定义水分子合成的辅助数据结构 25 | type H2O struct { 26 | semaH *semaphore.Weighted // 氢原子的信号量 27 | semaO *semaphore.Weighted // 氧原子的信号量 28 | b cyclicbarrier.CyclicBarrier // 循环栅栏,用来控制合成 29 | } 30 | 31 | func New() *H2O { 32 | return &H2O{ 33 | semaH: semaphore.NewWeighted(2), //氢原子需要两个 34 | semaO: semaphore.NewWeighted(1), // 氧原子需要一个 35 | b: cyclicbarrier.New(3), // 需要三个原子才能合成 36 | } 37 | } 38 | 39 | func (h2o *H2O) hydrogen(releaseHydrogen func()) { 40 | h2o.semaH.Acquire(context.Background(), 1) 41 | 42 | releaseHydrogen() // 输出H 43 | h2o.b.Await(context.Background()) //等待栅栏放行 44 | h2o.semaH.Release(1) // 释放氢原子空槽 45 | } 46 | 47 | func (h2o *H2O) oxygen(releaseOxygen func()) { 48 | h2o.semaO.Acquire(context.Background(), 1) 49 | 50 | releaseOxygen() // 输出O 51 | h2o.b.Await(context.Background()) //等待栅栏放行 52 | h2o.semaO.Release(1) // 释放氢原子空槽 53 | } 54 | 55 | func main() { 56 | //用来存放水分子结果的channel 57 | var ch chan string 58 | releaseHydrogen := func() { 59 | ch <- "H" 60 | } 61 | releaseOxygen := func() { 62 | ch <- "O" 63 | } 64 | 65 | // 300个原子,300个goroutine,每个goroutine并发的产生一个原子 66 | N := 100 67 | ch = make(chan string, N*3) 68 | 69 | h2o := New() 70 | 71 | // 用来等待所有的goroutine完成 72 | var wg sync.WaitGroup 73 | wg.Add(N * 3) 74 | 75 | // 200个氢原子goroutine 76 | for i := 0; i < 2*N; i++ { 77 | go func() { 78 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) 79 | h2o.hydrogen(releaseHydrogen) 80 | wg.Done() 81 | }() 82 | } 83 | // 100个氧原子goroutine 84 | for i := 0; i < N; i++ { 85 | go func() { 86 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) 87 | h2o.oxygen(releaseOxygen) 88 | wg.Done() 89 | }() 90 | } 91 | 92 | //等待所有的goroutine执行完 93 | wg.Wait() 94 | 95 | // 结果中肯定是300个原子 96 | if len(ch) != N*3 { 97 | log.Fatalf("expect %d atom but got %d", N*3, len(ch)) 98 | } 99 | 100 | // 每三个原子一组,分别进行检查。要求这一组原子中必须包含两个氢原子和一个氧原子,这样才能正确组成一个水分子。 101 | var s = make([]string, 3) 102 | for i := 0; i < N; i++ { 103 | s[0] = <-ch 104 | s[1] = <-ch 105 | s[2] = <-ch 106 | sort.Strings(s) 107 | 108 | water := s[0] + s[1] + s[2] 109 | if water != "HHO" { 110 | log.Fatalf("expect a water molecule but got %s", water) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /13-Semaphore/source.go: -------------------------------------------------------------------------------- 1 | // source: src/cmd/vendor/golang.org/x/sync/semaphore/semaphore.go 2 | 3 | // 信号量 P V 伪代码表示如下,中括号代表原子操作 4 | function V(semaphore S, integer I): 5 | [S ← S + I] 6 | 7 | function P(semaphore S, integer I): 8 | repeat: 9 | [if S ≥ I: 10 | S ← S − I 11 | break] 12 | 13 | // Go 内部使用信号量来控制 goroutine 的阻塞和唤醒。 14 | // 我们在学习基本并发原语的实现时也看到了,比如互斥锁的第二个字段: 15 | type Mutex struct { 16 | state int32 17 | sema uint32 18 | } 19 | 20 | type waiter struct { 21 | n int64 22 | ready chan<- struct{} // Closed when semaphore acquired. 23 | } 24 | 25 | func NewWeighted(n int64) *Weighted { 26 | w := &Weighted{size: n} 27 | return w 28 | } 29 | 30 | /* 31 | Go 扩展库中的信号量是使用互斥锁 +List 实现的。互斥锁实现其它字段的保护, 32 | 而 List 实现了一个等待队列,等待者的通知是通过 Channel 的通知机制实现的。 33 | */ 34 | type Weighted struct { 35 | size int64 // 最大资源数 36 | cur int64 // 当前已被使用的资源 37 | mu sync.Mutex // 互斥锁,对字段的保护 38 | waiters list.List // 等待队列 39 | } 40 | 41 | 42 | // 在信号量的几个实现方法里,Acquire 是代码最复杂的一个方法, 43 | // 它不仅仅要监控资源是否可用,而且还要检测 Context 的 Done 是否已关闭。 44 | func (s *Weighted) Acquire(ctx context.Context, n int64) error { 45 | s.mu.Lock() 46 | // fast path, 如果有足够的资源,都不考虑ctx.Done的状态,将cur加上n就返回 47 | if s.size-s.cur >= n && s.waiters.Len() == 0 { 48 | s.cur += n 49 | s.mu.Unlock() 50 | return nil 51 | } 52 | // 如果是不可能完成的任务,请求的资源数大于能提供的最大的资源数 53 | if n > s.size { 54 | s.mu.Unlock() 55 | // 依赖ctx的状态返回,否则一直等待 56 | <-ctx.Done() 57 | return ctx.Err() 58 | } 59 | // 否则就需要把调用者加入到等待队列中 60 | // 创建了一个ready chan,以便被通知唤醒 61 | ready := make(chan struct{}) 62 | w := waiter{n: n, ready: ready} 63 | elem := s.waiters.PushBack(w) 64 | s.mu.Unlock() 65 | 66 | // 等待 67 | select { 68 | case <-ctx.Done(): // context的Done被关闭 69 | err := ctx.Err() 70 | s.mu.Lock() 71 | select { 72 | case <-ready: // 如果被唤醒了,忽略ctx的状态 73 | err = nil 74 | default: 通知waiter 75 | isFront := s.waiters.Front() == elem 76 | s.waiters.Remove(elem) 77 | // 通知其它的waiters,检查是否有足够的资源 78 | if isFront && s.size > s.cur { 79 | s.notifyWaiters() 80 | } 81 | } 82 | s.mu.Unlock() 83 | return err 84 | case <-ready: // 被唤醒了 85 | return nil 86 | } 87 | } 88 | 89 | 90 | // Release 方法将当前计数值减去释放的资源数 n,并唤醒等待队列中的调用者,看是否有足够的资源被获取。 91 | func (s *Weighted) Release(n int64) { 92 | s.mu.Lock() 93 | s.cur -= n 94 | if s.cur < 0 { 95 | s.mu.Unlock() 96 | panic("semaphore: released more than held") 97 | } 98 | s.notifyWaiters() 99 | s.mu.Unlock() 100 | } 101 | 102 | // 一个可用资源数量的判断,数量够用表示成功返回 true ,否则 false,此方法并不会进行阻塞,而是直接返回。 103 | func (s *Weighted) TryAcquire(n int64) bool { 104 | s.mu.Lock() 105 | success := s.size-s.cur >= n && s.waiters.Len() == 0 106 | if success { 107 | s.cur += n 108 | } 109 | s.mu.Unlock() 110 | return success 111 | } 112 | 113 | // 通知机制 114 | // 通过 for 循环从链表头部开始头部依次遍历出链表中的所有waiter, 115 | // 并更新计数器 Weighted.cur,同时将其从链表中删除,直到遇到 空闲资源数量 < watier.n 为止。 116 | func (s *Weighted) notifyWaiters() { 117 | for { 118 | next := s.waiters.Front() 119 | if next == nil { 120 | break // No more waiters blocked. 121 | } 122 | 123 | w := next.Value.(waiter) 124 | if s.size-s.cur < w.n { 125 | //避免饥饿,这里还是按照先入先出的方式处理 126 | break 127 | } 128 | 129 | s.cur += w.n 130 | s.waiters.Remove(next) 131 | close(w.ready) 132 | } 133 | } -------------------------------------------------------------------------------- /16-group/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "context" 4 | 5 | // 在使用 ErrGroup 时,我们要用到三个方法,分别是 WithContext、Go 和 Wait。 6 | 7 | // 1.WithContext 8 | // 在创建一个 Group 对象时,需要使用 WithContext 方法: 9 | func WithContext(ctx context.Context) (*Group, context.Context) 10 | 11 | // 这个方法返回一个 Group 实例,同时还会返回一个使用 context.WithCancel(ctx) 生成的新 Context。 12 | // 一旦有一个子任务返回错误,或者是 Wait 调用返回,这个新 Context 就会被 cancel。 13 | 14 | // 2.Go 15 | // 执行子任务的 Go 方法: 16 | func (g *Group) Go(f func() error) 17 | 18 | // 传入的子任务函数 f 是类型为 func() error 的函数, 19 | // 如果任务执行成功,就返回 nil,否则就返回 error,并且会 cancel 那个新的 Context。 20 | 21 | // 3.Wait 22 | // 类似 WaitGroup,Group 也有 Wait 方法,等所有的子任务都完成后,它才会返回,否则只会阻塞等待。 23 | // 如果有多个子任务返回错误,它只会返回第一个出现的错误,如果所有的子任务都执行成功,就返回 nil: 24 | 25 | func (g *Group) Wait() error 26 | 27 | // ---------------------------------------------------------------- 28 | // gollback 29 | 30 | // 1.All 31 | // 方法All 方法的签名如下: 32 | func All(ctx context.Context, fns ...AsyncFunc) ([]interface{}, []error) 33 | 34 | // 它会等待所有的异步函数(AsyncFunc)都执行完才返回,而且返回结果的顺序和传入的函数的顺序保持一致。 35 | // 第一个返回参数是子任务的执行结果,第二个参数是子任务执行时的错误信息。其中,异步函数的定义如下: 36 | type AsyncFunc func(ctx context.Context) (interface{}, error) 37 | 38 | // 可以看到,ctx 会被传递给子任务。如果你 cancel 这个 ctx,可以取消子任务。 39 | // 例:example_all.go 40 | 41 | // 2.Race 42 | // 方法Race 方法跟 All 方法类似,只不过,在使用 Race 方法的时候,只要一个异步函数执行没有错误,就立马返回, 43 | // 而不会返回所有的子任务信息。如果所有的子任务都没有成功,就会返回最后一个 error 信息。 44 | // Race 方法签名如下: 45 | 46 | func Race(ctx context.Context, fns ...AsyncFunc) (interface{}, error) 47 | 48 | // 如果有一个正常的子任务的结果返回,Race 会把传入到其它子任务的 Context cancel 掉,这样子任务就可以中断自己的执行。 49 | // Race 的使用方法也跟 All 方法类似,可以把 All 方法的例子中的 All 替换成 Race 方式。 50 | 51 | // 3.Retry 52 | // 方法Retry 不是执行一组子任务,而是执行一个子任务。如果子任务执行失败,它会尝试一定的次数, 53 | // 如果一直不成功 ,就会返回失败错误 ,如果执行成功,它会立即返回。如果 retires 等于 0,它会永远尝试,直到成功。 54 | 55 | func Retry(ctx context.Context, retires int, fn AsyncFunc) (interface{}, error) 56 | 57 | // 例:example_retry.go 58 | 59 | // --------------------------------------------------------------- 60 | // Hunch 61 | // Hunch提供的功能和 gollback 类似,不过它提供的方法更多,而且它提供的和 gollback 相应的方法,也有一些不同。 62 | // 它定义了执行子任务的函数,这和 gollback 的 AyncFunc 是一样的, 63 | // 它的定义如下: 64 | 65 | type Executable func(context.Context) (interface{}, error) 66 | 67 | // 1.All 68 | // 方法All 方法的签名如下: 69 | 70 | func All(parentCtx context.Context, execs ...Executable) ([]interface{}, error) 71 | 72 | // 它会传入一组可执行的函数(子任务),返回子任务的执行结果。 73 | // 和 gollback 的 All 方法不一样的是,一旦一个子任务出现错误,它就会返回错误信息,执行结果(第一个返回参数)为 nil。 74 | 75 | // 2.Take 方法Take 方法的签名如下: 76 | 77 | func Take(parentCtx context.Context, num int, execs ...Executable) ([]interface{}, error) 78 | 79 | // 你可以指定 num 参数,只要有 num 个子任务正常执行完没有错误,这个方法就会返回这几个子任务的结果。 80 | // 一旦一个子任务出现错误,它就会返回错误信息,执行结果(第一个返回参数)为 nil。 81 | 82 | // 3.Last 83 | // 方法Last 方法的签名如下: 84 | 85 | func Last(parentCtx context.Context, num int, execs ...Executable) ([]interface{}, error) 86 | 87 | // 它只返回最后 num 个正常执行的、没有错误的子任务的结果。 88 | // 一旦一个子任务出现错误,它就会返回错误信息,执行结果(第一个返回参数)为 nil。 89 | // 比如 num 等于 1,那么,它只会返回最后一个无错的子任务的结果。 90 | 91 | // 4.Retry 92 | // 方法Retry 方法的签名如下: 93 | 94 | func Retry(parentCtx context.Context, retries int, fn Executable) (interface{}, error) 95 | 96 | // 它的功能和 gollback 的 Retry 方法的功能一样,如果子任务执行出错,就会不断尝试,直到成功或者是达到重试上限。 97 | // 如果达到重试上限,就会返回错误。如果 retries 等于 0,它会不断尝试。 98 | 99 | // 5.Waterfall 100 | // 方法Waterfall 方法签名如下: 101 | 102 | func Waterfall(parentCtx context.Context, execs ...ExecutableInSequence) (interface{}, error) 103 | 104 | // 它其实是一个 pipeline 的处理方式,所有的子任务都是串行执行的, 105 | // 前一个子任务的执行结果会被当作参数传给下一个子任务, 106 | // 直到所有的任务都完成,返回最后的执行结果。 107 | // 一旦一个子任务出现错误,它就会返回错误信息,执行结果(第一个返回参数)为 nil。 108 | // gollback 和 Hunch 是属于同一类的并发原语,对一组子任务的执行结果, 109 | // 可以选择一个结果或者多个结果,这也是现在热门的微服务常用的服务治理的方法。 110 | 111 | // ---------------------------------------------------------------- 112 | // schedgroup 113 | // 一个和时间相关的处理一组 goroutine 的并发原语 schedgroup。 114 | // 这个并发原语包含的方法如下: 115 | 116 | type Group 117 | func New(ctx context.Context) *Group 118 | func (g *Group) Delay(delay time.Duration, fn func()) 119 | func (g *Group) Schedule(when time.Time, fn func()) 120 | func (g *Group) Wait() error 121 | 122 | // 1 2.Delay 和 Schedule 方法。 123 | // 它们的功能其实是一样的,都是用来指定在某个时间或者之后执行一个函数。 124 | // 只不过,Delay 传入的是一个 time.Duration 参数,它会在 time.Now()+delay 之后执行函数, 125 | // 而 Schedule 可以指定明确的某个时间执行。 126 | 127 | // 3. Wait 方法。 128 | // 这个方法调用会阻塞调用者,直到之前安排的所有子任务都执行完才返回。 129 | // 如果 Context 被取消,那么,Wait 方法会返回这个 cancel error。 130 | // 需要两点注意: 131 | // 第一点是,如果调用了 Wait 方法,你就不能再调用它的 Delay 和 Schedule 方法,否则会 panic。 132 | // 第二点是,Wait 方法只能调用一次,如果多次调用的话,就会 panic。 -------------------------------------------------------------------------------- /09-Pool/source.go: -------------------------------------------------------------------------------- 1 | // source: src/sync/pool.go 2 | 3 | type Pool struct { 4 | noCopy noCopy 5 | 6 | local unsafe.Pointer // 每个 P 的本地队列,实际类型为 [P]poolLocal 7 | localSize uintptr // [P]poolLocal 的大小 8 | 9 | // Victim Cache 本来是计算机架构里面的一个概念,是CPU硬件处理缓存的一种技术, 10 | // sync.Pool引入的意图在于降低GC压力的同时提高命中率。 11 | // 在一轮GC到来时,victim 和 victimSize 会分别接管 local 和 localSize, 12 | // victim机制在于减少GC冷启动导致的性能抖动让分配对象更加平滑。 13 | victim unsafe.Pointer 14 | victimSize uintptr 15 | 16 | // 自定义的对象创建回调函数,当 pool 中无可用对象时会调用此函数 17 | New func() any // interface{} 18 | } 19 | 20 | // ================================================================ 21 | // 垃圾回收时 sync.Pool 的处理逻辑: 22 | func poolCleanup() { 23 | // 丢弃当前victim, STW所以不用加锁 24 | for _, p := range oldPools { 25 | p.victim = nil 26 | p.victimSize = 0 27 | } 28 | 29 | // 将local复制给victim, 并将原local置为nil 30 | for _, p := range allPools { 31 | p.victim = p.local 32 | p.victimSize = p.localSize 33 | p.local = nil 34 | p.localSize = 0 35 | } 36 | 37 | oldPools, allPools = allPools, nil 38 | } 39 | 40 | type poolLocalInternal struct { 41 | private any // P 的私有缓存区,使用时不需要加锁. 42 | shared poolChain // 公共缓存区。本地 P 可以 pushHead/popHead; 其它 P 只能 popTail. 43 | } 44 | 45 | /* Analysis: 46 | 你需要关注一下 local 字段,因为所有当前主要的空闲可用的元素都存放在 local 字段中, 47 | 请求元素时也是优先从 local 字段中查找可用的元素。 48 | local 字段包含一个 poolLocalInternal 字段,并提供 CPU 缓存对齐,从而避免 false sharing。 49 | 50 | poolLocalInternal 也包含两个字段:private 和 shared。 51 | - private,代表一个缓存的元素,而且只能由相应的一个 P 存取。 52 | 因为一个 P 同时只能执行一个 goroutine,所以不会有并发的问题。 53 | - shared,可以由任意的 P 访问,但是只有本地的 P 才能 pushHead/popHead, 54 | 其它 P 可以 popTail,相当于只有一个本地的 P 作为生产者(Producer), 55 | 多个 P 作为消费者(Consumer),它是使用一个 local-free 的 queue 列表实现的。 56 | 57 | */ 58 | 59 | // ================================================================= 60 | // Get 方法 61 | func (p *Pool) Get() interface{} { 62 | // 把当前goroutine固定在当前的P上 63 | l, pid := p.pin() 64 | x := l.private // 优先从local的private字段取,快速 65 | l.private = nil 66 | if x == nil { 67 | // 从当前的local.shared弹出一个,注意是从head读取并移除 68 | x, _ = l.shared.popHead() 69 | if x == nil { // 如果没有,则去偷一个 70 | x = p.getSlow(pid) 71 | } 72 | } 73 | runtime_procUnpin() 74 | // 如果没有获取到,尝试使用New函数生成一个新的 75 | if x == nil && p.New != nil { 76 | x = p.New() 77 | } 78 | return x 79 | } 80 | 81 | func (p *Pool) getSlow(pid int) interface{} { 82 | 83 | size := atomic.LoadUintptr(&p.localSize) 84 | locals := p.local 85 | // 从其它proc中尝试偷取一个元素 86 | for i := 0; i < int(size); i++ { 87 | l := indexLocal(locals, (pid+i+1)%int(size)) 88 | if x, _ := l.shared.popTail(); x != nil { 89 | return x 90 | } 91 | } 92 | 93 | // 如果其它proc也没有可用元素,那么尝试从victim中获取 94 | size = atomic.LoadUintptr(&p.victimSize) 95 | if uintptr(pid) >= size { 96 | return nil 97 | } 98 | locals = p.victim 99 | l := indexLocal(locals, pid) 100 | if x := l.private; x != nil { // 同样的逻辑,先从victim中的local private获取 101 | l.private = nil 102 | return x 103 | } 104 | for i := 0; i < int(size); i++ { // 从victim其它proc尝试偷取 105 | l := indexLocal(locals, (pid+i)%int(size)) 106 | if x, _ := l.shared.popTail(); x != nil { 107 | return x 108 | } 109 | } 110 | 111 | // 如果victim中都没有,则把这个victim标记为空,以后的查找可以快速跳过了 112 | atomic.StoreUintptr(&p.victimSize, 0) 113 | 114 | return nil 115 | } 116 | 117 | /* Analysis: 118 | 1)首先,调用 p.pin()函数将当前的 goroutine 和 P 绑定,禁止被抢占,返回当前 P对应的poolLocal 以及pid. 119 | 2)然后直接取 l.private, 赋值给 x,并置 l.private 为 nil。 120 | 3)判断x 是否为空,若为空,则尝试从 l.shared 的头部 pop一个对象出来,同时赋值给 x。 121 | 4)如果×仍然为空,则调用 getslow 尝试从其他 P 的shared 双端队列尾部“偷”一个对象出来。 122 | 5)Pool 的相关操作做完了,调用runtime_procUnpin()解除非抢占。 123 | 6) 最后如果还是没有取到缓存对象,那就直接调用预先设置好的New函数,创建一个出来。 124 | */ 125 | 126 | // ================================================================= 127 | // pin 128 | // 调用方必须完成取值后调用 runtime_procUnpin() 来取消抢占 129 | func (p *Pool) pin() (*poolLocal, int) { 130 | pid := runtime_procPin() 131 | 132 | s := runtime_LoadAcquintptr(&p.localSize) // load-acquire 133 | l := p.local // load-consume 134 | // 因为可能存在动态的 P (运行时调整 p 的个数) 135 | if uintptr(pid) < s { 136 | return indexLocal(l, pid), pid 137 | } 138 | return p.pinSlow() 139 | } 140 | 141 | // ==================================================================== 142 | // Put 143 | func (p *Pool) Put(x interface{}) { 144 | if x == nil { // nil值直接丢弃 145 | return 146 | } 147 | l, _ := p.pin() 148 | if l.private == nil { // 如果本地private没有值,直接设置这个值即可 149 | l.private = x 150 | x = nil 151 | } 152 | if x != nil { // 否则加入到本地队列中 153 | l.shared.pushHead(x) 154 | } 155 | runtime_procUnpin() 156 | } 157 | 158 | /* Analysis: 159 | Put 的逻辑相对简单,优先设置本地 private,如果 private 字段已经有值了, 160 | 那么就把此元素 push 到本地队列中。 161 | */ -------------------------------------------------------------------------------- /12-channel/source-send.go: -------------------------------------------------------------------------------- 1 | 2 | func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { 3 | // 如果 channel 是 nil 4 | if c == nil { 5 | // 不能阻塞,直接返回 false,表示未发送成功 6 | if !block { 7 | return false 8 | } 9 | // 当前 goroutine 被挂起 10 | gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2) 11 | throw("unreachable") 12 | } 13 | 14 | // 省略 debug 相关…… 15 | 16 | // 对于不阻塞的 send,快速检测失败场景 17 | // 18 | // 如果 channel 未关闭且 channel 没有多余的缓冲空间。这可能是: 19 | // 1. channel 是非缓冲型的,且等待接收队列里没有 goroutine 20 | // 2. channel 是缓冲型的,但循环数组已经装满了元素 21 | if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) || 22 | (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) { 23 | return false 24 | } 25 | 26 | var t0 int64 27 | if blockprofilerate > 0 { 28 | t0 = cputicks() 29 | } 30 | 31 | // 锁住 channel,并发安全 32 | lock(&c.lock) 33 | 34 | // 如果 channel 关闭了 35 | if c.closed != 0 { 36 | // 解锁 37 | unlock(&c.lock) 38 | // 直接 panic 39 | panic(plainError("send on closed channel")) 40 | } 41 | 42 | // 如果接收队列里有 goroutine,直接将要发送的数据拷贝到接收 goroutine 43 | if sg := c.recvq.dequeue(); sg != nil { 44 | send(c, sg, ep, func() { unlock(&c.lock) }, 3) 45 | return true 46 | } 47 | 48 | // 对于缓冲型的 channel,如果还有缓冲空间 49 | if c.qcount < c.dataqsiz { 50 | // qp 指向 buf 的 sendx 位置 51 | qp := chanbuf(c, c.sendx) 52 | 53 | // …… 54 | 55 | // 将数据从 ep 处拷贝到 qp 56 | typedmemmove(c.elemtype, qp, ep) 57 | // 发送游标值加 1 58 | c.sendx++ 59 | // 如果发送游标值等于容量值,游标值归 0 60 | if c.sendx == c.dataqsiz { 61 | c.sendx = 0 62 | } 63 | // 缓冲区的元素数量加一 64 | c.qcount++ 65 | 66 | // 解锁 67 | unlock(&c.lock) 68 | return true 69 | } 70 | 71 | // 如果不需要阻塞,则直接返回错误 72 | if !block { 73 | unlock(&c.lock) 74 | return false 75 | } 76 | 77 | // channel 满了,发送方会被阻塞。接下来会构造一个 sudog 78 | 79 | // 获取当前 goroutine 的指针 80 | gp := getg() 81 | mysg := acquireSudog() 82 | mysg.releasetime = 0 83 | if t0 != 0 { 84 | mysg.releasetime = -1 85 | } 86 | 87 | mysg.elem = ep 88 | mysg.waitlink = nil 89 | mysg.g = gp 90 | mysg.selectdone = nil 91 | mysg.c = c 92 | gp.waiting = mysg 93 | gp.param = nil 94 | 95 | // 当前 goroutine 进入发送等待队列 96 | c.sendq.enqueue(mysg) 97 | 98 | // 当前 goroutine 被挂起 99 | goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3) 100 | 101 | // 从这里开始被唤醒了(channel 有机会可以发送了) 102 | if mysg != gp.waiting { 103 | throw("G waiting list is corrupted") 104 | } 105 | gp.waiting = nil 106 | if gp.param == nil { 107 | if c.closed == 0 { 108 | throw("chansend: spurious wakeup") 109 | } 110 | // 被唤醒后,channel 关闭了。panic 111 | panic(plainError("send on closed channel")) 112 | } 113 | gp.param = nil 114 | if mysg.releasetime > 0 { 115 | blockevent(mysg.releasetime-t0, 2) 116 | } 117 | // 去掉 mysg 上绑定的 channel 118 | mysg.c = nil 119 | releaseSudog(mysg) 120 | return true 121 | } 122 | 123 | /* 不加锁快速检测失败并返回的三种情况 124 | 1.如果检测到 channel 是空的,并且属于非阻塞型发送,直接返回 false,同时发送给失败。 125 | 2.如果检测到 channel 是空的,并且属于阻塞型发送,那么当前 goroutine 会被挂起,并且永远不会被唤醒。 126 | 3.对于不阻塞的发送操作,如果 channel 未关闭并且没有多余的缓冲空间, 直接返回 false。 127 | 此时状态:a. channel 是非缓冲型的,且等待接收队列里没有 goroutine; 128 | b. channel 是缓冲型的,但循环数组已经装满了元素。 129 | */ 130 | 131 | // 如果检测到 channel 已经关闭,直接 panic。 132 | 133 | // 如果能从等待接收队列 recvq 里出队一个 sudog(代表一个 goroutine), 134 | // 说明此时 channel 是空的,没有元素,所以才会有等待接收者。 135 | // 这时会调用 send 函数将元素直接从发送者的栈拷贝到接收者的栈,关键操作由 sendDirect 函数完成。 136 | // send 函数处理向一个空的 channel 发送操作 137 | 138 | // ep 指向被发送的元素,会被直接拷贝到接收的 goroutine 139 | // 之后,接收的 goroutine 会被唤醒 140 | // c 必须是空的(因为等待队列里有 goroutine,肯定是空的) 141 | // c 必须被上锁,发送操作执行完后,会使用 unlockf 函数解锁 142 | // sg 必须已经从等待队列里取出来了 143 | // ep 必须是非空,并且它指向堆或调用者的栈 144 | 145 | func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { 146 | // 省略一些用不到的 147 | // …… 148 | 149 | // sg.elem 指向接收到的值存放的位置,如 val <- ch,指的就是 &val 150 | if sg.elem != nil { 151 | // 直接拷贝内存(从发送者到接收者) 152 | sendDirect(c.elemtype, sg, ep) 153 | sg.elem = nil 154 | } 155 | // sudog 上绑定的 goroutine 156 | gp := sg.g 157 | // 解锁 158 | unlockf() 159 | gp.param = unsafe.Pointer(sg) 160 | if sg.releasetime != 0 { 161 | sg.releasetime = cputicks() 162 | } 163 | // 唤醒接收的 goroutine. skip 和打印栈相关,暂时不理会 164 | goready(gp, skip+1) 165 | } 166 | // 向一个非缓冲型的 channel 发送数据、从一个无元素的(非缓冲型或缓冲型但空)的 channel 167 | // 接收数据,都会导致一个 goroutine 直接操作另一个 goroutine 的栈 168 | // 由于 GC 假设对栈的写操作只能发生在 goroutine 正在运行中并且由当前 goroutine 来写 169 | // 所以这里实际上违反了这个假设。可能会造成一些问题,所以需要用到写屏障来规避 170 | func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) { 171 | // src 在当前 goroutine 的栈上,dst 是另一个 goroutine 的栈 172 | 173 | // 直接进行内存"搬迁" 174 | // 如果目标地址的栈发生了栈收缩,当我们读出了 sg.elem 后 175 | // 就不能修改真正的 dst 位置的值了 176 | // 因此需要在读和写之前加上一个屏障 177 | dst := sg.elem 178 | typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) 179 | memmove(dst, src, t.size) 180 | } -------------------------------------------------------------------------------- /12-channel/source-receive.go: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 接收操作有两种写法,一种带 “ok”,反应 channel 是否关闭; 4 | 一种不带 “ok”,这种写法,当接收到相应类型的零值时无法知道是真实的发送者发送过来的值, 5 | 还是 channel 被关闭后,返回给接收者的默认类型的零值。两种写法,都有各自的应用场景。 6 | */ 7 | func chanrecv1(c *hchan, elem unsafe.Pointer) { 8 | chanrecv(c, elem, true) 9 | } 10 | 11 | func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) { 12 | _, received = chanrecv(c, elem, true) 13 | return 14 | } 15 | 16 | /* 17 | chanrecv1 函数处理不带 “ok” 的情形,chanrecv2 则通过返回 “received” 这个字段来反应 channel 是否被关闭。 18 | 接收值则比较特殊,会“放到”参数 elem 所指向的地址了。如果代码里忽略了接收值,这里的 elem 为 nil。 19 | 20 | 无论如何,最终转向了 chanrecv 函数: 21 | */ 22 | 23 | // chanrecv 函数接收 channel c 的元素并将其写入 ep 所指向的内存地址。 24 | // 如果 ep 是 nil,说明忽略了接收值。 25 | // 如果 block == false,即非阻塞型接收,在没有数据可接收的情况下,返回 (false, false) 26 | // 否则,如果 c 处于关闭状态,将 ep 指向的地址清零,返回 (true, false) 27 | // 否则,用返回值填充 ep 指向的内存地址。返回 (true, true) 28 | // 如果 ep 非空,则应该指向堆或者函数调用者的栈 29 | 30 | func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { 31 | // 省略 debug 内容 ………… 32 | 33 | // 如果是一个 nil 的 channel 34 | if c == nil { 35 | // 如果不阻塞,直接返回 (false, false) 36 | if !block { 37 | return 38 | } 39 | // 否则,接收一个 nil 的 channel,goroutine 挂起 40 | gopark(nil, nil, "chan receive (nil chan)", traceEvGoStop, 2) 41 | // 不会执行到这里 42 | throw("unreachable") 43 | } 44 | 45 | // 在非阻塞模式下,快速检测到失败,不用获取锁,快速返回 46 | // 当我们观察到 channel 没准备好接收: 47 | // 1. 非缓冲型,等待发送列队 sendq 里没有 goroutine 在等待 48 | // 2. 缓冲型,但 buf 里没有元素 49 | // 之后,又观察到 closed == 0,即 channel 未关闭。 50 | // 因为 channel 不可能被重复打开,所以前一个观测的时候 channel 也是未关闭的, 51 | // 因此在这种情况下可以直接宣布接收失败,返回 (false, false) 52 | if !block && (c.dataqsiz == 0 && c.sendq.first == nil || 53 | c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) && 54 | atomic.Load(&c.closed) == 0 { 55 | return 56 | } 57 | 58 | var t0 int64 59 | if blockprofilerate > 0 { 60 | t0 = cputicks() 61 | } 62 | 63 | // 加锁 64 | lock(&c.lock) 65 | 66 | // channel 已关闭,并且循环数组 buf 里没有元素 67 | // 这里可以处理非缓冲型关闭 和 缓冲型关闭但 buf 无元素的情况 68 | // 也就是说即使是关闭状态,但在缓冲型的 channel, 69 | // buf 里有元素的情况下还能接收到元素 70 | if c.closed != 0 && c.qcount == 0 { 71 | if raceenabled { 72 | raceacquire(unsafe.Pointer(c)) 73 | } 74 | // 解锁 75 | unlock(&c.lock) 76 | if ep != nil { 77 | // 从一个已关闭的 channel 执行接收操作,且未忽略返回值 78 | // 那么接收的值将是一个该类型的零值 79 | // typedmemclr 根据类型清理相应地址的内存 80 | typedmemclr(c.elemtype, ep) 81 | } 82 | // 从一个已关闭的 channel 接收,selected 会返回true 83 | return true, false 84 | } 85 | 86 | // 等待发送队列里有 goroutine 存在,说明 buf 是满的 87 | // 这有可能是: 88 | // 1. 非缓冲型的 channel 89 | // 2. 缓冲型的 channel,但 buf 满了 90 | // 针对 1,直接进行内存拷贝(从 sender goroutine -> receiver goroutine) 91 | // 针对 2,接收到循环数组头部的元素,并将发送者的元素放到循环数组尾部 92 | if sg := c.sendq.dequeue(); sg != nil { 93 | // Found a waiting sender. If buffer is size 0, receive value 94 | // directly from sender. Otherwise, receive from head of queue 95 | // and add sender's value to the tail of the queue (both map to 96 | // the same buffer slot because the queue is full). 97 | recv(c, sg, ep, func() { unlock(&c.lock) }, 3) 98 | return true, true 99 | } 100 | 101 | // 缓冲型,buf 里有元素,可以正常接收 102 | if c.qcount > 0 { 103 | // 直接从循环数组里找到要接收的元素 104 | qp := chanbuf(c, c.recvx) 105 | 106 | // ………… 107 | 108 | // 代码里,没有忽略要接收的值,不是 "<- ch",而是 "val <- ch",ep 指向 val 109 | if ep != nil { 110 | typedmemmove(c.elemtype, ep, qp) 111 | } 112 | // 清理掉循环数组里相应位置的值 113 | typedmemclr(c.elemtype, qp) 114 | // 接收游标向前移动 115 | c.recvx++ 116 | // 接收游标归零 117 | if c.recvx == c.dataqsiz { 118 | c.recvx = 0 119 | } 120 | // buf 数组里的元素个数减 1 121 | c.qcount-- 122 | // 解锁 123 | unlock(&c.lock) 124 | return true, true 125 | } 126 | 127 | if !block { 128 | // 非阻塞接收,解锁。selected 返回 false,因为没有接收到值 129 | unlock(&c.lock) 130 | return false, false 131 | } 132 | 133 | // 接下来就是要被阻塞的情况了 134 | // 构造一个 sudog 135 | gp := getg() 136 | mysg := acquireSudog() 137 | mysg.releasetime = 0 138 | if t0 != 0 { 139 | mysg.releasetime = -1 140 | } 141 | 142 | // 待接收数据的地址保存下来 143 | mysg.elem = ep 144 | mysg.waitlink = nil 145 | gp.waiting = mysg 146 | mysg.g = gp 147 | mysg.selectdone = nil 148 | mysg.c = c 149 | gp.param = nil 150 | // 进入channel 的等待接收队列 151 | c.recvq.enqueue(mysg) 152 | // 将当前 goroutine 挂起 153 | goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3) 154 | 155 | // 被唤醒了,接着从这里继续执行一些扫尾工作 156 | if mysg != gp.waiting { 157 | throw("G waiting list is corrupted") 158 | } 159 | gp.waiting = nil 160 | if mysg.releasetime > 0 { 161 | blockevent(mysg.releasetime-t0, 2) 162 | } 163 | closed := gp.param == nil 164 | gp.param = nil 165 | mysg.c = nil 166 | releaseSudog(mysg) 167 | return true, !closed 168 | } 169 | 170 | /* 171 | 1.如果 channel 是一个空值(nil),在非阻塞模式下,会直接返回。 172 | 在阻塞模式下,会调用 gopark 函数挂起 goroutine,这个会一直阻塞下去。 173 | 因为在 channel 是 nil 的情况下,要想不阻塞,只有关闭它,但关闭一个 nil 174 | 的 channel 又会发生 panic,所以没有机会被唤醒了。 175 | 176 | 2.在非阻塞模式下,不用获取锁,快速检测到失败并且返回的操作。 177 | */ 178 | 179 | 180 | // 在非阻塞模式下,快速检测到失败,不用获取锁,快速返回 (false, false) 181 | if !block && (c.dataqsiz == 0 && c.sendq.first == nil || 182 | c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) && 183 | atomic.Load(&c.closed) == 0 { 184 | return 185 | } 186 | 187 | -------------------------------------------------------------------------------- /08-Map/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | func main() { 9 | // 源码地址:src/sync/map.go 10 | } 11 | 12 | type Map struct { 13 | mu Mutex 14 | // 基本上你可以把它看成一个安全的只读的map 15 | // 它包含的元素其实也是通过原子操作更新的,但是已删除的entry就需要加锁操作了 16 | read atomic.Value // readOnly 17 | 18 | // 包含需要加锁才能访问的元素 19 | // 包括所有在read字段中但未被expunged(删除)的元素以及新加的元素 20 | dirty map[interface{}]*entry 21 | 22 | // 记录从read中读取miss的次数,一旦miss数和dirty长度一样了,就会把dirty提升为read,并把dirty置空 23 | misses int 24 | } 25 | 26 | type readOnly struct { 27 | m map[interface{}]*entry 28 | amended bool // 当dirty中包含read没有的数据时为true,比如新增一条数据 29 | } 30 | 31 | // expunged是用来标识此项已经删掉的指针 32 | // 当map中的一个项目被删除了,只是把它的值标记为expunged,以后才有机会真正删除此项 33 | var expunged = unsafe.Pointer(new(interface{})) 34 | 35 | // entry代表一个值 36 | type entry struct { 37 | p unsafe.Pointer // *interface{} 38 | } 39 | 40 | /*Analysis: 41 | 如果 dirty 字段非 nil 的话,map 的 read 字段和 dirty 字段会 42 | 包含相同的非 expunged 的项,所以如果通过 read 字段更改了这个项 43 | 的值,从 dirty 字段中也会读取到这个项的新值,因为本来它们指向的 44 | 就是同一个地址。 45 | dirty 包含重复项目的好处就是,一旦 miss 数达到阈值需要将 dirty 46 | 提升为 read 的话,只需简单地把 dirty 设置为 read 对象即可。不 47 | 好的一点就是,当创建新的 dirty 对象的时候,需要逐条遍历 read, 48 | 把非 expunged 的项复制到 dirty 对象中。 49 | */ 50 | 51 | // ================================================================= 52 | func (m *Map) Store(key, value interface{}) { 53 | read, _ := m.read.Load().(readOnly) 54 | // 如果read字段包含这个项,说明是更新,cas更新项目的值即可 55 | if e, ok := read.m[key]; ok && e.tryStore(&value) { 56 | return 57 | } 58 | 59 | // read中不存在,或者cas更新失败,就需要加锁访问dirty了 60 | m.mu.Lock() 61 | read, _ = m.read.Load().(readOnly) 62 | if e, ok := read.m[key]; ok { // 双检查,看看read是否已经存在了 63 | if e.unexpungeLocked() { 64 | // 此项目先前已经被删除了,通过将它的值设置为nil,标记为unexpunged 65 | m.dirty[key] = e 66 | } 67 | e.storeLocked(&value) // 更新 68 | } else if e, ok := m.dirty[key]; ok { // 如果dirty中有此项 69 | e.storeLocked(&value) // 直接更新 70 | } else { // 否则就是一个新的key 71 | if !read.amended { //如果dirty为nil 72 | // 需要创建dirty对象,并且标记read的amended为true, 73 | // 说明有元素它不包含而dirty包含 74 | m.dirtyLocked() 75 | m.read.Store(readOnly{m: read.m, amended: true}) 76 | } 77 | m.dirty[key] = newEntry(value) //将新值增加到dirty对象中 78 | } 79 | m.mu.Unlock() 80 | } 81 | 82 | func (m *Map) dirtyLocked() { 83 | if m.dirty != nil { // 如果dirty字段已经存在,不需要创建了 84 | return 85 | } 86 | 87 | read, _ := m.read.Load().(readOnly) // 获取read字段 88 | m.dirty = make(map[interface{}]*entry, len(read.m)) 89 | for k, e := range read.m { // 遍历read字段 90 | if !e.tryExpungeLocked() { // 把非punged的键值对复制到dirty中 91 | m.dirty[k] = e 92 | } 93 | } 94 | } 95 | 96 | /*Analysis: 97 | Store方法有多条路径, 98 | 第一条,如果read中存在,直接更新read和dirty(他们的key共享的value都是同一个entry,所以更新read会把dirty也更新) 99 | 第二条,如果这个key在read中存在并且之前这个key已经被删除了(expunged),那么就将他设置为nil表示未删除然后把这个nil替换成要保存的值 (read和dirty同时修改) 100 | 第三条,如果在dirty中存在 ,直接修改(这条路径其实就是 dirty有 但是read没有) 101 | 第四条,是新增key 102 | 103 | 所以从这么来看,sync.Map 适合那些只会增长的缓存系统,可以进行更新,但是不要删除,并且不要频繁地增加新元素。 104 | 新加的元素需要放入到 dirty 中,如果 dirty 为 nil,那么需要从 read 字段中复制出来一个 dirty 对象 105 | */ 106 | 107 | // ================================================================= 108 | 109 | func (m *Map) Load(key interface{}) (value interface{}, ok bool) { 110 | // 首先从read处理 111 | read, _ := m.read.Load().(readOnly) 112 | e, ok := read.m[key] 113 | if !ok && read.amended { // 如果不存在并且dirty不为nil(有新的元素) 114 | m.mu.Lock() 115 | // 双检查,看看read中现在是否存在此key 116 | read, _ = m.read.Load().(readOnly) 117 | e, ok = read.m[key] 118 | if !ok && read.amended { //依然不存在,并且dirty不为nil 119 | e, ok = m.dirty[key] // 从dirty中读取 120 | // 不管dirty中存不存在,miss数都加1 121 | m.missLocked() 122 | } 123 | m.mu.Unlock() 124 | } 125 | if !ok { 126 | return nil, false 127 | } 128 | return e.load() //返回读取的对象,e既可能是从read中获得的,也可能是从dirty中获得的 129 | } 130 | 131 | func (m *Map) missLocked() { 132 | m.misses++ // misses计数加一 133 | if m.misses < len(m.dirty) { // 如果没达到阈值(dirty字段的长度),返回 134 | return 135 | } 136 | m.read.Store(readOnly{m: m.dirty}) //把dirty字段的内存提升为read字段 137 | m.dirty = nil // 清空dirty 138 | m.misses = 0 // misses数重置为0 139 | } 140 | 141 | /*Analysis: 142 | Load 方法用来读取一个 key 对应的值。它也是从 read 开始处理,一开始并不需要锁。 143 | 如果幸运的话,我们从 read 中读取到了这个 key 对应的值,那么就不需要加锁了,性能会非常好。 144 | 但是,如果请求的 key 不存在或者是新加的,就需要加锁从 dirty 中读取。 145 | 所以,读取不存在的 key 会因为加锁而导致性能下降,读取还没有提升的新值的情况下也会因为加锁性能下降。 146 | 147 | missLocked 增加 miss 的时候,如果 miss 数等于 dirty 长度,会将 dirty 提升为 read,并将 dirty 置空。 148 | */ 149 | 150 | // ================================================================= 151 | 152 | func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) { 153 | read, _ := m.read.Load().(readOnly) 154 | e, ok := read.m[key] 155 | if !ok && read.amended { 156 | m.mu.Lock() 157 | // 双检查 158 | read, _ = m.read.Load().(readOnly) 159 | e, ok = read.m[key] 160 | if !ok && read.amended { 161 | e, ok = m.dirty[key] 162 | // 这一行长坤在1.15中实现的时候忘记加上了,导致在特殊的场景下有些key总是没有被回收 163 | delete(m.dirty, key) 164 | // miss数加1 165 | m.missLocked() 166 | } 167 | m.mu.Unlock() 168 | } 169 | if ok { 170 | return e.delete() 171 | } 172 | return nil, false 173 | } 174 | 175 | func (m *Map) Delete(key interface{}) { 176 | m.LoadAndDelete(key) 177 | } 178 | func (e *entry) delete() (value interface{}, ok bool) { 179 | for { 180 | p := atomic.LoadPointer(&e.p) 181 | if p == nil || p == expunged { 182 | return nil, false 183 | } 184 | if atomic.CompareAndSwapPointer(&e.p, p, nil) { //如果read中存在当前key,那么获取e之后调用delete的行为是。将read中的这个e设置为nil 185 | return *(*interface{})(p), true 186 | } 187 | } 188 | } 189 | 190 | /* Analysis: 191 | Delete 方法也是先从 read 操作开始,原因我们已经知道了,因为不需要锁。 192 | 193 | 如果 read 中不存在,那么就需要从 dirty 中寻找这个项目。 194 | 最终,如果项目存在就删除(将它的值标记为 nil)。 195 | 如果项目不为 nil 或者没有被标记为 expunged,那么还可以把它的值返回。 196 | */ 197 | -------------------------------------------------------------------------------- /10-Context/source.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | // 源码地址:src/go/types/context.go 12 | } 13 | 14 | // Context 的具体实现包括 4 个方法,分别是 Deadline、Done、Err 和 Value,如下所示: 15 | type Context interface { 16 | Deadline() (deadline time.Time, ok bool) 17 | Done() <-chan struct{} 18 | Err() error 19 | Value(key interface{}) interface{} 20 | } 21 | 22 | // ================================================================= 23 | 24 | // Context 中实现了 2 个常用的生成顶层 Context 的方法。 25 | // context.Background() --- 可以直接使用 26 | // context.TODO() --- 不知道传什么的时候可以传TODO 27 | // 事实上,它们两个底层的实现是一模一样的: 28 | var ( 29 | background = new(emptyCtx) 30 | todo = new(emptyCtx) 31 | ) 32 | 33 | func Background() Context { 34 | return background 35 | } 36 | 37 | func TODO() Context { 38 | return todo 39 | } 40 | 41 | // ================================================================= 42 | // WithValue 43 | func WithValue(parent Context, key, val interface{}) Context { 44 | if key == nil { 45 | panic("nil key") 46 | } 47 | if !reflect.TypeOf(key).Comparable() { 48 | panic("key is not comparable") 49 | } 50 | return &valueCtx{parent, key, val} 51 | } 52 | 53 | // WithValue 方法其实是创建了一个类型为 valueCtx 的 Context 54 | type valueCtx struct { 55 | Context 56 | key, val interface{} 57 | } 58 | 59 | // 它实现了两个方法: 60 | func (c *valueCtx) String() string { 61 | return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) 62 | } 63 | func (c *valueCtx) Value(key interface{}) interface{} { 64 | if c.key == key { 65 | return c.val 66 | } 67 | return c.Context.Value(key) 68 | } 69 | 70 | /* Analysis: 71 | 对 key 的要求是可比较,因为之后需要通过 key 取出 context 中的值,可比较是必须的。 72 | 通过层层传递 context,最终形成这样一棵树: 73 | 74 | BackgroundContext <- Context[Key1, Val1] <- Context[Key2, Val2] <- Context[Key3, Val3] 75 | ↑ 76 | ⏐ 77 | ⎣__________ Context[Key4, Val4] <- Context[Key5, Val5] 78 | 79 | 和链表有点像,只是它的方向相反:Context 指向它的父节点,链表则指向下一个节点。 80 | 通过 WithValue 函数,可以创建层层的 valueCtx,存储 goroutine 间可以共享的变量。 81 | 82 | 取值的过程,实际上是一个递归查找的过程 83 | 它会顺着链路一直往上找,比较当前节点的 key 是否是要找的 key,如果是, 84 | 则直接返回 value。否则,一直顺着 context 往前,最终找到根节点(一般是 emptyCtx), 85 | 直接返回一个 nil。所以用 Value 方法的时候要判断结果是否为 nil。 86 | 87 | 因为查找方向是往上走的,所以,父节点没法获取子节点存储的值,子节点却可以获取父节点的值。 88 | */ 89 | 90 | // ================================================================= 91 | // WithCancel 92 | 93 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 94 | c := newCancelCtx(parent) 95 | propagateCancel(parent, &c) // 把c朝上传播 96 | return &c, func() { c.cancel(true, Canceled) } 97 | } 98 | 99 | // newCancelCtx returns an initialized cancelCtx. 100 | func newCancelCtx(parent Context) cancelCtx { 101 | return cancelCtx{Context: parent} 102 | } 103 | 104 | /* Analysis: 105 | 这是一个暴露给用户的方法,传入一个父 Context(这通常是一个 background,作为根节点), 106 | 返回新建的 context,新 context 的 done channel 是新建的(前文讲过)。 107 | 当 WithCancel 函数返回的 CancelFunc 被调用或者是父节点的 done channel 被关闭 108 | (父节点的 CancelFunc 被调用),此 context(子节点) 的 done channel 也会被关闭。 109 | 注意传给 WithCancel 方法的参数,前者是 true,也就是说取消的时候,需要将自己从父节点里删除。 110 | 111 | */ 112 | // cancelCtx 被取消时,它的 Err 字段就是下面这个 Canceled 错误: 113 | var Canceled = errors.New("context canceled") 114 | 115 | // ================================================================= 116 | // WithTimeout 117 | // WithTimeout 其实是和 WithDeadline 一样,只不过一个参数是超时时间,一个参数是截止时间。 118 | // 超时时间加上当前时间,其实就是截止时间,因此,WithTimeout 的实现是: 119 | func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 120 | // 当前时间+timeout就是deadline 121 | return WithDeadline(parent, time.Now().Add(timeout)) 122 | } 123 | 124 | // ================================================================= 125 | // WithDeadline 126 | func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 127 | // 如果parent的截止时间更早,直接返回一个cancelCtx即可 128 | if cur, ok := parent.Deadline(); ok && cur.Before(d) { 129 | return WithCancel(parent) 130 | } 131 | c := &timerCtx{ 132 | cancelCtx: newCancelCtx(parent), 133 | deadline: d, 134 | } 135 | propagateCancel(parent, c) // 同cancelCtx的处理逻辑 136 | dur := time.Until(d) 137 | if dur <= 0 { //当前时间已经超过了截止时间,直接cancel 138 | c.cancel(true, DeadlineExceeded) 139 | return c, func() { c.cancel(false, Canceled) } 140 | } 141 | c.mu.Lock() 142 | defer c.mu.Unlock() 143 | if c.err == nil { 144 | // 设置一个定时器,到截止时间后取消 145 | c.timer = time.AfterFunc(dur, func() { 146 | c.cancel(true, DeadlineExceeded) 147 | }) 148 | } 149 | return c, func() { c.cancel(true, Canceled) } 150 | } 151 | 152 | /* 153 | WithDeadline 会返回一个 parent 的副本,并且设置了一个不晚于参数 d 的截止时间, 154 | 类型为 timerCtx(或者是 cancelCtx)。如果它的截止时间晚于 parent 的截止时间, 155 | 那么就以 parent 的截止时间为准,并返回一个类型为 cancelCtx 的 Context, 156 | 因为 parent 的截止时间到了,就会取消这个 cancelCtx。 157 | 如果当前时间已经超过了截止时间,就直接返回一个已经被 cancel 的 timerCtx。 158 | 否则就会启动一个定时器,到截止时间取消这个 timerCtx。 159 | 160 | 综合起来,timerCtx 的 Done 被 Close 掉,主要是由下面的某个事件触发的: 161 | 截止时间到了; 162 | cancel 函数被调用; 163 | parent 的 Done 被 close。 164 | 165 | 和 cancelCtx 一样,WithDeadline(WithTimeout)返回的 cancel 一定要调用, 166 | 并且要尽可能早地被调用,这样才能尽早释放资源,不要单纯地依赖截止时间被动取消 167 | */ 168 | 169 | // ================================================================= 170 | 171 | func (c *cancelCtx) cancel(removeFromParent bool, err error) { 172 | // 必须要传 err 173 | if err == nil { 174 | panic("context: internal error: missing cancel error") 175 | } 176 | c.mu.Lock() 177 | if c.err != nil { 178 | c.mu.Unlock() 179 | return // 已经被其他协程取消 180 | } 181 | // 给 err 字段赋值 182 | c.err = err 183 | // 关闭 channel,通知其他协程 184 | if c.done == nil { 185 | c.done = closedchan 186 | } else { 187 | close(c.done) 188 | } 189 | 190 | // 遍历它的所有子节点 191 | for child := range c.children { 192 | // 递归地取消所有子节点 193 | child.cancel(false, err) 194 | } 195 | // 将子节点置空 196 | c.children = nil 197 | c.mu.Unlock() 198 | 199 | if removeFromParent { 200 | // 从父节点中移除自己 201 | removeChild(c.Context, c) 202 | } 203 | } 204 | 205 | /* 206 | cancel() 方法的功能就是关闭 channel:c.done;递归地取消它的所有子节点; 207 | 从父节点从删除自己。达到的效果是通过关闭 channel,将取消信号传递给了它的所有子节点。 208 | goroutine 接收到取消信号的方式就是 select 语句中的读 c.done 被选中。 209 | */ 210 | 211 | // ================================================================= 212 | func propagateCancel(parent Context, child canceler) { 213 | // 父节点是个空节点 214 | if parent.Done() == nil { 215 | return // parent is never canceled 216 | } 217 | // 找到可以取消的父 context 218 | if p, ok := parentCancelCtx(parent); ok { 219 | p.mu.Lock() 220 | if p.err != nil { 221 | // 父节点已经被取消了,本节点(子节点)也要取消 222 | child.cancel(false, p.err) 223 | } else { 224 | // 父节点未取消 225 | if p.children == nil { 226 | p.children = make(map[canceler]struct{}) 227 | } 228 | // "挂到"父节点上 229 | p.children[child] = struct{}{} 230 | } 231 | p.mu.Unlock() 232 | } else { 233 | // 如果没有找到可取消的父 context。新启动一个协程监控父节点或子节点取消信号 234 | go func() { 235 | select { 236 | case <-parent.Done(): 237 | child.cancel(false, parent.Err()) 238 | case <-child.Done(): 239 | } 240 | }() 241 | } 242 | } 243 | 244 | /* 245 | 这个方法的作用就是向上寻找可以“挂靠”的“可取消”的 context,并且“挂靠”上去。这样, 246 | 调用上层 cancel 方法的时候,就可以层层传递,将那些挂靠的子 context 同时“取消”。 247 | */ 248 | --------------------------------------------------------------------------------