Skip to content

Commit 7f13543

Browse files
JoelLinnbwrsandman
authored andcommitted
[threading] Fix Fence for multiple waiting threads
1 parent 97b2ee6 commit 7f13543

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/xenia/base/testing/threading_test.cc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ TEST_CASE("Fence") {
3838
pFence->Signal();
3939
pFence->Wait();
4040

41+
// Signal and wait two times
42+
pFence = std::make_unique<threading::Fence>();
43+
pFence->Signal();
44+
pFence->Wait();
45+
pFence->Signal();
46+
pFence->Wait();
47+
4148
// Test to synchronize multiple threads
4249
std::atomic<int> started(0);
4350
std::atomic<int> finished(0);
@@ -57,15 +64,10 @@ TEST_CASE("Fence") {
5764
});
5865

5966
Sleep(100ms);
67+
REQUIRE(started.load() == threads.size());
6068
REQUIRE(finished.load() == 0);
6169

62-
// TODO(bwrsandman): Check if this is correct behaviour: looping with Sleep
63-
// is the only way to get fence to signal all threads on windows
64-
for (int i = 0; i < threads.size(); ++i) {
65-
Sleep(10ms);
66-
pFence->Signal();
67-
}
68-
REQUIRE(started.load() == threads.size());
70+
pFence->Signal();
6971

7072
for (auto& t : threads) t.join();
7173
REQUIRE(finished.load() == threads.size());

src/xenia/base/threading.h

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,56 @@
2424
#include <utility>
2525
#include <vector>
2626

27+
#include "xenia/base/assert.h"
28+
2729
namespace xe {
2830
namespace threading {
2931

32+
// This is more like an Event with self-reset when returning from Wait()
3033
class Fence {
3134
public:
32-
Fence() : signaled_(false) {}
35+
Fence() : signal_state_(0) {}
36+
3337
void Signal() {
3438
std::unique_lock<std::mutex> lock(mutex_);
35-
signaled_ = true;
39+
signal_state_ |= SIGMASK_;
3640
cond_.notify_all();
3741
}
42+
43+
// Wait for the Fence to be signaled. Clears the signal on return.
3844
void Wait() {
3945
std::unique_lock<std::mutex> lock(mutex_);
40-
cond_.wait(lock, [this] { return signaled_; });
41-
signaled_ = false;
46+
assert_true((signal_state_ & ~SIGMASK_) < (SIGMASK_ - 1) &&
47+
"Too many threads?");
48+
49+
// keep local copy to minimize loads
50+
auto signal_state = ++signal_state_;
51+
for (; !(signal_state & SIGMASK_); signal_state = signal_state_) {
52+
cond_.wait(lock);
53+
}
54+
55+
// We can't just clear the signal as other threads may not have read it yet
56+
assert_true((signal_state & ~SIGMASK_) > 0); // wait_count > 0
57+
if (signal_state == (1 | SIGMASK_)) { // wait_count == 1
58+
// Last one out turn off the lights
59+
signal_state_ = 0;
60+
} else {
61+
// Oops, another thread is still waiting, set the new count and keep the
62+
// signal.
63+
signal_state_ = --signal_state;
64+
}
4265
}
4366

4467
private:
68+
using state_t_ = uint_fast32_t;
69+
static constexpr state_t_ SIGMASK_ = state_t_(1)
70+
<< (sizeof(state_t_) * 8 - 1);
71+
4572
std::mutex mutex_;
4673
std::condition_variable cond_;
47-
bool signaled_;
74+
// Use the highest bit (sign bit) as the signal flag and the rest to count
75+
// waiting threads.
76+
volatile state_t_ signal_state_;
4877
};
4978

5079
// Returns the total number of logical processors in the host system.

0 commit comments

Comments
 (0)