[threading] Fix Fence for multiple waiting threads

This commit is contained in:
Joel Linn 2020-11-06 18:45:32 +01:00 committed by Rick Gibbed
parent d7094fae52
commit 68cf47e245
2 changed files with 43 additions and 12 deletions

View File

@ -38,6 +38,13 @@ TEST_CASE("Fence") {
pFence->Signal();
pFence->Wait();
// Signal and wait two times
pFence = std::make_unique<threading::Fence>();
pFence->Signal();
pFence->Wait();
pFence->Signal();
pFence->Wait();
// Test to synchronize multiple threads
std::atomic<int> started(0);
std::atomic<int> finished(0);
@ -57,15 +64,10 @@ TEST_CASE("Fence") {
});
Sleep(100ms);
REQUIRE(started.load() == threads.size());
REQUIRE(finished.load() == 0);
// TODO(bwrsandman): Check if this is correct behaviour: looping with Sleep
// is the only way to get fence to signal all threads on windows
for (int i = 0; i < threads.size(); ++i) {
Sleep(10ms);
pFence->Signal();
}
REQUIRE(started.load() == threads.size());
pFence->Signal();
for (auto& t : threads) t.join();
REQUIRE(finished.load() == threads.size());

View File

@ -24,27 +24,56 @@
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
namespace xe {
namespace threading {
// This is more like an Event with self-reset when returning from Wait()
class Fence {
public:
Fence() : signaled_(false) {}
Fence() : signal_state_(0) {}
void Signal() {
std::unique_lock<std::mutex> lock(mutex_);
signaled_ = true;
signal_state_ |= SIGMASK_;
cond_.notify_all();
}
// Wait for the Fence to be signaled. Clears the signal on return.
void Wait() {
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this] { return signaled_; });
signaled_ = false;
assert_true((signal_state_ & ~SIGMASK_) < (SIGMASK_ - 1) &&
"Too many threads?");
// keep local copy to minimize loads
auto signal_state = ++signal_state_;
for (; !(signal_state & SIGMASK_); signal_state = signal_state_) {
cond_.wait(lock);
}
// We can't just clear the signal as other threads may not have read it yet
assert_true((signal_state & ~SIGMASK_) > 0); // wait_count > 0
if (signal_state == (1 | SIGMASK_)) { // wait_count == 1
// Last one out turn off the lights
signal_state_ = 0;
} else {
// Oops, another thread is still waiting, set the new count and keep the
// signal.
signal_state_ = --signal_state;
}
}
private:
using state_t_ = uint_fast32_t;
static constexpr state_t_ SIGMASK_ = state_t_(1)
<< (sizeof(state_t_) * 8 - 1);
std::mutex mutex_;
std::condition_variable cond_;
bool signaled_;
// Use the highest bit (sign bit) as the signal flag and the rest to count
// waiting threads.
volatile state_t_ signal_state_;
};
// Returns the total number of logical processors in the host system.