From 408f38f41245e33bcbf2d18fd8978afd96011c9b Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:42:13 +0300 Subject: [PATCH 1/8] Add static channel --- .github/workflows/cmake.yml | 8 +- include/msd/blocking_iterator.hpp | 2 +- include/msd/channel.hpp | 10 +- include/msd/static_channel.hpp | 215 ++++++++++++++++++++++++++++ tests/CMakeLists.txt | 9 +- tests/static_channel_test.cpp | 223 ++++++++++++++++++++++++++++++ 6 files changed, 452 insertions(+), 15 deletions(-) create mode 100644 include/msd/static_channel.hpp create mode 100644 tests/static_channel_test.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 65831b1..60dcfa0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -76,11 +76,11 @@ jobs: - name: Build working-directory: ${{github.workspace}}/build - run: cmake --build . --config ${{ matrix.config.build_type }} --target tests -j + run: cmake --build . --config ${{ matrix.config.build_type }} --target channel_tests -j - name: Test working-directory: ${{github.workspace}}/build - run: ctest -C ${{ matrix.config.build_type }} --verbose -R channel_test --output-on-failure -j + run: ctest -C ${{ matrix.config.build_type }} --verbose -L channel_tests --output-on-failure -j - name: Run examples working-directory: ${{github.workspace}}/build @@ -102,11 +102,11 @@ jobs: - name: Build working-directory: ${{github.workspace}}/build - run: cmake --build . --config Debug --target tests -j + run: cmake --build . --config Debug --target channel_tests -j - name: Test working-directory: ${{github.workspace}}/build - run: ctest -C Debug --verbose -R channel_test -j + run: ctest -C Debug --verbose -L channel_tests -j - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 diff --git a/include/msd/blocking_iterator.hpp b/include/msd/blocking_iterator.hpp index cb55195..ea3077f 100644 --- a/include/msd/blocking_iterator.hpp +++ b/include/msd/blocking_iterator.hpp @@ -64,7 +64,7 @@ class blocking_iterator { */ reference operator*() { - chan_ >> value_; + chan_.read(value_); return value_; } diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 5f19799..3646cec 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -134,11 +134,9 @@ class channel { return false; } - if (!(size_ == 0)) { - out = std::move(queue_.front()); - queue_.pop(); - --size_; - } + out = std::move(queue_.front()); + queue_.pop(); + --size_; } cnd_.notify_one(); @@ -247,8 +245,6 @@ class channel { cnd_.wait(lock, [this]() { return size_ < cap_; }); } } - - friend class blocking_iterator; }; template diff --git a/include/msd/static_channel.hpp b/include/msd/static_channel.hpp new file mode 100644 index 0000000..c193b94 --- /dev/null +++ b/include/msd/static_channel.hpp @@ -0,0 +1,215 @@ +// Copyright (C) 2020-2025 Andrei Avram + +#ifndef MSD_STATIC_CHANNEL_HPP_ +#define MSD_STATIC_CHANNEL_HPP_ + +#include +#include +#include +#include + +#include "blocking_iterator.hpp" +#include "channel.hpp" + +namespace msd { + +/** + * @brief Thread-safe container for sharing data between threads. + * + * Allocates on the stack. + * Does not throw exceptions. + * Implements a blocking input iterator. + * + * @tparam T The type of the elements. + * @tparam Capacity The maximum number of elements the channel can hold before blocking. + */ +template +class static_channel { + public: + static_assert(Capacity > 0, "Channel capacity must be greater than zero."); + + /** + * @brief The type of elements stored in the channel. + */ + using value_type = T; + + /** + * @brief The iterator type used to traverse the channel. + */ + using iterator = blocking_iterator>; + + /** + * @brief The type used to represent sizes and counts. + */ + using size_type = std::size_t; + + /** + * @brief Creates a new channel. + */ + constexpr static_channel() = default; + + /** + * @brief Pushes an element into the channel. + * + * @tparam Type The type of the elements. + * + * @param value The element to be pushed into the channel. + * + * @return true If an element was successfully pushed into the channel. + * @return false If the channel is closed. + */ + template + bool write(Type&& value) + { + { + std::unique_lock lock{mtx_}; + waitBeforeWrite(lock); + + if (is_closed_) { + return false; + } + + array_[(front_ + size_) % Capacity] = std::forward(value); + ++size_; + } + + cnd_.notify_one(); + + return true; + } + + /** + * @brief Pops an element from the channel. + * + * @param out Reference to the variable where the popped element will be stored. + * + * @return true If an element was successfully read from the channel. + * @return false If the channel is closed and empty. + */ + bool read(T& out) + { + { + std::unique_lock lock{mtx_}; + waitBeforeRead(lock); + + if (is_closed_ && size_ == 0) { + return false; + } + + out = std::move(array_[front_]); + front_ = (front_ + 1) % Capacity; + --size_; + } + + cnd_.notify_one(); + + return true; + } + + /** + * @brief Returns the current size of the channel. + * + * @return The number of elements in the channel. + */ + NODISCARD size_type size() const noexcept + { + std::unique_lock lock{mtx_}; + return size_; + } + + /** + * @brief Checks if the channel is empty. + * + * @return true If the channel contains no elements. + * @return false Otherwise. + */ + NODISCARD bool empty() const noexcept + { + std::unique_lock lock{mtx_}; + return size_ == 0; + } + + /** + * @brief Closes the channel, no longer accepting new elements. + */ + void close() noexcept + { + { + std::unique_lock lock{mtx_}; + is_closed_ = true; + } + cnd_.notify_all(); + } + + /** + * @brief Checks if the channel has been closed. + * + * @return true If no more elements can be added to the channel. + * @return false Otherwise. + */ + NODISCARD bool closed() const noexcept + { + std::unique_lock lock{mtx_}; + return is_closed_; + } + + /** + * @brief Checks if the channel has been closed and is empty. + * + * @return true If nothing can be read anymore from the channel. + * @return false Otherwise. + */ + NODISCARD bool drained() noexcept + { + std::unique_lock lock{mtx_}; + return is_closed_ && size_ == 0; + } + + /** + * @brief Returns an iterator to the beginning of the channel. + * + * @return A blocking iterator pointing to the start of the channel. + */ + iterator begin() noexcept { return blocking_iterator>{*this}; } + + /** + * @brief Returns an iterator representing the end of the channel. + * + * @return A blocking iterator representing the end condition. + */ + iterator end() noexcept { return blocking_iterator>{*this}; } + + /** + * Channel cannot be copied or moved. + */ + static_channel(const static_channel&) = delete; + static_channel& operator=(const static_channel&) = delete; + static_channel(static_channel&&) = delete; + static_channel& operator=(static_channel&&) = delete; + virtual ~static_channel() = default; + + private: + std::array array_{}; + size_type front_{0}; + std::size_t size_{0}; + const size_type cap_{Capacity}; + mutable std::mutex mtx_; + std::condition_variable cnd_; + bool is_closed_{false}; + + void waitBeforeRead(std::unique_lock& lock) + { + cnd_.wait(lock, [this]() { return !(size_ == 0) || is_closed_; }); + }; + + void waitBeforeWrite(std::unique_lock& lock) + { + if (cap_ > 0 && size_ == cap_) { + cnd_.wait(lock, [this]() { return size_ < cap_; }); + } + } +}; + +} // namespace msd + +#endif // MSD_STATIC_CHANNEL_HPP_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 76be80d..ed7dcb1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,14 +34,17 @@ function(package_add_test TESTNAME) endif () add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) + set_tests_properties(${TESTNAME} PROPERTIES LABELS "channel_tests") - add_dependencies(tests ${TESTNAME}) + add_dependencies(channel_tests ${TESTNAME}) endfunction() -add_custom_target(tests) +add_custom_target(channel_tests) # Tests -package_add_test(channel_test channel_test.cpp blocking_iterator_test.cpp) +package_add_test(channel_test channel_test.cpp) +package_add_test(static_channel_test static_channel_test.cpp) +package_add_test(blocking_iterator_test blocking_iterator_test.cpp) if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") # Disable warnings about C++17 extensions diff --git a/tests/static_channel_test.cpp b/tests/static_channel_test.cpp new file mode 100644 index 0000000..cc3b95a --- /dev/null +++ b/tests/static_channel_test.cpp @@ -0,0 +1,223 @@ +#include "msd/static_channel.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +TEST(StaticChannelTest, Traits) +{ + using type = int; + using channel = msd::static_channel; + EXPECT_TRUE((std::is_same::value)); + + using iterator = msd::blocking_iterator>; + EXPECT_TRUE((std::is_same::value)); + + EXPECT_TRUE((std::is_same::value)); +} + +TEST(StaticChannelTest, WriteAndRead) +{ + msd::static_channel channel; + + int in = 1; + EXPECT_TRUE(channel.write(in)); + + const int cin = 3; + EXPECT_TRUE(channel.write(cin)); + + channel.close(); + EXPECT_FALSE(channel.write(2)); + + int out = 0; + + EXPECT_TRUE(channel.read(out)); + EXPECT_EQ(1, out); + + EXPECT_TRUE(channel.read(out)); + EXPECT_EQ(3, out); + + EXPECT_FALSE(channel.read(out)); +} + +TEST(StaticChannelTest, WriteByMoveAndRead) +{ + msd::static_channel channel; + + std::string in = "abc"; + EXPECT_TRUE(channel.write(std::move(in))); + + EXPECT_TRUE(channel.write(std::string{"def"})); + + std::string out{}; + EXPECT_TRUE(channel.read(out)); + EXPECT_EQ("abc", out); + + EXPECT_TRUE(channel.read(out)); + EXPECT_EQ("def", out); +} + +TEST(StaticChannelTest, size) +{ + msd::static_channel channel; + EXPECT_EQ(0, channel.size()); + + int in = 1; + channel.write(in); + EXPECT_EQ(1, channel.size()); + + channel.read(in); + EXPECT_EQ(0, channel.size()); +} + +TEST(StaticChannelTest, empty) +{ + msd::static_channel channel; + EXPECT_TRUE(channel.empty()); + + int in = 1; + channel.write(in); + EXPECT_FALSE(channel.empty()); + + channel.read(in); + EXPECT_TRUE(channel.empty()); +} + +TEST(StaticChannelTest, close) +{ + msd::static_channel channel; + EXPECT_FALSE(channel.closed()); + + int in = 1; + EXPECT_TRUE(channel.write(in)); + + channel.close(); + EXPECT_TRUE(channel.closed()); + + int out = 0; + EXPECT_TRUE(channel.read(out)); + EXPECT_EQ(1, out); + EXPECT_FALSE(channel.read(out)); + + EXPECT_FALSE(channel.write(in)); +} + +TEST(StaticChannelTest, Iterator) +{ + msd::static_channel channel; + + channel.write(1); + + for (auto it = channel.begin(); it != channel.end();) { + EXPECT_EQ(1, *it); + break; + } +} + +TEST(StaticChannelTest, Multithreading) +{ + const int numbers = 10000; + const std::int64_t expected = 50005000; + constexpr std::size_t kThreadsToReadFrom = 100; + + msd::static_channel channel{}; + + std::mutex mtx_read{}; + std::condition_variable cond_read{}; + bool ready_to_read{}; + std::atomic count_numbers{}; + std::atomic sum_numbers{}; + + std::mutex mtx_wait{}; + std::condition_variable cond_wait{}; + std::atomic wait_counter{kThreadsToReadFrom}; + + auto worker = [&] { + // Wait until there is data on the channel + std::unique_lock lock{mtx_read}; + cond_read.wait(lock, [&ready_to_read] { return ready_to_read; }); + + // Read until all items have been read from the channel + while (count_numbers < numbers) { + int out{}; + channel.read(out); + + sum_numbers += out; + ++count_numbers; + } + --wait_counter; + cond_wait.notify_one(); + }; + + std::vector threads{}; + for (std::size_t i = 0U; i < kThreadsToReadFrom; ++i) { + threads.emplace_back(std::thread{worker}); + } + + // Send numbers to channel + for (int i = 1; i <= numbers; ++i) { + channel.write(i); + + // Notify threads than then can start reading + if (!ready_to_read) { + ready_to_read = true; + cond_read.notify_all(); + } + } + + // Wait until all items have been read + std::unique_lock lock{mtx_wait}; + cond_wait.wait(lock, [&wait_counter]() { return wait_counter.load() == 0; }); + + std::for_each(threads.begin(), threads.end(), [](std::thread& thread) { thread.join(); }); + + EXPECT_EQ(expected, sum_numbers); +} + +TEST(StaticChannelTest, ReadWriteClose) +{ + const int numbers = 10000; + const std::int64_t expected_sum = 50005000; + constexpr std::size_t kThreadsToReadFrom = 20; + + msd::static_channel channel{}; + std::atomic sum{0}; + std::atomic nums{0}; + + std::thread writer([&channel]() { + for (int i = 1; i <= numbers; ++i) { + channel.write(i); + } + channel.close(); + }); + + std::vector readers; + for (std::size_t i = 0; i < kThreadsToReadFrom; ++i) { + readers.emplace_back([&channel, &sum, &nums]() { + while (true) { + int value = 0; + + if (!channel.read(value)) { + return; + } + + sum += value; + ++nums; + } + }); + } + + writer.join(); + for (auto& reader : readers) { + reader.join(); + } + + EXPECT_EQ(sum, expected_sum); + EXPECT_EQ(nums, numbers); +} From f90115f9748d8ff2397e425f6b5b5228726ed429 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:53:17 +0300 Subject: [PATCH 2/8] Simplify conditions --- include/msd/channel.hpp | 4 ++-- include/msd/static_channel.hpp | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 3646cec..6b1bf12 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -200,7 +200,7 @@ class channel { NODISCARD bool drained() noexcept { std::unique_lock lock{mtx_}; - return is_closed_ && size_ == 0; + return size_ == 0 && is_closed_; } /** @@ -236,7 +236,7 @@ class channel { void waitBeforeRead(std::unique_lock& lock) { - cnd_.wait(lock, [this]() { return !(size_ == 0) || is_closed_; }); + cnd_.wait(lock, [this]() { return size_ > 0 || is_closed_; }); }; void waitBeforeWrite(std::unique_lock& lock) diff --git a/include/msd/static_channel.hpp b/include/msd/static_channel.hpp index c193b94..d8c6945 100644 --- a/include/msd/static_channel.hpp +++ b/include/msd/static_channel.hpp @@ -162,7 +162,7 @@ class static_channel { NODISCARD bool drained() noexcept { std::unique_lock lock{mtx_}; - return is_closed_ && size_ == 0; + return size_ == 0 && is_closed_; } /** @@ -192,20 +192,19 @@ class static_channel { std::array array_{}; size_type front_{0}; std::size_t size_{0}; - const size_type cap_{Capacity}; mutable std::mutex mtx_; std::condition_variable cnd_; bool is_closed_{false}; void waitBeforeRead(std::unique_lock& lock) { - cnd_.wait(lock, [this]() { return !(size_ == 0) || is_closed_; }); + cnd_.wait(lock, [this]() { return size_ > 0 || is_closed_; }); }; void waitBeforeWrite(std::unique_lock& lock) { - if (cap_ > 0 && size_ == cap_) { - cnd_.wait(lock, [this]() { return size_ < cap_; }); + if (size_ == Capacity) { + cnd_.wait(lock, [this]() { return size_ < Capacity; }); } } }; From e2a9cdf0eb54c1c9f250289f820006e4c7638c27 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:38:13 +0300 Subject: [PATCH 3/8] Add CPP_CHANNEL_SANITIZE_THREADS --- .vscode/settings.json | 1 + CMakeLists.txt | 1 + tests/CMakeLists.txt | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ce48ab..507f7f5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "-DCPP_CHANNEL_BUILD_TESTS=ON", "-DCPP_CHANNEL_COVERAGE=ON", "-DCPP_CHANNEL_SANITIZERS=ON", + "-DCPP_CHANNEL_SANITIZE_THREADS=OFF", "-DCMAKE_CXX_STANDARD=11", "-DCMAKE_INSTALL_PREFIX=${workspaceFolder}/install", ], diff --git a/CMakeLists.txt b/CMakeLists.txt index 78f7e1d..a9825ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ option(CPP_CHANNEL_BUILD_TESTS "Build all of cpp_channel's own tests." OFF) option(CPP_CHANNEL_BUILD_EXAMPLES "Build cpp_channel's example programs." OFF) option(CPP_CHANNEL_COVERAGE "Generate test coverage." OFF) option(CPP_CHANNEL_SANITIZERS "Build with sanitizers." OFF) +option(CPP_CHANNEL_SANITIZE_THREADS "Build with thread sanitizer." OFF) if (CPP_CHANNEL_BUILD_TESTS) enable_testing() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed7dcb1..fb5c7d1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,6 +33,11 @@ function(package_add_test TESTNAME) target_compile_options(${TESTNAME} PRIVATE -fsanitize=undefined) endif () + if (CPP_CHANNEL_SANITIZE_THREADS) + target_link_libraries(${TESTNAME} -fsanitize=thread) + target_compile_options(${TESTNAME} PRIVATE -fsanitize=thread) + endif () + add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) set_tests_properties(${TESTNAME} PROPERTIES LABELS "channel_tests") From 573f866ef5d34554620dd8de9c7829ee984ca8aa Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:38:26 +0300 Subject: [PATCH 4/8] Extract nodiscard --- include/msd/channel.hpp | 7 +------ include/msd/nodiscard.hpp | 16 ++++++++++++++++ include/msd/static_channel.hpp | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 include/msd/nodiscard.hpp diff --git a/include/msd/channel.hpp b/include/msd/channel.hpp index 6b1bf12..02ceb41 100644 --- a/include/msd/channel.hpp +++ b/include/msd/channel.hpp @@ -11,15 +11,10 @@ #include #include "blocking_iterator.hpp" +#include "nodiscard.hpp" namespace msd { -#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) -#define NODISCARD [[nodiscard]] -#else -#define NODISCARD -#endif - /** * @brief Exception thrown if trying to write on closed channel. */ diff --git a/include/msd/nodiscard.hpp b/include/msd/nodiscard.hpp new file mode 100644 index 0000000..ea91575 --- /dev/null +++ b/include/msd/nodiscard.hpp @@ -0,0 +1,16 @@ +// Copyright (C) 2020-2025 Andrei Avram + +#ifndef MSD_NODISCARD_HPP_ +#define MSD_NODISCARD_HPP_ + +namespace msd { + +#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) +#define NODISCARD [[nodiscard]] +#else +#define NODISCARD +#endif + +} // namespace msd + +#endif // MSD_NODISCARD_HPP_ diff --git a/include/msd/static_channel.hpp b/include/msd/static_channel.hpp index d8c6945..ea5dee6 100644 --- a/include/msd/static_channel.hpp +++ b/include/msd/static_channel.hpp @@ -9,14 +9,14 @@ #include #include "blocking_iterator.hpp" -#include "channel.hpp" +#include "nodiscard.hpp" namespace msd { /** * @brief Thread-safe container for sharing data between threads. * - * Allocates on the stack. + * Allocates elements on the stack. * Does not throw exceptions. * Implements a blocking input iterator. * From a70d94ac5b61d084fc57d0ed21aa81ef9af67550 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:48:31 +0300 Subject: [PATCH 5/8] Add example --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 8b65976..2cc1403 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ * Close to prevent pushing and stop waiting to fetch. * Integrates well with STL algorithms in some cases. Eg: std::move(ch.begin(), ch.end(), ...). * Tested with GCC, Clang, and MSVC. +* Includes stack-based, exception-free alternative (static channel). ## Requirements @@ -111,6 +112,26 @@ int main() { } ``` +```c++ +#include + +int main() { + msd::static_channel chan{}; // always buffered + + int in = 1; + int out = 0; + + // Send to channel + chan.write(in); + chan.write(in); + + // Read from channel + chan.read(out); + chan.read(out); + chan.read(out); // blocking because channel is empty (and no one writes on it) +} +``` + See [examples](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples) and [documentation](https://andreiavrammsd.github.io/cpp-channel/).
From c7fb37585245bbfe58429e20ed04006c5621d4d8 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:29:48 +0300 Subject: [PATCH 6/8] Add static channel example --- examples/CMakeLists.txt | 3 ++ examples/multithreading_static_channel.cpp | 47 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 examples/multithreading_static_channel.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 042bf70..2de8c80 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -36,3 +36,6 @@ add_example(example_multithreading multithreading.cpp) add_example(example_streaming streaming.cpp) run_example(example_streaming) + +add_example(example_multithreading_static_channel multithreading_static_channel.cpp) +run_example(example_multithreading_static_channel) diff --git a/examples/multithreading_static_channel.cpp b/examples/multithreading_static_channel.cpp new file mode 100644 index 0000000..f0f5618 --- /dev/null +++ b/examples/multithreading_static_channel.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +#include "msd/static_channel.hpp" + +int main() +{ + msd::static_channel chan{}; // always buffered + + // Send to channel + const auto writer = [&chan](int begin, int end) { + for (int i = begin; i <= end; ++i) { + chan.write(i); + + std::stringstream msg; + msg << "Sent " << i << " from " << std::this_thread::get_id() << "\n"; + std::cout << msg.str(); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // simulate work + } + chan.close(); + }; + + const auto reader = [&chan]() { + for (const auto out : chan) { // blocking until channel is drained (closed and empty) + std::stringstream msg; + msg << "Received " << out << " on " << std::this_thread::get_id() << "\n"; + std::cout << msg.str(); + + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // simulate work + } + }; + + const auto reader_1 = std::async(std::launch::async, reader); + const auto reader_2 = std::async(std::launch::async, reader); + const auto reader_3 = std::async(std::launch::async, reader); + const auto writer_1 = std::async(std::launch::async, writer, 1, 50); + const auto writer_2 = std::async(std::launch::async, writer, 51, 100); + + reader_1.wait(); + reader_2.wait(); + reader_3.wait(); + writer_1.wait(); + writer_2.wait(); +} From c4d4dbd188ad0d66a502008ed826b15496008e5d Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:36:48 +0300 Subject: [PATCH 7/8] Rename iterator tests --- tests/blocking_iterator_test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/blocking_iterator_test.cpp b/tests/blocking_iterator_test.cpp index 674db82..5b85737 100644 --- a/tests/blocking_iterator_test.cpp +++ b/tests/blocking_iterator_test.cpp @@ -4,7 +4,7 @@ #include "msd/channel.hpp" -TEST(ChannelIteratorTest, Traits) +TEST(BlockingIteratorTest, Traits) { using type = int; using iterator = msd::blocking_iterator>; @@ -15,7 +15,7 @@ TEST(ChannelIteratorTest, Traits) EXPECT_TRUE((std::is_same::value)); } -TEST(ChannelIteratorTest, Dereference) +TEST(BlockingIteratorTest, Dereference) { msd::channel channel; msd::blocking_iterator> it{channel}; @@ -29,7 +29,7 @@ TEST(ChannelIteratorTest, Dereference) EXPECT_EQ(2, *it); } -TEST(ChannelIteratorTest, NotEqualStop) +TEST(BlockingIteratorTest, NotEqualStop) { msd::channel channel; msd::blocking_iterator> it{channel}; @@ -39,7 +39,7 @@ TEST(ChannelIteratorTest, NotEqualStop) EXPECT_FALSE(it != it); } -TEST(ChannelIteratorTest, NotEqualContinue) +TEST(BlockingIteratorTest, NotEqualContinue) { msd::channel channel; msd::blocking_iterator> it{channel}; From d634328610ccf2f10872a1e3925e082fae935c73 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:40:34 +0300 Subject: [PATCH 8/8] Set version --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9825ad..c50a038 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.12) project(cpp_channel) -set(PROJECT_VERSION 1.0.1) +set(PROJECT_VERSION 1.1.0) set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard") set(CMAKE_CXX_STANDARD_REQUIRED YES) diff --git a/README.md b/README.md index 2cc1403..8a35ef4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Choose one of the methods: * [CMake FetchContent](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples/cmake-project) * [CMake install](https://cmake.org/cmake/help/latest/command/install.html) ```shell -VERSION=1.0.1 \ +VERSION=1.1.0 \ && wget https://github.com/andreiavrammsd/cpp-channel/archive/refs/tags/v$VERSION.zip \ && unzip v$VERSION.zip \ && cd cpp-channel-$VERSION \