mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Runtime] Remove all use of read/write locks.
Read/write locks are not as good as you'd think; a simple mutex is better in almost all cases. rdar://90776105
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//===--- Mutex.cpp - Mutex and ReadWriteLock Tests ------------------------===//
|
||||
//===--- Mutex.cpp - Mutex Tests ------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
@@ -254,504 +254,3 @@ void scopedReadThreaded(RW &lock) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ScopedReadLockThreaded) {
|
||||
ReadWriteLock lock;
|
||||
scopedReadThreaded<ScopedReadLock, true>(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ScopedReadLockThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
scopedReadThreaded<StaticScopedReadLock, true>(lock);
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ScopedReadUnlockThreaded) {
|
||||
ReadWriteLock lock;
|
||||
scopedReadThreaded<ScopedReadUnlock, false>(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ScopedReadUnlockThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
scopedReadThreaded<StaticScopedReadUnlock, false>(lock);
|
||||
}
|
||||
|
||||
template <typename SWL, bool Locking, typename RW>
|
||||
void scopedWriteLockThreaded(RW &lock) {
|
||||
const int threadCount = 10;
|
||||
|
||||
std::set<int> readerHistory;
|
||||
std::vector<std::set<int>> writerHistory;
|
||||
writerHistory.assign(threadCount, std::set<int>());
|
||||
|
||||
int protectedValue = 0;
|
||||
readerHistory.insert(protectedValue);
|
||||
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
if (Locking) {
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
{
|
||||
SWL guard(lock);
|
||||
protectedValue += index * i;
|
||||
writerHistory[index].insert(protectedValue);
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
} else {
|
||||
lock.writeLock();
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
protectedValue += index * i;
|
||||
writerHistory[index].insert(protectedValue);
|
||||
{
|
||||
SWL unguard(lock);
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
lock.writeUnlock();
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
lock.readLock();
|
||||
readerHistory.insert(protectedValue);
|
||||
lock.readUnlock();
|
||||
}
|
||||
});
|
||||
|
||||
std::set<int> mergedHistory;
|
||||
for (auto &history : writerHistory) {
|
||||
mergedHistory.insert(history.begin(), history.end());
|
||||
}
|
||||
|
||||
for (auto value : readerHistory) {
|
||||
ASSERT_EQ(mergedHistory.count(value), 1U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ScopedWriteLockThreaded) {
|
||||
ReadWriteLock lock;
|
||||
scopedWriteLockThreaded<ScopedWriteLock, true>(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ScopedWriteLockThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
scopedWriteLockThreaded<StaticScopedWriteLock, true>(lock);
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ScopedWriteUnlockThreaded) {
|
||||
ReadWriteLock lock;
|
||||
scopedWriteLockThreaded<ScopedWriteUnlock, false>(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ScopedWriteUnlockThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
scopedWriteLockThreaded<StaticScopedWriteUnlock, false>(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void readLockWhileReadLockedThreaded(RW &lock) {
|
||||
lock.readLock();
|
||||
|
||||
const int threadCount = 10;
|
||||
|
||||
std::atomic<bool> results[threadCount] = {};
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
lock.withReadLock([&] {
|
||||
results[index] = true;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(5));
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
});
|
||||
|
||||
lock.readUnlock();
|
||||
|
||||
for (auto &result : results) {
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ReadLockWhileReadLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
readLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ReadLockWhileReadLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
readLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void readLockWhileWriteLockedThreaded(RW &lock) {
|
||||
lock.writeLock();
|
||||
|
||||
const int threadCount = 10;
|
||||
|
||||
std::atomic<int> results[threadCount] = {};
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
lock.withReadLock([&] {
|
||||
results[index] += 1;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(5));
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
lock.writeUnlock();
|
||||
});
|
||||
|
||||
for (auto &result : results) {
|
||||
ASSERT_EQ(result, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ReadLockWhileWriteLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
readLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ReadLockWhileWriteLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
readLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void writeLockWhileReadLockedThreaded(RW &lock) {
|
||||
lock.readLock();
|
||||
|
||||
const int threadCount = 10;
|
||||
|
||||
std::atomic<int> results[threadCount] = {};
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
lock.withWriteLock([&] {
|
||||
results[index] += 1;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(5));
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
lock.readUnlock();
|
||||
});
|
||||
|
||||
for (auto &result : results) {
|
||||
ASSERT_EQ(result, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, WriteLockWhileReadLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
writeLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, WriteLockWhileReadLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
writeLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void writeLockWhileWriteLockedThreaded(RW &lock) {
|
||||
lock.writeLock();
|
||||
|
||||
const int threadCount = 10;
|
||||
|
||||
std::atomic<int> results[threadCount] = {};
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
lock.withWriteLock([&] {
|
||||
results[index] += 1;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(5));
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
lock.writeUnlock();
|
||||
});
|
||||
|
||||
for (auto &result : results) {
|
||||
ASSERT_EQ(result, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, WriteLockWhileWriteLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
writeLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, WriteLockWhileWriteLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
writeLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void tryReadLockWhileWriteLockedThreaded(RW &lock) {
|
||||
lock.writeLock();
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(10,
|
||||
[&](int) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
ASSERT_FALSE(lock.try_readLock());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
});
|
||||
|
||||
lock.writeUnlock();
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, TryReadLockWhileWriteLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
tryReadLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, TryReadLockWhileWriteLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
tryReadLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void tryReadLockWhileReadLockedThreaded(RW &lock) {
|
||||
lock.readLock();
|
||||
|
||||
const int threadCount = 10;
|
||||
|
||||
std::atomic<bool> results[threadCount] = {};
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(threadCount,
|
||||
[&](int index) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
ASSERT_TRUE(lock.try_readLock());
|
||||
results[index] = true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
lock.readUnlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
});
|
||||
|
||||
lock.readUnlock();
|
||||
|
||||
for (auto &result : results) {
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, TryReadLockWhileReadLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
tryReadLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, TryReadLockWhileReadLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
tryReadLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void tryWriteLockWhileWriteLockedThreaded(RW &lock) {
|
||||
lock.writeLock();
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(10,
|
||||
[&](int) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
ASSERT_FALSE(lock.try_writeLock());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
});
|
||||
|
||||
lock.writeUnlock();
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, TryWriteLockWhileWriteLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
tryWriteLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, TryWriteLockWhileWriteLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
tryWriteLockWhileWriteLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void tryWriteLockWhileReadLockedThreaded(RW &lock) {
|
||||
lock.readLock();
|
||||
|
||||
std::atomic<bool> done(false);
|
||||
threadedExecute(10,
|
||||
[&](int) {
|
||||
// Always perform at least one iteration of this loop to
|
||||
// avoid spurious failures if this thread is slow to run.
|
||||
do {
|
||||
ASSERT_FALSE(lock.try_writeLock());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!done);
|
||||
},
|
||||
[&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
done = true;
|
||||
});
|
||||
|
||||
lock.readUnlock();
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, TryWriteLockWhileReadLockedThreaded) {
|
||||
ReadWriteLock lock;
|
||||
tryWriteLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, TryWriteLockWhileReadLockedThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
tryWriteLockWhileReadLockedThreaded(lock);
|
||||
}
|
||||
|
||||
template <typename RW> void readWriteLockCacheExampleThreaded(RW &lock) {
|
||||
std::map<uint8_t, uint32_t> cache;
|
||||
std::vector<std::thread> workers;
|
||||
std::vector<std::set<uint8_t>> workerHistory;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, UINT8_MAX);
|
||||
|
||||
workerHistory.push_back(std::set<uint8_t>());
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint8_t key = dis(gen);
|
||||
cache[key] = 0;
|
||||
workerHistory[0].insert(key);
|
||||
|
||||
if (trace)
|
||||
printf("WarmUp create for key = %d, value = %d.\n", key, 0);
|
||||
}
|
||||
|
||||
// Block the threads we are about to create.
|
||||
const int threadCount = 20;
|
||||
std::atomic<bool> spinWait(true);
|
||||
std::atomic<int> readyCount(0);
|
||||
|
||||
for (int i = 1; i <= threadCount; ++i) {
|
||||
workerHistory.push_back(std::set<uint8_t>());
|
||||
workers.push_back(std::thread([&, i] {
|
||||
readyCount++;
|
||||
|
||||
// Block ourself until we are released to start working.
|
||||
while (spinWait) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
for (int j = 0; j < 50; j++) {
|
||||
uint8_t key = dis(gen);
|
||||
bool found = false;
|
||||
|
||||
auto cacheLookupSection = [&] {
|
||||
auto value = cache.find(key);
|
||||
if (value == cache.end()) {
|
||||
if (trace)
|
||||
printf("Worker[%d] miss for key = %d.\n", i, key);
|
||||
found = false; // cache miss, need to grab write lock
|
||||
}
|
||||
if (trace)
|
||||
printf("Worker[%d] HIT for key = %d, value = %d.\n", i, key,
|
||||
value->second);
|
||||
found = true; // cache hit, no need to grab write lock
|
||||
};
|
||||
|
||||
lock.withReadLock(cacheLookupSection);
|
||||
if (found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lock.withWriteLock([&] {
|
||||
cacheLookupSection();
|
||||
if (!found) {
|
||||
if (trace)
|
||||
printf("Worker[%d] create for key = %d, value = %d.\n", i, key,
|
||||
i);
|
||||
cache[key] = i;
|
||||
workerHistory[i].insert(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (trace)
|
||||
printf("### Worker[%d] thread exiting.\n", i);
|
||||
}));
|
||||
}
|
||||
|
||||
while (readyCount < threadCount) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Allow our threads to fight for the lock.
|
||||
spinWait = false;
|
||||
|
||||
// Wait until all of our workers threads have finished.
|
||||
for (auto &thread : workers) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
for (auto &entry : cache) {
|
||||
if (trace)
|
||||
printf("### Cache dump key = %d, value = %d.\n", entry.first,
|
||||
entry.second);
|
||||
ASSERT_EQ(workerHistory[entry.second].count(entry.first), 1U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReadWriteLockTest, ReadWriteLockCacheExampleThreaded) {
|
||||
ReadWriteLock lock;
|
||||
readWriteLockCacheExampleThreaded(lock);
|
||||
}
|
||||
|
||||
TEST(StaticReadWriteLockTest, ReadWriteLockCacheExampleThreaded) {
|
||||
static StaticReadWriteLock lock;
|
||||
readWriteLockCacheExampleThreaded(lock);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user