Home | History | Annotate | Download | only in private
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #ifndef SkSemaphore_DEFINED
      9 #define SkSemaphore_DEFINED
     10 
     11 #include "../private/SkOnce.h"
     12 #include "SkTypes.h"
     13 #include <atomic>
     14 
     15 class SkBaseSemaphore {
     16 public:
     17     constexpr SkBaseSemaphore(int count = 0)
     18         : fCount(count), fOSSemaphore(nullptr) {}
     19 
     20     // Increment the counter n times.
     21     // Generally it's better to call signal(n) instead of signal() n times.
     22     void signal(int n = 1);
     23 
     24     // Decrement the counter by 1,
     25     // then if the counter is < 0, sleep this thread until the counter is >= 0.
     26     void wait();
     27 
     28     // If the counter is positive, decrement it by 1 and return true, otherwise return false.
     29     bool try_wait();
     30 
     31     // SkBaseSemaphore has no destructor.  Call this to clean it up.
     32     void cleanup();
     33 
     34 private:
     35     // This implementation follows the general strategy of
     36     //     'A Lightweight Semaphore with Partial Spinning'
     37     // found here
     38     //     http://preshing.com/20150316/semaphores-are-surprisingly-versatile/
     39     // That article (and entire blog) are very much worth reading.
     40     //
     41     // We wrap an OS-provided semaphore with a user-space atomic counter that
     42     // lets us avoid interacting with the OS semaphore unless strictly required:
     43     // moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads.
     44     struct OSSemaphore;
     45 
     46     void osSignal(int n);
     47     void osWait();
     48 
     49     std::atomic<int> fCount;
     50     SkOnce           fOSSemaphoreOnce;
     51     OSSemaphore*     fOSSemaphore;
     52 };
     53 
     54 class SkSemaphore : public SkBaseSemaphore {
     55 public:
     56     using SkBaseSemaphore::SkBaseSemaphore;
     57     ~SkSemaphore() { this->cleanup(); }
     58 };
     59 
     60 inline void SkBaseSemaphore::signal(int n) {
     61     int prev = fCount.fetch_add(n, std::memory_order_release);
     62 
     63     // We only want to call the OS semaphore when our logical count crosses
     64     // from <0 to >=0 (when we need to wake sleeping threads).
     65     //
     66     // This is easiest to think about with specific examples of prev and n.
     67     // If n == 5 and prev == -3, there are 3 threads sleeping and we signal
     68     // SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2.
     69     //
     70     // If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0,
     71     // so we don't call the OS semaphore, leaving the count at (prev + n).
     72     int toSignal = SkTMin(-prev, n);
     73     if (toSignal > 0) {
     74         this->osSignal(toSignal);
     75     }
     76 }
     77 
     78 inline void SkBaseSemaphore::wait() {
     79     // Since this fetches the value before the subtract, zero and below means that there are no
     80     // resources left, so the thread needs to wait.
     81     if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) {
     82         this->osWait();
     83     }
     84 }
     85 
     86 #endif//SkSemaphore_DEFINED
     87