skia2/include/private/SkSemaphore.h
Mike Klein a1991f5e3b experimental support for go/fibers
When run on cooperative threads, we need to notify the threading system
when we're going to block (in any way, sleep or spin) so that it doesn't
wake that thread up expecting it to make forward progress.

This should cover SkOnce, SkSpinlock, SkSemaphore, and by extension
SkMutex.  Not sure if there are others to hit, but really the only way
to find out is by deadlocked stack traces.  This CL (obviously?) does
nothing to mark synchronization primitives used by Skia's dependencies,
and in general I don't think we can do anything about them.

See cr/275261423 for more background and discussion.  It's not clear to
me that marking these symbols as weak is necessary, so I figured I'd
just try not doing that and seeing what breaks.  I can always follow up
with weak symbols if proven necessary.

Bug: skia:9577

Change-Id: I2c03fe92c58ad506dd8a68bdc90a09b28f965149
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/251221
Commit-Queue: Mike Klein <mtklein@google.com>
Auto-Submit: Mike Klein <mtklein@google.com>
Reviewed-by: Herb Derby <herb@google.com>
2019-10-28 16:48:55 +00:00

83 lines
2.7 KiB
C++

/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSemaphore_DEFINED
#define SkSemaphore_DEFINED
#include "include/core/SkTypes.h"
#include "include/private/SkOnce.h"
#include "include/private/SkThreadAnnotations.h"
#include <atomic>
class SkSemaphore {
public:
constexpr SkSemaphore(int count = 0) : fCount(count), fOSSemaphore(nullptr) {}
// Cleanup the underlying OS semaphore.
~SkSemaphore();
// Increment the counter n times.
// Generally it's better to call signal(n) instead of signal() n times.
void signal(int n = 1);
// Decrement the counter by 1,
// then if the counter is < 0, sleep this thread until the counter is >= 0.
void wait();
// If the counter is positive, decrement it by 1 and return true, otherwise return false.
bool try_wait();
private:
// This implementation follows the general strategy of
// 'A Lightweight Semaphore with Partial Spinning'
// found here
// http://preshing.com/20150316/semaphores-are-surprisingly-versatile/
// That article (and entire blog) are very much worth reading.
//
// We wrap an OS-provided semaphore with a user-space atomic counter that
// lets us avoid interacting with the OS semaphore unless strictly required:
// moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads.
struct OSSemaphore;
void osSignal(int n);
void osWait();
std::atomic<int> fCount;
SkOnce fOSSemaphoreOnce;
OSSemaphore* fOSSemaphore;
};
inline void SkSemaphore::signal(int n) {
int prev = fCount.fetch_add(n, std::memory_order_release);
// We only want to call the OS semaphore when our logical count crosses
// from <0 to >=0 (when we need to wake sleeping threads).
//
// This is easiest to think about with specific examples of prev and n.
// If n == 5 and prev == -3, there are 3 threads sleeping and we signal
// SkTMin(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2.
//
// If prev >= 0, no threads are waiting, SkTMin(-prev, n) is always <= 0,
// so we don't call the OS semaphore, leaving the count at (prev + n).
int toSignal = SkTMin(-prev, n);
if (toSignal > 0) {
this->osSignal(toSignal);
}
}
inline void SkSemaphore::wait() {
// Since this fetches the value before the subtract, zero and below means that there are no
// resources left, so the thread needs to wait.
if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) {
SK_POTENTIALLY_BLOCKING_REGION_BEGIN;
this->osWait();
SK_POTENTIALLY_BLOCKING_REGION_END;
}
}
#endif//SkSemaphore_DEFINED