自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇關(guān)于Semaphore 的原理與實現(xiàn)

開發(fā) 后端
在這個 treap 結(jié)構(gòu)里,從 elem 的視角(其實就是 lock 的 addr)來看,這個結(jié)構(gòu)是個二叉搜索樹。從 ticket 的角度來看,整個結(jié)構(gòu)就是一個小頂堆。所以才叫樹堆(treap)。

[[399540]]

本文轉(zhuǎn)載自微信公眾號「碼農(nóng)桃花源」,作者曹春暉。轉(zhuǎn)載本文請聯(lián)系碼農(nóng)桃花源公眾號。

Semaphore

數(shù)據(jù)結(jié)構(gòu)

  1. // Go 語言中暴露的 semaphore 實現(xiàn) 
  2. // 具體的用法是提供 sleep 和 wakeup 原語 
  3. // 以使其能夠在其它同步原語中的競爭情況下使用 
  4. // 因此這里的 semaphore 和 Linux 中的 futex 目標(biāo)是一致的 
  5. // 只不過語義上更簡單一些 
  6. // 
  7. // 也就是說,不要認(rèn)為這些是信號量 
  8. // 把這里的東西看作 sleep 和 wakeup 實現(xiàn)的一種方式 
  9. // 每一個 sleep 都會和一個 wakeup 配對 
  10. // 即使在發(fā)生 race 時,wakeup 在 sleep 之前時也是如此 
  11. // 
  12. // See Mullender and Cox, ``Semaphores in Plan 9,'' 
  13. // http://swtch.com/semaphore.pdf 
  14.  
  15. // 為 sync.Mutex 準(zhǔn)備的異步信號量 
  16.  
  17. // semaRoot 持有一棵 地址各不相同的 sudog(s.elem) 的平衡樹 
  18. // 每一個 sudog 都反過來指向(通過 s.waitlink)一個在同一個地址上等待的其它 sudog 們 
  19. // 同一地址的 sudog 的內(nèi)部列表上的操作時間復(fù)雜度都是 O(1)。頂層 semaRoot 列表的掃描 
  20. // 的時間復(fù)雜度是 O(log n),n 是被哈希到同一個 semaRoot 的不同地址的總數(shù),每一個地址上都會有一些 goroutine 被阻塞。 
  21. // 訪問 golang.org/issue/17953 來查看一個在引入二級列表之前性能較差的程序樣例,test/locklinear.go 
  22. // 中有一個復(fù)現(xiàn)這個樣例的測試 
  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 結(jié)構(gòu):

  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 結(jié)構(gòu)里,從 elem 的視角(其實就是 lock 的 addr)來看,這個結(jié)構(gòu)是個二叉搜索樹。從 ticket 的角度來看,整個結(jié)構(gòu)就是一個小頂堆。

所以才叫樹堆(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 里實現(xiàn)的內(nèi)容,用 go:linkname 導(dǎo)出給 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) 

實現(xiàn)

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.     //    再嘗試調(diào)用一次 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 中進(jìn)低成本的路徑了 
  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.         // (上面設(shè)置了 nwait),所以會直接進(jìn)入 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 的檢查必須發(fā)生在 xadd 之前,以避免錯誤喚醒 
  10.     // (具體參見 semacquire 中的循環(huán)) 
  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 值已經(jīng)被另一個 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 結(jié)構(gòu)

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

為啥同一個地址的 sudog 不需要展開放在 treap 中呢?顯然,sudog 喚醒的時候,block 在同一個 addr 上的 goroutine,說明都是加的同一把鎖,這些 goroutine 被喚醒肯定是一起被喚醒的,相同地址的 g 并不需要查找才能找到,只要決定是先進(jìn)隊列的被喚醒(fifo)還是后進(jìn)隊列的被喚醒(lifo)就可以了。

  1. // queue 函數(shù)會把 s 添加到 semaRoot 上阻塞的 goroutine 們中 
  2. // 實際上就是把 s 添加到其地址對應(yīng)的 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 作為樹的新的葉子插入進(jìn)去 
  59.     // 平衡樹使用 ticket 作為堆的權(quán)重值,這個 ticket 是隨機(jī)生成的 
  60.     // 也就是說,這個結(jié)構(gòu)以元素地址來看的話,是一個二叉搜索樹 
  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 相比,因此只設(shè)置最低位的 bit 
  67.     // 這樣不會明顯地影響 treap 的質(zhì)量? 
  68.     s.ticket = fastrand() | 1 
  69.     s.parent = last 
  70.     *pt = s 
  71.  
  72.     // 按照 ticket 來進(jìn)行旋轉(zhuǎn),以滿足 treap 的性質(zhì) 
  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 需要進(jìn)行 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.         // 向下旋轉(zhuǎn) s 到葉節(jié)點,以進(jìn)行刪除,同時要考慮優(yōu)先級 
  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,現(xiàn)在是葉子節(jié)點了 
  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 

 

責(zé)任編輯:武曉燕 來源: 碼農(nóng)桃花源
相關(guān)推薦

2021-09-05 07:55:36

Lsm核心實現(xiàn)

2021-04-08 11:00:56

CountDownLaJava進(jìn)階開發(fā)

2022-04-08 08:32:40

mobx狀態(tài)管理庫redux

2021-09-15 19:05:16

數(shù)據(jù)開源項目

2022-05-25 08:31:31

ArthasInstrument

2021-10-11 11:08:33

HDFS快照系統(tǒng)

2021-08-11 07:02:21

npm包管理器工具

2019-07-02 05:02:56

NVMe接口存儲

2019-04-01 08:15:21

Java線程池多核處理器

2022-07-14 14:18:59

Kubernetes架構(gòu)

2022-05-08 19:58:10

JSONPJavaScript

2021-08-27 07:47:06

SQL靜態(tài)程序

2022-02-18 08:54:21

docker操作系統(tǒng)Linux

2021-07-12 10:36:36

Blazor組件入門

2022-11-08 10:52:25

Flowable節(jié)點表單

2022-04-02 09:38:00

CSS3flex布局方式

2022-05-17 08:02:55

GoTryLock模式

2021-12-04 22:05:02

Linux

2021-09-08 09:22:34

SentryCLIOS

2021-04-18 18:03:06

工作樹遠(yuǎn)程版本
點贊
收藏

51CTO技術(shù)棧公眾號