[lfq][test] init commit

This commit is contained in:
Artur Mukhamadiev 2025-06-05 01:27:10 +08:00
commit b075f4d1a8
10 changed files with 255 additions and 0 deletions

1
.clang-format Normal file
View File

@ -0,0 +1 @@
BasedOnStyle: Chromium

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
compile_commands.json
.cache

20
CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View File

16
subprojects/gtest.wrap Normal file
View 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
View 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();
}