[lfq][test] init commit
This commit is contained in:
commit
b075f4d1a8
1
.clang-format
Normal file
1
.clang-format
Normal file
@ -0,0 +1 @@
|
|||||||
|
BasedOnStyle: Chromium
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
build/
|
||||||
|
compile_commands.json
|
||||||
|
.cache
|
||||||
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(lfq)
|
||||||
|
|
||||||
|
find_package(GTest REQUIRED)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME} test.cc)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC include)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE gtest::gtest)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||||
|
-O2
|
||||||
|
-fsanitize=thread
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE
|
||||||
|
-fsanitize=thread
|
||||||
|
)
|
||||||
10
CMakeUserPresets.json
Normal file
10
CMakeUserPresets.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"vendor": {
|
||||||
|
"conan": {}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"build/Release/generators/CMakePresets.json",
|
||||||
|
"build/Debug/generators/CMakePresets.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
10
conanfile.txt
Normal file
10
conanfile.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[requires]
|
||||||
|
gtest/1.16.0
|
||||||
|
glog/0.7.1
|
||||||
|
benchmark/1.9.1
|
||||||
|
|
||||||
|
[generators]
|
||||||
|
CMakeDeps
|
||||||
|
CMakeToolchain
|
||||||
|
[layout]
|
||||||
|
cmake_layout
|
||||||
115
include/queue.hh
Normal file
115
include/queue.hh
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <thread>
|
||||||
|
namespace lfq {
|
||||||
|
|
||||||
|
template <typename T, size_t Size = 256>
|
||||||
|
class LockFreeQueue {
|
||||||
|
static_assert(!(Size & (Size - 1)), "Size must be power of two");
|
||||||
|
|
||||||
|
struct node {
|
||||||
|
node() = default;
|
||||||
|
node(size_t pos, T obj) : pos(pos), obj(obj) {}
|
||||||
|
node(node& oth) : pos(oth.pos.load()), obj(oth.obj) {}
|
||||||
|
node(node&& oth) : pos(oth.pos.load()), obj(std::move(oth.obj)) {}
|
||||||
|
node& operator=(node& oth) {
|
||||||
|
pos = oth.pos.load();
|
||||||
|
obj = oth.obj;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
node& operator=(node&& oth) {
|
||||||
|
pos = oth.pos.load();
|
||||||
|
obj = std::move(oth.obj);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<size_t> pos{std::numeric_limits<size_t>::max()};
|
||||||
|
T obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
LockFreeQueue() : enqueue_pos(0), dequeue_pos(0) {}
|
||||||
|
|
||||||
|
void push_back(const T& element);
|
||||||
|
void pull_front(T& element);
|
||||||
|
size_t apprx_available() const;
|
||||||
|
bool is_full() const;
|
||||||
|
bool is_empty() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr size_t mask{Size - 1};
|
||||||
|
std::atomic<size_t> enqueue_pos;
|
||||||
|
std::atomic<size_t> dequeue_pos;
|
||||||
|
std::array<node, Size> elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief lfq implementation of pull operation.
|
||||||
|
* result will be written to reference argument
|
||||||
|
* it will block the thread until success
|
||||||
|
*/
|
||||||
|
template <typename T, size_t Size>
|
||||||
|
void LockFreeQueue<T, Size>::pull_front(T& element) {
|
||||||
|
node* n_obj;
|
||||||
|
size_t fix_pos{0};
|
||||||
|
bool res{false};
|
||||||
|
while (!res) {
|
||||||
|
while (is_empty()) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
fix_pos = dequeue_pos.load();
|
||||||
|
n_obj = &elements[fix_pos & mask];
|
||||||
|
if (n_obj->pos != fix_pos) {
|
||||||
|
// someone installed the value already retry
|
||||||
|
std::this_thread::yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res = dequeue_pos.compare_exchange_weak(fix_pos, fix_pos + 1);
|
||||||
|
}
|
||||||
|
element = n_obj->obj;
|
||||||
|
n_obj->pos = std::numeric_limits<size_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief lock free implementation for push back operation
|
||||||
|
* @details trying my best to make it work :D
|
||||||
|
*/
|
||||||
|
template <typename T, size_t Size>
|
||||||
|
void LockFreeQueue<T, Size>::push_back(const T& element) {
|
||||||
|
size_t fix_pos{0};
|
||||||
|
bool res{false};
|
||||||
|
node obj;
|
||||||
|
while (!res) {
|
||||||
|
while (is_full()) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
fix_pos = enqueue_pos.load();
|
||||||
|
obj = elements[fix_pos & mask];
|
||||||
|
res = enqueue_pos.compare_exchange_weak(fix_pos, fix_pos + 1);
|
||||||
|
}
|
||||||
|
elements[fix_pos & mask] = node{fix_pos, element};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, size_t Size>
|
||||||
|
bool LockFreeQueue<T, Size>::is_full() const {
|
||||||
|
assert(enqueue_pos.load() >= dequeue_pos.load());
|
||||||
|
return enqueue_pos.load() - dequeue_pos.load() >= Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, size_t Size>
|
||||||
|
bool LockFreeQueue<T, Size>::is_empty() const {
|
||||||
|
return enqueue_pos.load() == dequeue_pos.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, size_t Size>
|
||||||
|
size_t LockFreeQueue<T, Size>::apprx_available() const {
|
||||||
|
assert(enqueue_pos.load() >= dequeue_pos.load());
|
||||||
|
return enqueue_pos.load() - dequeue_pos.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lfq
|
||||||
8
main.cc
Normal file
8
main.cc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "queue.hh"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
lfq::LockFreeQueue<int, 16> queue;
|
||||||
|
std::cout << "Hello!\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
0
meson.build
Normal file
0
meson.build
Normal file
16
subprojects/gtest.wrap
Normal file
16
subprojects/gtest.wrap
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[wrap-file]
|
||||||
|
directory = googletest-1.17.0
|
||||||
|
source_url = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
|
||||||
|
source_filename = gtest-1.17.0.tar.gz
|
||||||
|
source_hash = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
||||||
|
patch_filename = gtest_1.17.0-2_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.17.0-2/get_patch
|
||||||
|
patch_hash = c6ff59f36c8ee48bcd6d968f08a5a08c2c4216a2327079c0ae2323b2e062971e
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.17.0-2/gtest-1.17.0.tar.gz
|
||||||
|
wrapdb_version = 1.17.0-2
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
gtest = gtest_dep
|
||||||
|
gtest_main = gtest_main_dep
|
||||||
|
gmock = gmock_dep
|
||||||
|
gmock_main = gmock_main_dep
|
||||||
72
test.cc
Normal file
72
test.cc
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <future>
|
||||||
|
#include <thread>
|
||||||
|
// #include <ranges>
|
||||||
|
#include "queue.hh"
|
||||||
|
|
||||||
|
struct Env {
|
||||||
|
size_t num_to_repeat{1000};
|
||||||
|
size_t time{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, size_t size>
|
||||||
|
void producer(lfq::LockFreeQueue<T, size>& queue, T el, Env env) {
|
||||||
|
for (int i = 0; i < env.num_to_repeat; ++i) {
|
||||||
|
queue.push_back({el});
|
||||||
|
sleep(env.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, size_t size>
|
||||||
|
void consumer(lfq::LockFreeQueue<T, size>& queue, Env env) {
|
||||||
|
T element;
|
||||||
|
for (int i = 0; i < env.num_to_repeat; ++i) {
|
||||||
|
queue.pull_front(element);
|
||||||
|
sleep(env.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LFQ, spsc) {
|
||||||
|
std::cout << "Single producer single consumer\n";
|
||||||
|
lfq::LockFreeQueue<size_t> queue;
|
||||||
|
size_t element = 10;
|
||||||
|
Env env{.num_to_repeat = 10, .time = 0};
|
||||||
|
EXPECT_NO_THROW(producer(queue, element, env));
|
||||||
|
EXPECT_NO_THROW(consumer(queue, env));
|
||||||
|
ASSERT_EQ(queue.apprx_available(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LFQ, spmc) {
|
||||||
|
std::cout << "Single producer multi consumer\n";
|
||||||
|
lfq::LockFreeQueue<size_t, 4096> queue;
|
||||||
|
size_t element = 10;
|
||||||
|
Env env_consumer{.num_to_repeat = 1000, .time = 0};
|
||||||
|
Env env_producer{.num_to_repeat = 10000, .time = 0};
|
||||||
|
|
||||||
|
std::future<void> pobj =
|
||||||
|
std::async(std::launch::async, [env_producer, &queue] {
|
||||||
|
EXPECT_NO_THROW(producer(queue, size_t(10), env_producer));
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<std::future<void>> consumers;
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
consumers.emplace_back(
|
||||||
|
std::async(std::launch::async, [env_consumer, &queue] {
|
||||||
|
EXPECT_NO_THROW(consumer(queue, env_consumer));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& cobj : consumers) {
|
||||||
|
EXPECT_NO_THROW(cobj.wait());
|
||||||
|
}
|
||||||
|
ASSERT_EQ(queue.apprx_available(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LFQ, mpsc) {}
|
||||||
|
|
||||||
|
TEST(LFQ, mpmc) {}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user