一篇关于Semaphore 的原理与实现

开发 后端
在这个 treap 结构里,从 elem 的视角(其实就是 lock 的 addr)来看,这个结构是个二叉搜索树。从 ticket 的角度来看,整个结构就是一个小顶堆。所以才叫树堆(treap)。

[[399540]]

本文转载自微信公众号「码农桃花源」,作者曹春晖。转载本文请联系码农桃花源公众号。

Semaphore

数据结构

  1. // Go 语言中暴露的 semaphore 实现 
  2. // 具体的用法是提供 sleep 和 wakeup 原语 
  3. // 以使其能够在其它同步原语中的竞争情况下使用 
  4. // 因此这里的 semaphore 和 Linux 中的 futex 目标是一致的 
  5. // 只不过语义上更简单一些 
  6. // 
  7. // 也就是说,不要认为这些是信号量 
  8. // 把这里的东西看作 sleep 和 wakeup 实现的一种方式 
  9. // 每一个 sleep 都会和一个 wakeup 配对 
  10. // 即使在发生 race 时,wakeup 在 sleep 之前时也是如此 
  11. // 
  12. // See Mullender and Cox, ``Semaphores in Plan 9,'' 
  13. // http://swtch.com/semaphore.pdf 
  14.  
  15. // 为 sync.Mutex 准备的异步信号量 
  16.  
  17. // semaRoot 持有一棵 地址各不相同的 sudog(s.elem) 的平衡树 
  18. // 每一个 sudog 都反过来指向(通过 s.waitlink)一个在同一个地址上等待的其它 sudog 们 
  19. // 同一地址的 sudog 的内部列表上的操作时间复杂度都是 O(1)。顶层 semaRoot 列表的扫描 
  20. // 的时间复杂度是 O(log n),n 是被哈希到同一个 semaRoot 的不同地址的总数,每一个地址上都会有一些 goroutine 被阻塞。 
  21. // 访问 golang.org/issue/17953 来查看一个在引入二级列表之前性能较差的程序样例,test/locklinear.go 
  22. // 中有一个复现这个样例的测试 
  23. type semaRoot struct { 
  24.     lock  mutex 
  25.     treap *sudog // root of balanced tree of unique waiters. 
  26.     nwait uint32 // Number of waiters. Read w/o the lock. 
  27.  
  28. // Prime to not correlate with any user patterns. 
  29. const semTabSize = 251 
  30.  
  31. var semtable [semTabSize]struct { 
  32.     root semaRoot 
  33.     pad  [sys.CacheLineSize - unsafe.Sizeof(semaRoot{})]byte 
  34.  
  35. func semroot(addr *uint32) *semaRoot { 
  36.     return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root 
  1. ┌─────┬─────┬─────┬─────┬─────┬────────────────────────┬─────┐                  
  2. │  0  │  1  │  2  │  3  │  4  │         .....          │ 250 │                  
  3. └─────┴─────┴─────┴─────┴─────┴────────────────────────┴─────┘                  
  4.    │                                                      │                     
  5.    │                                                      │                     
  6.    └──┐                                                   └─┐                   
  7.       │                                                     │                   
  8.       │                                                     │                   
  9.       ▼                                                     ▼                   
  10.  ┌─────────┐                                           ┌─────────┐              
  11.  │ struct  │                                           │ struct  │              
  12.  ├─────────┴─────────┐                                 ├─────────┴─────────┐    
  13.  │   root semaRoot   │──┐                              │   root semaRoot   │──┐ 
  14.  ├───────────────────┤  │                              ├───────────────────┤  │ 
  15.  │        pad        │  │                              │        pad        │  │ 
  16.  └───────────────────┘  │                              └───────────────────┘  │ 
  17.                         │                                                     │ 
  18.        ┌────────────────┘                                    ┌────────────────┘ 
  19.        │                                                     │                  
  20.        │                                                     │                  
  21.        ▼                                                     ▼                  
  22.  ┌──────────┐                                          ┌──────────┐             
  23.  │ semaRoot │                                          │ semaRoot │             
  24.  ├──────────┴────────┐                                 ├──────────┴────────┐    
  25.  │    lock mutex     │                                 │    lock mutex     │    
  26.  ├───────────────────┤                                 ├───────────────────┤    
  27.  │   treap *sudog    │                                 │   treap *sudog    │    
  28.  ├───────────────────┤                                 ├───────────────────┤    
  29.  │   nwait uint32    │                                 │   nwait uint32    │    
  30.  └───────────────────┘                                 └───────────────────┘ 

treap 结构:

  1.                                  ┌──────────┐                                     
  2.                             ┌─┬─▶│  sudog   │                                     
  3.                             │ │  ├──────────┴────────────┐                        
  4.       ┌─────────────────────┼─┼──│      prev *sudog      │                        
  5.       │                     │ │  ├───────────────────────┤                        
  6.       │                     │ │  │      next *sudog      │────┐                   
  7.       │                     │ │  ├───────────────────────┤    │                   
  8.       │                     │ │  │     parent *sudog     │    │                   
  9.       │                     │ │  ├───────────────────────┤    │                   
  10.       │                     │ │  │  elem unsafe.Pointer  │    │                   
  11.       │                     │ │  ├───────────────────────┤    │                   
  12.       │                     │ │  │     ticket uint32     │    │                   
  13.       │                     │ │  └───────────────────────┘    │                   
  14.       │                     │ │                               │                   
  15.       │                     │ │                               │                   
  16.       │                     │ │                               │                   
  17.       │                     │ │                               │                   
  18.       │                     │ │                               │                   
  19.       │                     │ │                               │                   
  20.       ▼                     │ │                               ▼                   
  21. ┌──────────┐                │ │                         ┌──────────┐              
  22. │  sudog   │                │ │                         │  sudog   │              
  23. ├──────────┴────────────┐   │ │                         ├──────────┴────────────┐ 
  24. │      prev *sudog      │   │ │                         │      prev *sudog      │ 
  25. ├───────────────────────┤   │ │                         ├───────────────────────┤ 
  26. │      next *sudog      │   │ │                         │      next *sudog      │ 
  27. ├───────────────────────┤   │ │                         ├───────────────────────┤ 
  28. │     parent *sudog     │───┘ └─────────────────────────│     parent *sudog     │ 
  29. ├───────────────────────┤                               ├───────────────────────┤ 
  30. │  elem unsafe.Pointer  │                               │  elem unsafe.Pointer  │ 
  31. ├───────────────────────┤                               ├───────────────────────┤ 
  32. │     ticket uint32     │                               │     ticket uint32     │ 
  33. └───────────────────────┘                               └───────────────────────┘ 

在这个 treap 结构里,从 elem 的视角(其实就是 lock 的 addr)来看,这个结构是个二叉搜索树。从 ticket 的角度来看,整个结构就是一个小顶堆。

所以才叫树堆(treap)。

相同 addr,即对同一个 mutex 上锁的 g,会阻塞在同一个地址上。这些阻塞在同一个地址上的 goroutine 会被打包成 sudog,组成一个链表。用 sudog 的 waitlink 相连:

  1. ┌──────────┐                         ┌──────────┐                          ┌──────────┐              
  2. │  sudog   │                  ┌─────▶│  sudog   │                   ┌─────▶│  sudog   │              
  3. ├──────────┴────────────┐     │      ├──────────┴────────────┐      │      ├──────────┴────────────┐ 
  4. │    waitlink *sudog    │─────┘      │    waitlink *sudog    │──────┘      │    waitlink *sudog    │ 
  5. ├───────────────────────┤            ├───────────────────────┤             ├───────────────────────┤ 
  6. │    waittail *sudog    │            │    waittail *sudog    │             │    waittail *sudog    │ 
  7. └───────────────────────┘            └───────────────────────┘             └───────────────────────┘ 

中间的元素的 waittail 都会指向最后一个元素:

  1. ┌──────────┐                                                                                            
  2. │  sudog   │                                                                                            
  3. ├──────────┴────────────┐                                                                               
  4. │    waitlink *sudog    │                                                                               
  5. ├───────────────────────┤                                                                               
  6. │    waittail *sudog    │───────────────────────────────────────────────────────────┐                   
  7. └───────────────────────┘                                                           │                   
  8.                                   ┌──────────┐                                      │                   
  9.                                   │  sudog   │                                      │                   
  10.                                   ├──────────┴────────────┐                         │                   
  11.                                   │    waitlink *sudog    │                         │                   
  12.                                   ├───────────────────────┤                         │                   
  13.                                   │    waittail *sudog    │─────────────────────────┤                   
  14.                                   └───────────────────────┘                         ▼                   
  15.                                                                               ┌──────────┐              
  16.                                                                               │  sudog   │              
  17.                                                                               ├──────────┴────────────┐ 
  18.                                                                               │    waitlink *sudog    │ 
  19.                                                                               ├───────────────────────┤ 
  20.                                                                               │    waittail *sudog    │ 
  21.                                                                               └───────────────────────┘ 

对外封装

在 sema.go 里实现的内容,用 go:linkname 导出给 sync、poll 库来使用,也是在链接期做了些手脚:

  1. //go:linkname sync_runtime_Semacquire sync.runtime_Semacquire 
  2. func sync_runtime_Semacquire(addr *uint32) { 
  3.     semacquire1(addr, false, semaBlockProfile) 
  4.  
  5. //go:linkname poll_runtime_Semacquire internal/poll.runtime_Semacquire 
  6. func poll_runtime_Semacquire(addr *uint32) { 
  7.     semacquire1(addr, false, semaBlockProfile) 
  8.  
  9. //go:linkname sync_runtime_Semrelease sync.runtime_Semrelease 
  10. func sync_runtime_Semrelease(addr *uint32, handoff bool) { 
  11.     semrelease1(addr, handoff) 
  12.  
  13. //go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex 
  14. func sync_runtime_SemacquireMutex(addr *uint32, lifo bool) { 
  15.     semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile) 
  16.  
  17. //go:linkname poll_runtime_Semrelease internal/poll.runtime_Semrelease 
  18. func poll_runtime_Semrelease(addr *uint32) { 
  19.     semrelease(addr) 

实现

sem 本身支持 acquire 和 release,其实就是 OS 里常说的 P 操作和 V 操作。

公共部分

  1. func cansemacquire(addr *uint32) bool { 
  2.     for { 
  3.         v := atomic.Load(addr) 
  4.         if v == 0 { 
  5.             return false 
  6.         } 
  7.         if atomic.Cas(addr, v, v-1) { 
  8.             return true 
  9.         } 
  10.     } 

acquire 过程

  1. type semaProfileFlags int 
  2.  
  3. const ( 
  4.     semaBlockProfile semaProfileFlags = 1 << iota 
  5.     semaMutexProfile 
  6.  
  7. // Called from runtime. 
  8. func semacquire(addr *uint32) { 
  9.     semacquire1(addr, false, 0) 
  10.  
  11. func semacquire1(addr *uint32, lifo bool, profile semaProfileFlags) { 
  12.     gp := getg() 
  13.     if gp != gp.m.curg { 
  14.         throw("semacquire not on the G stack"
  15.     } 
  16.  
  17.     // 低成本的情况 
  18.     if cansemacquire(addr) { 
  19.         return 
  20.     } 
  21.  
  22.     // 高成本的情况: 
  23.     //    增加 waiter count 的值 
  24.     //    再尝试调用一次 cansemacquire,成本了就直接返回 
  25.     //    没成功就把自己作为一个 waiter 入队 
  26.     //    sleep 
  27.     //    (之后 waiter 的 descriptor 被 signaler 用 dequeue 踢出) 
  28.     s := acquireSudog() 
  29.     root := semroot(addr) 
  30.     t0 := int64(0) 
  31.     s.releasetime = 0 
  32.     s.acquiretime = 0 
  33.     s.ticket = 0 
  34.  
  35.     for { 
  36.         lock(&root.lock) 
  37.         // 给 nwait 加一,这样后来的就不会在 semrelease 中进低成本的路径了 
  38.         atomic.Xadd(&root.nwait, 1) 
  39.         // 检查 cansemacquire 避免错过了唤醒 
  40.         if cansemacquire(addr) { 
  41.             atomic.Xadd(&root.nwait, -1) 
  42.             unlock(&root.lock) 
  43.             break 
  44.         } 
  45.         // 在 cansemacquire 之后的 semrelease 都可以知道我们正在等待 
  46.         // (上面设置了 nwait),所以会直接进入 sleep 
  47.         // 注: 这里说的 sleep 其实就是 goparkunlock 
  48.         root.queue(addr, s, lifo) 
  49.         goparkunlock(&root.lock, "semacquire", traceEvGoBlockSync, 4) 
  50.         if s.ticket != 0 || cansemacquire(addr) { 
  51.             break 
  52.         } 
  53.     } 
  54.     if s.releasetime > 0 { 
  55.         blockevent(s.releasetime-t0, 3) 
  56.     } 
  57.     releaseSudog(s) 

release 过程

  1. func semrelease(addr *uint32) { 
  2.     semrelease1(addr, false
  3.  
  4. func semrelease1(addr *uint32, handoff bool) { 
  5.     root := semroot(addr) 
  6.     atomic.Xadd(addr, 1) 
  7.  
  8.     // 低成本情况: 没有 waiter? 
  9.     // 这个 atomic 的检查必须发生在 xadd 之前,以避免错误唤醒 
  10.     // (具体参见 semacquire 中的循环) 
  11.     if atomic.Load(&root.nwait) == 0 { 
  12.         return 
  13.     } 
  14.  
  15.     // 高成本情况: 搜索 waiter 并唤醒它 
  16.     lock(&root.lock) 
  17.     if atomic.Load(&root.nwait) == 0 { 
  18.         // count 值已经被另一个 goroutine 消费了 
  19.         // 所以我们不需要唤醒其它 goroutine 了 
  20.         unlock(&root.lock) 
  21.         return 
  22.     } 
  23.     s, t0 := root.dequeue(addr) 
  24.     if s != nil { 
  25.         atomic.Xadd(&root.nwait, -1) 
  26.     } 
  27.     unlock(&root.lock) 
  28.     if s != nil { // 可能会很慢,所以先解锁 
  29.         acquiretime := s.acquiretime 
  30.         if acquiretime != 0 { 
  31.             mutexevent(t0-acquiretime, 3) 
  32.         } 
  33.         if s.ticket != 0 { 
  34.             throw("corrupted semaphore ticket"
  35.         } 
  36.         if handoff && cansemacquire(addr) { 
  37.             s.ticket = 1 
  38.         } 
  39.         readyWithTime(s, 5) 
  40.     } 
  41.  
  42. func readyWithTime(s *sudog, traceskip int) { 
  43.     if s.releasetime != 0 { 
  44.         s.releasetime = cputicks() 
  45.     } 
  46.     goready(s.g, traceskip) 

treap 结构

sudog 按照地址 hash 到 251 个 bucket 中的其中一个,每一个 bucket 都是一棵 treap。而相同 addr 上的 sudog 会形成一个链表。

为啥同一个地址的 sudog 不需要展开放在 treap 中呢?显然,sudog 唤醒的时候,block 在同一个 addr 上的 goroutine,说明都是加的同一把锁,这些 goroutine 被唤醒肯定是一起被唤醒的,相同地址的 g 并不需要查找才能找到,只要决定是先进队列的被唤醒(fifo)还是后进队列的被唤醒(lifo)就可以了。

  1. // queue 函数会把 s 添加到 semaRoot 上阻塞的 goroutine 们中 
  2. // 实际上就是把 s 添加到其地址对应的 treap 上 
  3. func (root *semaRoot) queue(addr *uint32, s *sudog, lifo bool) { 
  4.     s.g = getg() 
  5.     s.elem = unsafe.Pointer(addr) 
  6.     s.next = nil 
  7.     s.prev = nil 
  8.  
  9.     var last *sudog 
  10.     pt := &root.treap 
  11.     for t := *pt; t != nil; t = *pt { 
  12.         if t.elem == unsafe.Pointer(addr) { 
  13.             // Already have addr in list. 
  14.             if lifo { 
  15.                 // treap 中在 t 的位置用 s 覆盖掉 t 
  16.                 *pt = s 
  17.                 s.ticket = t.ticket 
  18.                 s.acquiretime = t.acquiretime 
  19.                 s.parent = t.parent 
  20.                 s.prev = t.prev 
  21.                 s.next = t.next 
  22.                 if s.prev != nil { 
  23.                     s.prev.parent = s 
  24.                 } 
  25.                 if s.next != nil { 
  26.                     s.next.parent = s 
  27.                 } 
  28.                 // 把 t 放在 s 的 wait list 的第一个位置 
  29.                 s.waitlink = t 
  30.                 s.waittail = t.waittail 
  31.                 if s.waittail == nil { 
  32.                     s.waittail = t 
  33.                 } 
  34.                 t.parent = nil 
  35.                 t.prev = nil 
  36.                 t.next = nil 
  37.                 t.waittail = nil 
  38.             } else { 
  39.                 // 把 s 添加到 t 的等待列表的末尾 
  40.                 if t.waittail == nil { 
  41.                     t.waitlink = s 
  42.                 } else { 
  43.                     t.waittail.waitlink = s 
  44.                 } 
  45.                 t.waittail = s 
  46.                 s.waitlink = nil 
  47.             } 
  48.             return 
  49.         } 
  50.         last = t 
  51.         if uintptr(unsafe.Pointer(addr)) < uintptr(t.elem) { 
  52.             pt = &t.prev 
  53.         } else { 
  54.             pt = &t.next 
  55.         } 
  56.     } 
  57.  
  58.     // 把 s 作为树的新的叶子插入进去 
  59.     // 平衡树使用 ticket 作为堆的权重值,这个 ticket 是随机生成的 
  60.     // 也就是说,这个结构以元素地址来看的话,是一个二叉搜索树 
  61.     // 同时用 ticket 值使其同时又是一个小顶堆,满足 
  62.     // s.ticket <= both s.prev.ticket and s.next.ticket. 
  63.     // https://en.wikipedia.org/wiki/Treap 
  64.     // http://faculty.washington.edu/aragon/pubs/rst89.pdf 
  65.     // 
  66.     // s.ticket 会在一些地方和 0 相比,因此只设置最低位的 bit 
  67.     // 这样不会明显地影响 treap 的质量? 
  68.     s.ticket = fastrand() | 1 
  69.     s.parent = last 
  70.     *pt = s 
  71.  
  72.     // 按照 ticket 来进行旋转,以满足 treap 的性质 
  73.     for s.parent != nil && s.parent.ticket > s.ticket { 
  74.         if s.parent.prev == s { 
  75.             root.rotateRight(s.parent) 
  76.         } else { 
  77.             if s.parent.next != s { 
  78.                 panic("semaRoot queue"
  79.             } 
  80.             root.rotateLeft(s.parent) 
  81.         } 
  82.     } 
  83.  
  84. // dequeue 会搜索到阻塞在 addr 地址的 semaRoot 中的第一个 goroutine 
  85. // 如果这个 sudog 需要进行 profile,dequeue 会返回它被唤醒的时间(now),否则的话 now 为 0 
  86. func (root *semaRoot) dequeue(addr *uint32) (found *sudog, now int64) { 
  87.     ps := &root.treap 
  88.     s := *ps 
  89.     for ; s != nil; s = *ps { 
  90.         if s.elem == unsafe.Pointer(addr) { 
  91.             goto Found 
  92.         } 
  93.         if uintptr(unsafe.Pointer(addr)) < uintptr(s.elem) { 
  94.             ps = &s.prev 
  95.         } else { 
  96.             ps = &s.next 
  97.         } 
  98.     } 
  99.     return nil, 0 
  100.  
  101. Found: 
  102.     now = int64(0) 
  103.     if s.acquiretime != 0 { 
  104.         now = cputicks() 
  105.     } 
  106.     if t := s.waitlink; t != nil { 
  107.         // 替换掉同样在 addr 上等待的 t。 
  108.         *ps = t 
  109.         t.ticket = s.ticket 
  110.         t.parent = s.parent 
  111.         t.prev = s.prev 
  112.         if t.prev != nil { 
  113.             t.prev.parent = t 
  114.         } 
  115.         t.next = s.next 
  116.         if t.next != nil { 
  117.             t.next.parent = t 
  118.         } 
  119.         if t.waitlink != nil { 
  120.             t.waittail = s.waittail 
  121.         } else { 
  122.             t.waittail = nil 
  123.         } 
  124.         t.acquiretime = now 
  125.         s.waitlink = nil 
  126.         s.waittail = nil 
  127.     } else { 
  128.         // 向下旋转 s 到叶节点,以进行删除,同时要考虑优先级 
  129.         for s.next != nil || s.prev != nil { 
  130.             if s.next == nil || s.prev != nil && s.prev.ticket < s.next.ticket { 
  131.                 root.rotateRight(s) 
  132.             } else { 
  133.                 root.rotateLeft(s) 
  134.             } 
  135.         } 
  136.         // Remove s, now a leaf. 
  137.         // 删除 s,现在是叶子节点了 
  138.         if s.parent != nil { 
  139.             if s.parent.prev == s { 
  140.                 s.parent.prev = nil 
  141.             } else { 
  142.                 s.parent.next = nil 
  143.             } 
  144.         } else { 
  145.             root.treap = nil 
  146.         } 
  147.     } 
  148.     s.parent = nil 
  149.     s.elem = nil 
  150.     s.next = nil 
  151.     s.prev = nil 
  152.     s.ticket = 0 
  153.     return s, now 

 

责任编辑:武晓燕 来源: 码农桃花源
相关推荐

2021-09-05 07:55:36

Lsm核心实现

2021-04-08 11:00:56

CountDownLaJava进阶开发

2022-04-08 08:32:40

mobx状态管理库redux

2021-09-15 19:05:16

数据开源项目

2022-05-25 08:31:31

ArthasInstrument

2021-10-11 11:08:33

HDFS快照系统

2019-04-01 08:15:21

Java线程池多核处理器

2021-08-27 07:47:06

SQL静态程序

2022-05-08 19:58:10

JSONPJavaScript

2019-07-02 05:02:56

NVMe接口存储

2021-08-11 07:02:21

npm包管理器工具

2022-07-14 14:18:59

Kubernetes架构

2022-02-18 08:54:21

docker操作系统Linux

2021-07-12 10:36:36

Blazor组件入门

2022-11-08 10:52:25

Flowable节点表单

2021-12-04 22:05:02

Linux

2022-05-17 08:02:55

GoTryLock模式

2021-09-08 09:22:34

SentryCLIOS

2021-04-18 18:03:06

工作树远程版本

2018-12-27 09:28:08

Consul服务Server
点赞
收藏

51CTO技术栈公众号