2022-04-08 10:28:45 +00:00
|
|
|
#include <thread>
|
2022-04-10 20:39:42 +00:00
|
|
|
#include <vector>
|
2023-10-30 05:39:25 +00:00
|
|
|
#include <cstdio>
|
2022-04-08 10:28:45 +00:00
|
|
|
|
2020-11-27 23:12:06 +00:00
|
|
|
#include "Common/Log.h"
|
|
|
|
#include "Common/TimeUtil.h"
|
2022-04-08 09:41:50 +00:00
|
|
|
#include "Common/Thread/Barrier.h"
|
2020-11-27 23:12:06 +00:00
|
|
|
#include "Common/Thread/ThreadManager.h"
|
|
|
|
#include "Common/Thread/Channel.h"
|
|
|
|
#include "Common/Thread/Promise.h"
|
|
|
|
#include "Common/Thread/ParallelLoop.h"
|
2021-06-12 19:06:59 +00:00
|
|
|
#include "Common/Thread/ThreadUtil.h"
|
2022-04-08 10:04:34 +00:00
|
|
|
#include "Common/Thread/Waitable.h"
|
2020-11-27 23:12:06 +00:00
|
|
|
|
2022-04-08 09:41:50 +00:00
|
|
|
#include "UnitTest.h"
|
|
|
|
|
2020-11-27 23:12:06 +00:00
|
|
|
struct ResultObject {
|
|
|
|
bool ok;
|
|
|
|
};
|
|
|
|
|
|
|
|
ResultObject *ResultProducer() {
|
2024-11-21 14:25:02 +00:00
|
|
|
sleep_ms(250, "test-result");
|
2021-06-12 19:06:59 +00:00
|
|
|
printf("result produced: thread %d\n", GetCurrentThreadIdForDebug());
|
2020-11-27 23:12:06 +00:00
|
|
|
return new ResultObject{ true };
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestMailbox() {
|
2021-11-20 21:40:10 +00:00
|
|
|
Mailbox<ResultObject *> *mailbox = new Mailbox<ResultObject *>();
|
2020-11-27 23:12:06 +00:00
|
|
|
mailbox->Send(new ResultObject{ true });
|
|
|
|
ResultObject *data;
|
|
|
|
data = mailbox->Wait();
|
|
|
|
_assert_(data && data->ok);
|
|
|
|
delete data;
|
|
|
|
mailbox->Release();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rangeFunc(int lower, int upper) {
|
2024-11-21 14:25:02 +00:00
|
|
|
sleep_ms(30, "test-range");
|
2021-06-12 19:21:28 +00:00
|
|
|
printf(" - range %d-%d (thread %d)\n", lower, upper, GetCurrentThreadIdForDebug());
|
2020-11-27 23:12:06 +00:00
|
|
|
}
|
|
|
|
|
2021-06-12 19:06:59 +00:00
|
|
|
// This always passes unless something is badly broken, the interesting thing is the
|
|
|
|
// logged output.
|
2020-11-27 23:12:06 +00:00
|
|
|
bool TestParallelLoop(ThreadManager *threadMan) {
|
2021-06-12 19:21:28 +00:00
|
|
|
printf("tester thread ID: %d\n", GetCurrentThreadIdForDebug());
|
2020-11-27 23:12:06 +00:00
|
|
|
|
2021-06-12 19:21:28 +00:00
|
|
|
printf("waitable test\n");
|
2023-01-15 15:55:07 +00:00
|
|
|
WaitableCounter *waitable = ParallelRangeLoopWaitable(threadMan, rangeFunc, 0, 7, 1, TaskPriority::HIGH);
|
2020-11-27 23:12:06 +00:00
|
|
|
// Can do stuff here if we like.
|
|
|
|
waitable->WaitAndRelease();
|
|
|
|
// Now it's done.
|
2021-06-12 19:06:59 +00:00
|
|
|
|
2021-06-12 19:21:28 +00:00
|
|
|
// Try a loop with stragglers.
|
2021-06-12 19:57:16 +00:00
|
|
|
printf("blocking test #1 [0-65)\n");
|
2021-06-12 19:21:28 +00:00
|
|
|
ParallelRangeLoop(threadMan, rangeFunc, 0, 65, 1);
|
|
|
|
// Try a loop with a relatively large minimum size.
|
2021-06-12 19:57:16 +00:00
|
|
|
printf("blocking test #2 [0-100)\n");
|
2021-06-12 19:21:28 +00:00
|
|
|
ParallelRangeLoop(threadMan, rangeFunc, 0, 100, 40);
|
2021-06-13 08:16:53 +00:00
|
|
|
// Try a loop with minimum size larger than range.
|
|
|
|
printf("waitable test [10-30)\n");
|
2023-01-15 15:55:07 +00:00
|
|
|
WaitableCounter *waitable2 = ParallelRangeLoopWaitable(threadMan, rangeFunc, 10, 30, 40, TaskPriority::LOW);
|
2021-06-13 08:16:53 +00:00
|
|
|
waitable2->WaitAndRelease();
|
2020-11-27 23:12:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-10 20:39:42 +00:00
|
|
|
const size_t THREAD_COUNT = 9;
|
2022-04-08 10:28:45 +00:00
|
|
|
const size_t ITERATIONS = 40000;
|
2022-04-08 09:41:50 +00:00
|
|
|
|
|
|
|
static std::atomic<int> g_atomicCounter;
|
|
|
|
static ThreadManager *g_threadMan;
|
|
|
|
static CountingBarrier g_barrier(THREAD_COUNT + 1);
|
|
|
|
|
|
|
|
class IncrementTask : public Task {
|
|
|
|
public:
|
2022-04-08 10:04:34 +00:00
|
|
|
IncrementTask(TaskType type, LimitedWaitable *waitable) : type_(type), waitable_(waitable) {}
|
2022-04-08 09:41:50 +00:00
|
|
|
~IncrementTask() {}
|
2022-12-11 04:32:12 +00:00
|
|
|
TaskType Type() const override { return type_; }
|
2023-01-15 15:55:07 +00:00
|
|
|
TaskPriority Priority() const override {
|
|
|
|
return TaskPriority::NORMAL;
|
|
|
|
}
|
2022-12-11 04:32:12 +00:00
|
|
|
void Run() override {
|
2022-04-08 09:41:50 +00:00
|
|
|
g_atomicCounter++;
|
2022-04-08 10:04:34 +00:00
|
|
|
waitable_->Notify();
|
2022-04-08 09:41:50 +00:00
|
|
|
}
|
|
|
|
private:
|
|
|
|
TaskType type_;
|
2022-04-08 10:04:34 +00:00
|
|
|
LimitedWaitable *waitable_;
|
2022-04-08 09:41:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void ThreadFunc() {
|
|
|
|
for (int i = 0; i < ITERATIONS; i++) {
|
2022-04-08 10:04:34 +00:00
|
|
|
auto threadWaitable = new LimitedWaitable();
|
|
|
|
g_threadMan->EnqueueTask(new IncrementTask((i & 1) ? TaskType::CPU_COMPUTE : TaskType::IO_BLOCKING, threadWaitable));
|
|
|
|
threadWaitable->WaitAndRelease();
|
2022-04-08 09:41:50 +00:00
|
|
|
}
|
|
|
|
g_barrier.Arrive();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestMultithreadedScheduling() {
|
|
|
|
g_atomicCounter = 0;
|
2022-04-08 09:55:49 +00:00
|
|
|
|
|
|
|
auto start = Instant::Now();
|
|
|
|
|
2022-04-10 20:39:42 +00:00
|
|
|
std::vector<std::thread> threads;
|
|
|
|
for (int i = 0; i < THREAD_COUNT; i++) {
|
|
|
|
threads.push_back(std::thread(ThreadFunc));
|
|
|
|
}
|
2022-04-08 09:41:50 +00:00
|
|
|
|
|
|
|
// Just testing the barrier
|
|
|
|
g_barrier.Arrive();
|
|
|
|
// OK, all are done.
|
|
|
|
|
|
|
|
EXPECT_EQ_INT(g_atomicCounter, THREAD_COUNT * ITERATIONS);
|
|
|
|
|
2022-04-10 20:39:42 +00:00
|
|
|
for (int i = 0; i < THREAD_COUNT; i++) {
|
|
|
|
threads[i].join();
|
|
|
|
}
|
|
|
|
|
|
|
|
threads.clear();
|
2022-04-08 09:41:50 +00:00
|
|
|
|
2024-06-05 08:29:04 +00:00
|
|
|
printf("Stress test elapsed: %0.2f", start.ElapsedSeconds());
|
2022-04-08 09:55:49 +00:00
|
|
|
|
2022-04-08 09:41:50 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-11-27 23:12:06 +00:00
|
|
|
bool TestThreadManager() {
|
|
|
|
ThreadManager manager;
|
2021-06-13 08:28:27 +00:00
|
|
|
manager.Init(8, 1);
|
2020-11-27 23:12:06 +00:00
|
|
|
|
2022-04-08 09:41:50 +00:00
|
|
|
g_threadMan = &manager;
|
|
|
|
|
2021-11-20 21:40:10 +00:00
|
|
|
Promise<ResultObject *> *object(Promise<ResultObject *>::Spawn(&manager, &ResultProducer, TaskType::IO_BLOCKING));
|
2020-11-27 23:12:06 +00:00
|
|
|
|
|
|
|
if (!TestParallelLoop(&manager)) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-11-21 14:25:02 +00:00
|
|
|
sleep_ms(100, "test-threadman");
|
2020-11-27 23:12:06 +00:00
|
|
|
|
|
|
|
ResultObject *result = object->BlockUntilReady();
|
|
|
|
if (result) {
|
2022-01-29 22:06:55 +00:00
|
|
|
printf("Got result back!\n");
|
2020-11-27 23:12:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete object;
|
2021-06-13 09:26:24 +00:00
|
|
|
|
|
|
|
if (!TestMailbox()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-08 09:41:50 +00:00
|
|
|
if (!TestMultithreadedScheduling()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-27 23:12:06 +00:00
|
|
|
return true;
|
|
|
|
}
|