326 lines
13 KiB
C++
326 lines
13 KiB
C++
|
|
/**
|
||
|
|
* @file test_concurrent.cpp
|
||
|
|
* @brief 无锁并发数据结构测试 — LockFreeQueue, LockFreeStack,
|
||
|
|
* ConcurrentHashMap, ReadWriteSpinLock
|
||
|
|
*
|
||
|
|
* 测试项 (10项):
|
||
|
|
* 1. LockFreeQueue push/pop (单线程)
|
||
|
|
* 2. LockFreeQueue 空/满检测
|
||
|
|
* 3. LockFreeQueue 多生产者-多消费者
|
||
|
|
* 4. LockFreeStack push/pop (单线程)
|
||
|
|
* 5. LockFreeStack 清空
|
||
|
|
* 6. LockFreeStack 多线程
|
||
|
|
* 7. ConcurrentHashMap insert/find/erase
|
||
|
|
* 8. ConcurrentHashMap 多线程并发
|
||
|
|
* 9. ReadWriteSpinLock 基本锁
|
||
|
|
* 10. ReadWriteSpinLock 多读者+单写者
|
||
|
|
*/
|
||
|
|
#include <gtest/gtest.h>
|
||
|
|
#include "vde/core/concurrent_data.h"
|
||
|
|
#include <thread>
|
||
|
|
#include <vector>
|
||
|
|
#include <algorithm>
|
||
|
|
|
||
|
|
using namespace vde::core;
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 1: LockFreeQueue push/pop (单线程)
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeQueueTest, SingleThreadPushPop) {
|
||
|
|
LockFreeQueue<int> q(16);
|
||
|
|
EXPECT_TRUE(q.empty());
|
||
|
|
|
||
|
|
for (int i = 0; i < 10; ++i) {
|
||
|
|
EXPECT_TRUE(q.try_push(i));
|
||
|
|
}
|
||
|
|
EXPECT_FALSE(q.empty());
|
||
|
|
|
||
|
|
for (int i = 0; i < 10; ++i) {
|
||
|
|
int val = -1;
|
||
|
|
EXPECT_TRUE(q.try_pop(val));
|
||
|
|
EXPECT_EQ(val, i);
|
||
|
|
}
|
||
|
|
EXPECT_TRUE(q.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 2: LockFreeQueue 空/满检测
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeQueueTest, FullAndEmptyDetection) {
|
||
|
|
LockFreeQueue<int> q(8); // 自动向上取整到 8
|
||
|
|
EXPECT_TRUE(q.empty());
|
||
|
|
EXPECT_EQ(q.capacity(), 8u);
|
||
|
|
|
||
|
|
// 填满 (capacity-1 个元素,因为一个 slot 要用于区分空/满)
|
||
|
|
int pushed = 0;
|
||
|
|
for (int i = 0; i < 10; ++i) {
|
||
|
|
if (!q.try_push(i)) break;
|
||
|
|
++pushed;
|
||
|
|
}
|
||
|
|
EXPECT_GT(pushed, 0);
|
||
|
|
EXPECT_LE(pushed, 8);
|
||
|
|
EXPECT_FALSE(q.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 3: LockFreeQueue 多生产者-多消费者
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeQueueTest, MultiProducerMultiConsumer) {
|
||
|
|
constexpr int NUM_PRODUCERS = 4;
|
||
|
|
constexpr int NUM_CONSUMERS = 4;
|
||
|
|
constexpr int ITEMS_PER_PRODUCER = 250;
|
||
|
|
constexpr int TOTAL = NUM_PRODUCERS * ITEMS_PER_PRODUCER;
|
||
|
|
|
||
|
|
LockFreeQueue<int> q(1024);
|
||
|
|
std::atomic<int> produced{0};
|
||
|
|
std::atomic<int> consumed{0};
|
||
|
|
std::atomic<int> sum_produced{0};
|
||
|
|
std::atomic<int> sum_consumed{0};
|
||
|
|
|
||
|
|
// 生产者
|
||
|
|
std::vector<std::thread> producers;
|
||
|
|
for (int p = 0; p < NUM_PRODUCERS; ++p) {
|
||
|
|
producers.emplace_back([&, p]() {
|
||
|
|
for (int i = 0; i < ITEMS_PER_PRODUCER; ++i) {
|
||
|
|
int val = p * 1000 + i;
|
||
|
|
while (!q.try_push(val)) {
|
||
|
|
std::this_thread::yield();
|
||
|
|
}
|
||
|
|
sum_produced.fetch_add(val);
|
||
|
|
produced.fetch_add(1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 消费者
|
||
|
|
std::vector<std::thread> consumers;
|
||
|
|
for (int c = 0; c < NUM_CONSUMERS; ++c) {
|
||
|
|
consumers.emplace_back([&]() {
|
||
|
|
int val;
|
||
|
|
while (consumed.load() < TOTAL) {
|
||
|
|
if (q.try_pop(val)) {
|
||
|
|
sum_consumed.fetch_add(val);
|
||
|
|
consumed.fetch_add(1);
|
||
|
|
} else {
|
||
|
|
std::this_thread::yield();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
for (auto& t : producers) t.join();
|
||
|
|
for (auto& t : consumers) t.join();
|
||
|
|
|
||
|
|
EXPECT_EQ(produced.load(), TOTAL);
|
||
|
|
EXPECT_EQ(consumed.load(), TOTAL);
|
||
|
|
EXPECT_EQ(sum_produced.load(), sum_consumed.load());
|
||
|
|
EXPECT_TRUE(q.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 4: LockFreeStack push/pop (单线程)
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeStackTest, SingleThreadPushPop) {
|
||
|
|
LockFreeStack<int> s;
|
||
|
|
EXPECT_TRUE(s.empty());
|
||
|
|
|
||
|
|
s.push(1);
|
||
|
|
s.push(2);
|
||
|
|
s.push(3);
|
||
|
|
EXPECT_FALSE(s.empty());
|
||
|
|
|
||
|
|
int val;
|
||
|
|
EXPECT_TRUE(s.try_pop(val)); EXPECT_EQ(val, 3);
|
||
|
|
EXPECT_TRUE(s.try_pop(val)); EXPECT_EQ(val, 2);
|
||
|
|
EXPECT_TRUE(s.try_pop(val)); EXPECT_EQ(val, 1);
|
||
|
|
EXPECT_FALSE(s.try_pop(val));
|
||
|
|
EXPECT_TRUE(s.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 5: LockFreeStack 清空
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeStackTest, Clear) {
|
||
|
|
LockFreeStack<int> s;
|
||
|
|
s.push(10);
|
||
|
|
s.push(20);
|
||
|
|
s.push(30);
|
||
|
|
EXPECT_FALSE(s.empty());
|
||
|
|
|
||
|
|
s.clear();
|
||
|
|
EXPECT_TRUE(s.empty());
|
||
|
|
|
||
|
|
int val;
|
||
|
|
EXPECT_FALSE(s.try_pop(val));
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 6: LockFreeStack 多线程
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(LockFreeStackTest, MultiThreaded) {
|
||
|
|
constexpr int NUM_THREADS = 4;
|
||
|
|
constexpr int PER_THREAD = 500;
|
||
|
|
|
||
|
|
LockFreeStack<int> s;
|
||
|
|
std::atomic<int> pop_count{0};
|
||
|
|
std::atomic<int64_t> pop_sum{0};
|
||
|
|
|
||
|
|
// 推送线程
|
||
|
|
std::vector<std::thread> pushers;
|
||
|
|
for (int t = 0; t < NUM_THREADS; ++t) {
|
||
|
|
pushers.emplace_back([&, t]() {
|
||
|
|
for (int i = 0; i < PER_THREAD; ++i) {
|
||
|
|
s.push(t * 1000 + i);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 等待推送完成
|
||
|
|
for (auto& t : pushers) t.join();
|
||
|
|
|
||
|
|
// 弹出线程
|
||
|
|
std::vector<std::thread> poppers;
|
||
|
|
for (int t = 0; t < NUM_THREADS; ++t) {
|
||
|
|
poppers.emplace_back([&]() {
|
||
|
|
int val;
|
||
|
|
while (pop_count.load() < NUM_THREADS * PER_THREAD) {
|
||
|
|
if (s.try_pop(val)) {
|
||
|
|
pop_sum.fetch_add(val);
|
||
|
|
pop_count.fetch_add(1);
|
||
|
|
} else {
|
||
|
|
std::this_thread::yield();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
for (auto& t : poppers) t.join();
|
||
|
|
|
||
|
|
EXPECT_EQ(pop_count.load(), NUM_THREADS * PER_THREAD);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 7: ConcurrentHashMap insert/find/erase
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(ConcurrentHashMapTest, BasicOps) {
|
||
|
|
ConcurrentHashMap<int, int> map;
|
||
|
|
|
||
|
|
// insert
|
||
|
|
map.insert(1, 100);
|
||
|
|
map.insert(2, 200);
|
||
|
|
map.insert(3, 300);
|
||
|
|
|
||
|
|
int val;
|
||
|
|
EXPECT_TRUE(map.find(1, val)); EXPECT_EQ(val, 100);
|
||
|
|
EXPECT_TRUE(map.find(2, val)); EXPECT_EQ(val, 200);
|
||
|
|
EXPECT_TRUE(map.contains(3));
|
||
|
|
EXPECT_FALSE(map.contains(99));
|
||
|
|
|
||
|
|
// update
|
||
|
|
map.insert(1, 111);
|
||
|
|
EXPECT_TRUE(map.find(1, val)); EXPECT_EQ(val, 111);
|
||
|
|
|
||
|
|
// erase
|
||
|
|
EXPECT_TRUE(map.erase(2));
|
||
|
|
EXPECT_FALSE(map.contains(2));
|
||
|
|
EXPECT_FALSE(map.erase(2)); // 重复删除
|
||
|
|
|
||
|
|
EXPECT_EQ(map.size(), 2u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 8: ConcurrentHashMap 多线程并发
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(ConcurrentHashMapTest, ConcurrentInsert) {
|
||
|
|
ConcurrentHashMap<int, int> map;
|
||
|
|
constexpr int NUM_THREADS = 8;
|
||
|
|
constexpr int PER_THREAD = 500;
|
||
|
|
|
||
|
|
std::vector<std::thread> threads;
|
||
|
|
for (int t = 0; t < NUM_THREADS; ++t) {
|
||
|
|
threads.emplace_back([&, t]() {
|
||
|
|
for (int i = 0; i < PER_THREAD; ++i) {
|
||
|
|
int key = t * PER_THREAD + i;
|
||
|
|
map.insert(key, key * 10);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
for (auto& t : threads) t.join();
|
||
|
|
|
||
|
|
EXPECT_EQ(map.size(), size_t(NUM_THREADS * PER_THREAD));
|
||
|
|
|
||
|
|
// 验证部分键值
|
||
|
|
int val;
|
||
|
|
EXPECT_TRUE(map.find(0, val)); EXPECT_EQ(val, 0);
|
||
|
|
EXPECT_TRUE(map.find(500, val)); EXPECT_EQ(val, 5000);
|
||
|
|
EXPECT_TRUE(map.find(3999, val)); EXPECT_EQ(val, 39990);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 9: ReadWriteSpinLock 基本锁
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(ReadWriteSpinLockTest, BasicLockUnlock) {
|
||
|
|
ReadWriteSpinLock rwlock;
|
||
|
|
|
||
|
|
EXPECT_FALSE(rwlock.is_write_locked());
|
||
|
|
|
||
|
|
// 写锁
|
||
|
|
rwlock.lock_write();
|
||
|
|
EXPECT_TRUE(rwlock.is_write_locked());
|
||
|
|
rwlock.unlock_write();
|
||
|
|
EXPECT_FALSE(rwlock.is_write_locked());
|
||
|
|
|
||
|
|
// 读锁
|
||
|
|
rwlock.lock_read();
|
||
|
|
EXPECT_FALSE(rwlock.is_write_locked()); // 读者不加写锁
|
||
|
|
rwlock.unlock_read();
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// 测试 10: ReadWriteSpinLock 多读者+单写者
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
TEST(ReadWriteSpinLockTest, ReadersWriter) {
|
||
|
|
ReadWriteSpinLock rwlock;
|
||
|
|
std::atomic<int> shared{0};
|
||
|
|
std::atomic<bool> done{false};
|
||
|
|
std::atomic<int> max_readers{0};
|
||
|
|
std::atomic<int> current_readers{0};
|
||
|
|
|
||
|
|
// 读者线程 (3 个)
|
||
|
|
std::vector<std::thread> readers;
|
||
|
|
for (int i = 0; i < 3; ++i) {
|
||
|
|
readers.emplace_back([&]() {
|
||
|
|
while (!done.load()) {
|
||
|
|
rwlock.lock_read();
|
||
|
|
int r = current_readers.fetch_add(1) + 1;
|
||
|
|
int m = max_readers.load();
|
||
|
|
while (r > m) max_readers.compare_exchange_weak(m, r);
|
||
|
|
|
||
|
|
EXPECT_GE(shared.load(), 0); // 读取共享数据
|
||
|
|
|
||
|
|
current_readers.fetch_sub(1);
|
||
|
|
rwlock.unlock_read();
|
||
|
|
|
||
|
|
std::this_thread::yield();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 写者线程
|
||
|
|
std::thread writer([&]() {
|
||
|
|
for (int i = 0; i < 100; ++i) {
|
||
|
|
rwlock.lock_write();
|
||
|
|
shared.fetch_add(1);
|
||
|
|
rwlock.unlock_write();
|
||
|
|
std::this_thread::yield();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
writer.join();
|
||
|
|
done.store(true);
|
||
|
|
for (auto& t : readers) t.join();
|
||
|
|
|
||
|
|
EXPECT_EQ(shared.load(), 100);
|
||
|
|
EXPECT_GT(max_readers.load(), 0); // 至少有一个读者进入过
|
||
|
|
}
|