├── .github └── workflows │ └── cmake.yaml ├── .gitignore ├── .idea └── .gitignore ├── CMakeLists.txt ├── Channel.hpp ├── LICENSE ├── README.md ├── benchmark ├── CMakeLists.txt ├── benchmark.cmake └── benchmark.cpp ├── example.cpp └── tests ├── CMakeLists.txt ├── gtest.cmake └── test.cpp /.github/workflows/cmake.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ "main", "develop"] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | ubuntu-build: 11 | name: "Ubuntu GCC" 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | config: 18 | - { 19 | build_type: "Release", 20 | } 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Create Build Environment 26 | # Some projects don't allow in-source building, so create a separate build directory 27 | # We'll use this as our working directory for all subsequent commands 28 | run: cmake -E make_directory ${{github.workspace}}/build 29 | 30 | - name: Configure CMake 31 | # Use a bash shell so we can use the same syntax for environment variable 32 | # access regardless of the host operating system 33 | shell: bash 34 | working-directory: ${{github.workspace}}/build 35 | # Note the current convention is to use the -S and -B options here to specify source 36 | # and build directories, but this is only available with CMake 3.13 and higher. 37 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 38 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -Dbuild_test:BOOL=ON -Dbuild_benchmark:BOOL=ON -DCMAKE_C_COMPILER=/usr/bin/gcc-12 -DCMAKE_CXX_COMPILER=/usr/bin/g++-12 39 | 40 | - name: Build 41 | working-directory: ${{github.workspace}}/build 42 | shell: bash 43 | # Execute the build. You can specify a specific target with "--target " 44 | run: cmake --build . --config ${{ matrix.config.build_type }} 45 | 46 | - name: Test 47 | working-directory: ${{github.workspace}}/build 48 | shell: bash 49 | # Execute tests defined by the CMake configuration. 50 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 51 | run: ctest -C ${{ matrix.config.build_type }} --verbose 52 | 53 | mac-build: 54 | name: "macOS Clang" 55 | runs-on: macos-latest 56 | 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | config: 61 | - { 62 | build_type: "Release", 63 | } 64 | 65 | steps: 66 | - uses: actions/checkout@v2 67 | - name: Set Compiler 68 | run: | 69 | brew install llvm@16 70 | 71 | - name: Create Build Environment 72 | # Some projects don't allow in-source building, so create a separate build directory 73 | # We'll use this as our working directory for all subsequent commands 74 | run: cmake -E make_directory ${{github.workspace}}/build 75 | 76 | - name: Configure CMake 77 | # Use a bash shell so we can use the same syntax for environment variable 78 | # access regardless of the host operating system 79 | shell: bash 80 | working-directory: ${{github.workspace}}/build 81 | # Note the current convention is to use the -S and -B options here to specify source 82 | # and build directories, but this is only available with CMake 3.13 and higher. 83 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 84 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -Dbuild_test:BOOL=ON -Dbuild_benchmark:BOOL=ON -DCMAKE_C_COMPILER=$(brew --prefix llvm@16)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@16)/bin/clang++ 85 | 86 | - name: Build 87 | working-directory: ${{github.workspace}}/build 88 | shell: bash 89 | # Execute the build. You can specify a specific target with "--target " 90 | run: cmake --build . --config ${{ matrix.config.build_type }} 91 | 92 | - name: Test 93 | working-directory: ${{github.workspace}}/build 94 | shell: bash 95 | # Execute tests defined by the CMake configuration. 96 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 97 | run: ctest -C ${{ matrix.config.build_type }} --verbose 98 | 99 | windows-build: 100 | name: "Windows MSVC" 101 | runs-on: windows-latest 102 | 103 | strategy: 104 | fail-fast: false 105 | matrix: 106 | config: 107 | - { 108 | build_type: "Release", 109 | } 110 | 111 | steps: 112 | - uses: actions/checkout@v2 113 | 114 | - name: Create Build Environment 115 | # Some projects don't allow in-source building, so create a separate build directory 116 | # We'll use this as our working directory for all subsequent commands 117 | run: cmake -E make_directory ${{github.workspace}}/build 118 | 119 | - name: Configure CMake 120 | # Use a bash shell so we can use the same syntax for environment variable 121 | # access regardless of the host operating system 122 | shell: bash 123 | working-directory: ${{github.workspace}}/build 124 | # Note the current convention is to use the -S and -B options here to specify source 125 | # and build directories, but this is only available with CMake 3.13 and higher. 126 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 127 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -Dbuild_test:BOOL=ON -Dbuild_benchmark:BOOL=ON 128 | 129 | - name: Build 130 | working-directory: ${{github.workspace}}/build 131 | shell: bash 132 | # Execute the build. You can specify a specific target with "--target " 133 | run: cmake --build . --config ${{ matrix.config.build_type }} 134 | 135 | - name: Test 136 | working-directory: ${{github.workspace}}/build 137 | shell: bash 138 | # Execute tests defined by the CMake configuration. 139 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 140 | run: ctest -C ${{ matrix.config.build_type }} --verbose 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | cmake-build-debug/_deps/googletest-subbuild 34 | cmake-build-debug 35 | .idea 36 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(Channel) 3 | 4 | set(CMAKE_CXX_STANDARD 23) 5 | 6 | option(build_test "build test" OFF) 7 | option(build_benchmark "build benchmark" OFF) 8 | 9 | if (build_test) 10 | message(STATUS "build test") 11 | enable_testing() 12 | include(tests/gtest.cmake) 13 | add_subdirectory(tests) 14 | endif() 15 | 16 | if (build_benchmark) 17 | message(STATUS "build benchmark") 18 | include(benchmark/benchmark.cmake) 19 | add_subdirectory(benchmark) 20 | endif() 21 | 22 | add_executable(Channel example.cpp Channel.hpp) 23 | -------------------------------------------------------------------------------- /Channel.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nevermore on 2023/11/25. 3 | // 4 | // Copyright (c) 2023 Nevermore All rights reserved. 5 | // 6 | 7 | #ifndef ASYNC_CHANNEL 8 | #define ASYNC_CHANNEL 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace Async { 21 | 22 | template 23 | struct IsSTLStringImpl : public std::false_type {}; 24 | 25 | template 26 | struct IsSTLStringImpl> : public std::true_type {}; 27 | 28 | template 29 | inline constexpr auto IsSTLString = IsSTLStringImpl::value; 30 | 31 | template 32 | concept IsRange = std::ranges::viewable_range && 33 | (!IsSTLString) && 34 | requires(C&& c) { 35 | std::forward(c).empty(); 36 | typename decltype(c.begin())::value_type; 37 | }; 38 | 39 | template 40 | requires requires(I&&) { 41 | typename std::remove_cvref_t::element_type::ValueType; 42 | } 43 | struct GetChannelTypeImpl { 44 | using type = std::remove_cvref_t::element_type::ValueType; 45 | }; 46 | 47 | template 48 | using GetChannelType = typename GetChannelTypeImpl::type; 49 | 50 | template 51 | class Sender; 52 | 53 | template 54 | class Receiver; 55 | 56 | template 57 | struct IsSenderImpl : public std::false_type {}; 58 | 59 | template 60 | struct IsSenderImpl> : public std::true_type {}; 61 | 62 | template 63 | inline constexpr auto IsSender = IsSenderImpl::value; 64 | 65 | template 66 | concept IsSenderPtr = requires(T&& t) { 67 | typename T::element_type::ValueType; 68 | IsSender; 69 | }; 70 | 71 | template 72 | struct IsReceiverImpl : public std::false_type {}; 73 | 74 | template 75 | struct IsReceiverImpl> : public std::true_type {}; 76 | 77 | template 78 | inline constexpr auto IsReceiver = IsReceiverImpl::value; 79 | 80 | template 81 | concept IsReceiverPtr = requires(T&& t) { 82 | typename T::element_type::ValueType; 83 | IsReceiver; 84 | }; 85 | 86 | template 87 | struct SenderAdaptorClosure { 88 | private: 89 | S& sender_; 90 | public: 91 | constexpr explicit SenderAdaptorClosure(S& sender) 92 | : sender_(sender) { 93 | } 94 | 95 | template 96 | friend auto operator|(InputView&& lhs, SenderAdaptorClosure self) { 97 | auto res = self.sender_->send(std::forward(lhs)); 98 | return std::ranges::single_view(res); 99 | } 100 | }; 101 | 102 | struct SendView { 103 | template 104 | auto operator()(S& sender) const { 105 | return SenderAdaptorClosure(sender); 106 | } 107 | }; 108 | 109 | template 110 | using SenderPtr = std::unique_ptr>; 111 | 112 | template 113 | using SenderRefPtr = std::shared_ptr>; 114 | 115 | template 116 | using ReceiverPtr = std::unique_ptr>; 117 | 118 | enum class ChannelEventType { 119 | Unknown, 120 | Success [[maybe_unused]], 121 | Closed, 122 | None, 123 | }; 124 | 125 | template > > 126 | requires (std::movable) 127 | class Channel { 128 | public: 129 | friend class Sender; 130 | 131 | friend class Receiver; 132 | 133 | static auto create() noexcept -> std::tuple, ReceiverPtr> { 134 | auto p = std::shared_ptr>(new Channel(), [](Channel* channel) { 135 | delete channel; 136 | }); 137 | return { std::make_unique>(p), std::make_unique>(p) }; 138 | } 139 | 140 | inline auto done() noexcept -> void { 141 | std::lock_guard lock(mutex_); 142 | if (!isClosed_) { 143 | isClosed_ = true; 144 | cond_.notify_all(); 145 | } 146 | } 147 | 148 | [[nodiscard]] inline auto isDone() noexcept -> bool { 149 | std::lock_guard lock(mutex_); 150 | return isClosed_; 151 | } 152 | 153 | Channel(const Channel&) = delete; 154 | 155 | Channel(Channel&&) = delete; 156 | 157 | auto operator=(const Channel&) -> Channel& = delete; 158 | 159 | auto operator=(Channel&&) -> Channel& = delete; 160 | 161 | private: 162 | Channel() = default; 163 | 164 | ~Channel() = default; 165 | 166 | private: 167 | bool isClosed_ = false; 168 | std::list messages_; 169 | std::mutex mutex_; 170 | std::condition_variable cond_; 171 | }; 172 | 173 | 174 | template 175 | class Sender final { 176 | public: 177 | using ValueType = T; 178 | explicit Sender(std::shared_ptr> channel) 179 | : channel_(channel) { 180 | 181 | } 182 | 183 | ~Sender() { 184 | if (!channel_->isClosed_) { 185 | done(); 186 | } 187 | channel_.reset(); 188 | } 189 | 190 | template 191 | inline auto emplace(Args&&... args) -> bool 192 | requires std::constructible_from { 193 | if (!channel_->isClosed_) { 194 | std::lock_guard lock(channel_->mutex_); 195 | channel_->messages_.emplace_back(std::forward(args)...); 196 | return true; 197 | } 198 | return false; 199 | } 200 | 201 | template 202 | inline auto send(const I& box) noexcept -> bool 203 | requires IsRange && std::copyable && std::is_convertible_v { 204 | if (box.empty()) { 205 | return true; 206 | } 207 | std::lock_guard lock(channel_->mutex_); 208 | if (!channel_->isClosed_) { 209 | for (const auto& v : std::views::all(box)) { 210 | channel_->messages_.push_back(v); 211 | } 212 | channel_->cond_.notify_one(); 213 | return true; 214 | } 215 | return false; 216 | } 217 | 218 | template 219 | inline auto send(I&& box) noexcept -> bool 220 | requires IsRange && std::is_convertible_v { 221 | if (box.empty()) { 222 | return true; 223 | } 224 | std::lock_guard lock(channel_->mutex_); 225 | if (!channel_->isClosed_ && !box.empty()) { 226 | if constexpr (std::is_rvalue_reference_v) { 227 | std::move(box.begin(), box.end(), std::back_inserter(channel_->messages_)); 228 | } else { 229 | for (const auto& v : std::views::all(box)) { 230 | channel_->messages_.push_back(v); 231 | } 232 | } 233 | channel_->cond_.notify_one(); 234 | return true; 235 | } 236 | return false; 237 | } 238 | 239 | template 240 | inline auto send(const U& message) noexcept -> bool 241 | requires std::copy_constructible && std::is_convertible_v { 242 | std::lock_guard lock(channel_->mutex_); 243 | if (!channel_->isClosed_) { 244 | channel_->messages_.push_back(message); 245 | channel_->cond_.notify_one(); 246 | return true; 247 | } 248 | return false; 249 | } 250 | 251 | template 252 | inline auto send(U&& message) noexcept -> bool 253 | requires std::is_convertible_v { 254 | std::lock_guard lock(channel_->mutex_); 255 | if (!channel_->isClosed_) { 256 | channel_->messages_.emplace_back(std::forward(message)); 257 | channel_->cond_.notify_one(); 258 | return true; 259 | } 260 | return false; 261 | } 262 | 263 | inline auto done() noexcept -> void { 264 | channel_->done(); 265 | } 266 | 267 | [[nodiscard]] inline auto isDone() noexcept -> bool { 268 | return channel_->isDone(); 269 | } 270 | 271 | private: 272 | std::shared_ptr> channel_; 273 | }; 274 | 275 | namespace _detail { 276 | auto closedHandler = []{ 277 | throw std::runtime_error("can't send message to closed channel."); 278 | }; 279 | } 280 | 281 | template 282 | auto operator<<(const IsSenderPtr auto& sender, U&& message) -> const IsSenderPtr auto& 283 | requires std::movable && std::is_convertible_v> { 284 | if (!sender->send(std::forward(message))) { 285 | _detail::closedHandler(); 286 | } 287 | return sender; 288 | } 289 | 290 | template 291 | auto operator<<(const IsSenderPtr auto& sender, const U& message) -> const IsSenderPtr auto& 292 | requires std::copyable && std::is_convertible_v> { 293 | if (!sender->send(message)) { 294 | _detail::closedHandler(); 295 | } 296 | return sender; 297 | } 298 | 299 | template 300 | auto operator<<(const IsSenderPtr auto& sender, const U& box) -> const IsSenderPtr auto& 301 | requires IsRange && std::is_convertible_v> { 302 | if (!sender->send(box)) { 303 | _detail::closedHandler(); 304 | } 305 | return sender; 306 | } 307 | 308 | template 309 | auto operator<<(const IsSenderPtr auto& sender, U&& box) -> const IsSenderPtr auto& 310 | requires IsRange && std::is_convertible_v> { 311 | if (!sender->send(std::forward(box))) { 312 | _detail::closedHandler(); 313 | } 314 | return sender; 315 | } 316 | 317 | template 318 | class ReceiverIterator; 319 | 320 | template 321 | class ConstReceiverIterator; 322 | 323 | template 324 | class Receiver final { 325 | private: 326 | friend class ReceiverIterator; 327 | friend class ConstReceiverIterator; 328 | private: 329 | class ReceiverImpl { 330 | public: 331 | friend class Channel; 332 | 333 | friend class ReceiverIterator; 334 | 335 | explicit ReceiverImpl(std::shared_ptr> channel) 336 | : channel_(channel) { 337 | 338 | } 339 | 340 | ~ReceiverImpl() { 341 | channel_->done(); 342 | } 343 | 344 | inline auto operator=(const ReceiverImpl& src) noexcept -> ReceiverImpl& { 345 | channel_ = src.channel_; 346 | } 347 | 348 | ReceiverImpl(const ReceiverImpl& src) noexcept 349 | : channel_(src.channel_) { 350 | 351 | } 352 | 353 | inline auto operator=(ReceiverImpl&& src) noexcept -> ReceiverImpl& { 354 | channel_ = std::move(src.channel_); 355 | } 356 | 357 | ReceiverImpl(ReceiverImpl&& src) noexcept 358 | : channel_(std::move(src.channel_)) { 359 | 360 | } 361 | 362 | inline auto receive() noexcept -> std::expected 363 | requires std::movable { 364 | std::unique_lock lock(channel_->mutex_); 365 | channel_->cond_.wait(lock, [this] { 366 | return !channel_->messages_.empty() || channel_->isClosed_; 367 | }); 368 | if (channel_->isClosed_ && channel_->messages_.empty()) { 369 | return std::unexpected(ChannelEventType::Closed); 370 | } 371 | auto value = std::move(channel_->messages_.front()); 372 | channel_->messages_.pop_front(); 373 | return value; 374 | } 375 | 376 | inline auto tryReceive() noexcept -> std::expected { 377 | std::unique_lock lock(channel_->mutex_); 378 | if (channel_->messages_.empty()) { 379 | ChannelEventType type = ChannelEventType::Unknown; 380 | if (channel_->isClosed_) { 381 | type = ChannelEventType::Closed; 382 | } else { 383 | type = ChannelEventType::None; 384 | } 385 | return std::unexpected(type); 386 | } 387 | auto value = std::move(channel_->messages_.front()); 388 | channel_->messages_.pop_front(); 389 | return value; 390 | } 391 | 392 | inline auto tryReceiveAll() -> std::list { 393 | decltype(channel_->messages_) messages; 394 | { 395 | std::lock_guard lock(channel_->mutex_); 396 | messages.swap(channel_->messages_); 397 | } 398 | return messages; 399 | } 400 | 401 | private: 402 | std::shared_ptr> channel_; 403 | };//end of class ReceiverImpl 404 | 405 | std::shared_ptr impl_; 406 | 407 | public: 408 | using ValueType = T; 409 | explicit Receiver(std::shared_ptr> channel) 410 | : impl_(std::make_shared(channel)) { 411 | 412 | } 413 | 414 | ~Receiver() noexcept = default; 415 | 416 | inline auto operator=(const Receiver& src) -> Receiver& { 417 | impl_ = src.impl_; 418 | } 419 | 420 | Receiver(const Receiver& src) noexcept 421 | : impl_(src.impl_) { 422 | 423 | } 424 | 425 | inline auto operator=(Receiver&& src) noexcept -> Receiver& { 426 | impl_ = std::move(src.impl_); 427 | } 428 | 429 | Receiver(Receiver&& src) noexcept 430 | : impl_(std::move(src.impl_)) { 431 | 432 | } 433 | 434 | inline auto begin() noexcept -> ReceiverIterator { 435 | auto it = ReceiverIterator(impl_, ChannelEventType::Unknown); 436 | it.value_ = std::move(impl_->receive()); 437 | return it; 438 | } 439 | 440 | inline auto end() noexcept -> ReceiverIterator { 441 | return ReceiverIterator(impl_, ChannelEventType::Closed); 442 | } 443 | 444 | inline auto receive() noexcept -> std::expected { 445 | return impl_->receive(); 446 | } 447 | 448 | inline auto tryReceive() noexcept -> std::expected { 449 | return impl_->tryReceive(); 450 | } 451 | 452 | inline auto tryReceiveAll() noexcept -> std::list { 453 | return impl_->tryReceiveAll(); 454 | } 455 | 456 | }; //end of class Receiver 457 | 458 | template 459 | auto operator>>(const IsReceiverPtr auto& receiver, U& value) -> const IsReceiverPtr auto& 460 | requires std::movable && std::is_convertible_v , U> { 461 | auto res = receiver->receive(); 462 | if (res.has_value()) { 463 | value = std::move(*res); 464 | } else if (res.error() == ChannelEventType::Closed) { 465 | _detail::closedHandler(); 466 | } 467 | return receiver; 468 | } 469 | 470 | template 471 | auto operator>>(const IsReceiverPtr auto& receiver, U& value) -> const IsReceiverPtr auto& 472 | requires std::is_copy_assignable_v && (!std::movable) && std::is_convertible_v, U> { 473 | auto res = receiver->receive(); 474 | if (res.has_value()) { 475 | value = *res; 476 | } else if (res.error() == ChannelEventType::Closed) { 477 | _detail::closedHandler(); 478 | } 479 | return receiver; 480 | } 481 | 482 | 483 | template 484 | class ReceiverIterator { 485 | friend class Receiver; 486 | protected: 487 | std::shared_ptr::ReceiverImpl> receiverImpl_; 488 | mutable std::expected value_; 489 | 490 | public: 491 | using iterator_category = std::input_iterator_tag; 492 | using value_type = T; 493 | using difference_type = std::ptrdiff_t; 494 | using pointer = T*; 495 | using reference = T&; 496 | 497 | explicit ReceiverIterator(std::shared_ptr::ReceiverImpl> receiverImpl, 498 | ChannelEventType eventType = ChannelEventType::Unknown) 499 | : receiverImpl_(receiverImpl) 500 | , value_(std::unexpected(eventType)) { 501 | 502 | } 503 | 504 | ReceiverIterator() 505 | : receiverImpl_(nullptr) 506 | , value_(std::unexpected(ChannelEventType::Closed)) { 507 | 508 | } 509 | 510 | [[maybe_unused]] ReceiverIterator(const ReceiverIterator& src) noexcept 511 | requires std::copyable 512 | : receiverImpl_(src.receiverImpl_) 513 | , value_(src.value_) { 514 | } 515 | 516 | inline auto operator=(const ReceiverIterator& src) noexcept -> ReceiverIterator& { 517 | receiverImpl_ = src.receiverImpl_; 518 | value_ = src.value_; 519 | return *this; 520 | } 521 | 522 | inline auto operator*() const noexcept -> reference { 523 | return *value_; 524 | } 525 | 526 | inline auto operator++() noexcept -> ReceiverIterator& { 527 | value_ = std::move(receiverImpl_->receive()); 528 | return *this; 529 | } 530 | 531 | inline auto operator++(int) noexcept -> ReceiverIterator { 532 | auto temp = std::move(*this); 533 | *this = ReceiverIterator(receiverImpl_); 534 | this->value_ = receiverImpl_->receive(); 535 | return temp; 536 | } 537 | 538 | inline auto operator==(const ReceiverIterator& iter) const noexcept -> bool { 539 | const auto& lvalue = value_; 540 | const auto& rvalue = iter.value_; 541 | if (lvalue.has_value() && rvalue.has_value()) { 542 | return &lvalue.value() == &rvalue.value(); 543 | } 544 | if (lvalue.has_value() || rvalue.has_value()) { 545 | return false; 546 | } 547 | return lvalue.error() == rvalue.error(); 548 | } 549 | 550 | inline auto operator!=(const ReceiverIterator& iter) const noexcept -> bool { 551 | return !operator==(iter); 552 | } 553 | }; //end of class ReceiverIterator 554 | 555 | } //end of namespace Async 556 | 557 | namespace std::ranges::views { // NOLINT(cert-dcl58-cpp) 558 | inline constexpr Async::SendView sender; 559 | } 560 | 561 | #endif //end if ASYNC_CHANNEL -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nevermore1994 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cpp-Channel 2 | 3 | [![build](https://github.com/Nevermore1994/Cpp-Channel/workflows/build/badge.svg)](https://github.com/Nevermore1994/Cpp-Channel/actions) 4 | 5 | ### Thread-safe container for sharing data between threads. Header-only. 6 | 7 | * Thread-safe push and fetch. 8 | * Use stream operators to push (<<) and fetch (>>) items. 9 | * Support Batch operations. 10 | * Support Ranges 11 | * No blocking or blocking fetch. 12 | * Range-based for loop supported. 13 | * If either the sender or receiver is released, the channel will be closed. 14 | * Integrates well with STL algorithms in some cases. Eg: std::move(rp->begin(), rp->end(), ...). 15 | * Tested with GCC(gcc 13), Clang(LLVM 16 and XCode15.0), and MSVC(Visual Studio 2022). 16 | 17 | ## Requirements 18 | 19 | * C++23 or newer 20 | 21 | ## Installation 22 | 23 | You just need to include the header file [Channel.hpp](Channel.hpp) 24 | 25 | ## Usage 26 | 27 | * Create 28 | ```c++ 29 | auto [sp, rp] = Channel::create(); //any movable type or support bit copy type 30 | ``` 31 | 32 | * Send 33 | ```c++ 34 | sp->send(0); 35 | sp << 1; 36 | sp << 2 << 3; 37 | 38 | //batch operations 39 | std::vector nums {1, 2, 3, 4, 5, 6, 7}; 40 | sp << nums; 41 | // or sp->send(nums); 42 | 43 | //use ranges 44 | sp << (nums | std::views::take(3)); 45 | // or sp->send(nums | std::views::take(3)); 46 | 47 | //use | operator 48 | for (bool res : nums | std::views::drop(4) | std::views::sender(sp)) { 49 | //if p = true, send success 50 | std::cout << "send result:" << std::boolalpha << res << std::endl; 51 | } 52 | 53 | //close 54 | sp->done(); //or sp.reset(); 55 | ``` 56 | 57 | * Receive 58 | ```c++ 59 | auto t = *(rp->receive()); 60 | int t1, t2; 61 | rp >> t1 >> t2; 62 | //use for range, blocking 63 | for (const auto& res : *rp) { 64 | std::cout << res << std::endl; 65 | } 66 | 67 | //use ranges, blocking 68 | for (const auto& num : *rp | std::views::filter([](const auto& num) { 69 | return num % 2 == 0; 70 | })) { 71 | std::cout << num << std::endl; 72 | } 73 | 74 | //use STL algorithm, blocking 75 | std::vector values; 76 | std::move(rp->begin(), rp->end(), std::back_inserter(values)); 77 | 78 | //no blocking 79 | auto res = rp->tryReceive(); 80 | //no blocking 81 | for (const auto& res : rp->tryReceiveAll()) { 82 | std::cout << res << std::endl; 83 | } 84 | 85 | //can use STL or Ranges 86 | for (const auto& res : rp->tryReceiveAll() | std::transform([](const auto& num) { 87 | return num + 3; 88 | })) { 89 | std::cout << res << std::endl; 90 | } 91 | ``` 92 | 93 | For specific usage, please refer to [example.cpp](./example.cpp) 94 | 95 | If you cannot support C++23, you can refer to [cpp-channel][def] 96 | 97 | [def]: https://github.com/andreiavrammsd/cpp-channel 98 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | add_executable(channel_benchmark benchmark.cpp) 4 | target_link_libraries(channel_benchmark benchmark::benchmark) 5 | -------------------------------------------------------------------------------- /benchmark/benchmark.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | include(FetchContent) 4 | FetchContent_Declare(benchmark 5 | GIT_REPOSITORY https://github.com/google/benchmark.git 6 | GIT_TAG v1.8.3 7 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/benchmark 8 | ) 9 | 10 | FetchContent_MakeAvailable(benchmark) 11 | -------------------------------------------------------------------------------- /benchmark/benchmark.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nevermore on 2023/11/24. 3 | // Channel benchmark 4 | // Copyright (c) 2023 Nevermore All rights reserved. 5 | // 6 | #include 7 | #include "../Channel.hpp" 8 | 9 | static void ChannelWithInt(benchmark::State& state) 10 | { 11 | auto [sp, rp] = Async::Channel::create(); 12 | int in = 1; 13 | for (auto _ : state) { 14 | sp << in; 15 | auto res = rp->receive(); 16 | } 17 | } 18 | 19 | BENCHMARK(ChannelWithInt); 20 | 21 | static void ChannelWithString(benchmark::State& state) 22 | { 23 | auto [sp, rp] = Async::Channel::create(); 24 | std::string in = "1"; 25 | for (auto _ : state) { 26 | sp << in; 27 | auto res = rp->receive(); 28 | } 29 | } 30 | 31 | BENCHMARK(ChannelWithString); 32 | 33 | struct TestValue { 34 | int value = 0; 35 | std::string key = "key"; 36 | }; 37 | 38 | static void ChannelWithStruct(benchmark::State& state) 39 | { 40 | auto [sp, rp] = Async::Channel::create(); 41 | TestValue in; 42 | for (auto _ : state) { 43 | sp << in; 44 | auto res = rp->receive(); 45 | } 46 | } 47 | 48 | BENCHMARK(ChannelWithStruct); 49 | 50 | BENCHMARK_MAIN(); 51 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Channel.hpp" 8 | 9 | using namespace Async; 10 | using namespace std::string_literals; 11 | 12 | int32_t randomCommon(int32_t min, int32_t max) { 13 | std::random_device rd; 14 | std::mt19937 gen(rd()); 15 | std::uniform_int_distribution dis(min, max); 16 | return dis(gen); 17 | } 18 | 19 | auto randomId() -> int32_t { 20 | return randomCommon(0, INT32_MAX); 21 | } 22 | 23 | auto randomAge() -> int32_t { 24 | return randomCommon(0, 99); 25 | } 26 | 27 | auto timestamp() -> int64_t { 28 | using namespace std::chrono; 29 | return time_point_cast(system_clock::now()).time_since_epoch().count(); 30 | } 31 | 32 | struct People { 33 | int64_t timestamp = 0; 34 | int age = 0; 35 | int id = 0; 36 | 37 | People(int64_t t,int age_, int id_) 38 | : timestamp(t) 39 | , age(age_) 40 | , id(id_) { 41 | 42 | } 43 | 44 | People() { 45 | 46 | } 47 | }; 48 | 49 | int main() { 50 | using type = std::unordered_map; 51 | using namespace std::chrono_literals; 52 | auto [sp, rp] = Channel::create(); 53 | std::thread t([rp = std::move(rp)] { 54 | //use for range 55 | People p; 56 | rp >> p; 57 | for (auto& people : *rp) { 58 | std::cout << " receive interval:" << (timestamp() - people.timestamp) 59 | << "ns, age:" << people.age << ", id:" << people.id << std::endl; 60 | } 61 | 62 | //use ranges 63 | for (const auto& people : *rp | std::views::filter([](const auto& ex) { 64 | return ex.id % 2 == 0; 65 | }) | std::views::transform([](auto& ex) { 66 | ex.id += 4; 67 | return ex; 68 | }) ) { 69 | std::cout << " receive interval:" << (timestamp() - people.timestamp) << "ns, age:" << people.age << ", id:" << people.id << std::endl; 70 | } 71 | 72 | //can use STL algorithm 73 | std::vector values; 74 | std::for_each(rp->begin(), rp->end(), [](auto& people) { 75 | std::cout << " receive interval:" << (timestamp() - people.timestamp) << "ns, age:" << people.age << ", id:" 76 | << people.id << std::endl; 77 | }); 78 | }); 79 | 80 | //send single message 81 | sp->send(People{timestamp(), randomAge(), randomId()}); 82 | sp << People{timestamp(), randomAge(), randomId()} << (People{timestamp(), randomAge(), randomId()}); 83 | std::this_thread::sleep_for(1s); 84 | //send multi message 85 | std::vector peoples; 86 | peoples.reserve(10); 87 | for(int i = 0; i < 4; i++) { 88 | peoples.emplace_back(timestamp(), randomAge(), randomId()); 89 | } 90 | sp << peoples; 91 | //emplace back 92 | sp->emplace(timestamp(), randomAge(), randomId()); 93 | 94 | peoples.clear(); 95 | for(int i = 0; i < 10; i++) { 96 | peoples.emplace_back(timestamp(), randomAge(), randomId()); 97 | } 98 | std::this_thread::sleep_for(1s); 99 | //can use ranges 100 | sp << (peoples | std::views::take(3)); // << higher priority than | 101 | for (bool p : peoples | std::views::drop(4) | std::views::sender(sp)) { 102 | //p = true, send success 103 | std::cout << "send ranges:" << std::boolalpha << p << std::endl; 104 | } 105 | sp->done(); 106 | t.join(); 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | include(GoogleTest) 4 | 5 | set(THREADS_PREFER_PTHREAD_FLAG ON) 6 | find_package(Threads REQUIRED) 7 | 8 | add_executable(channel_test test.cpp) 9 | 10 | target_link_libraries(channel_test 11 | gtest 12 | gtest_main 13 | ) 14 | 15 | gtest_add_tests(TARGET channel_test) -------------------------------------------------------------------------------- /tests/gtest.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | # Google Test settings 4 | include(FetchContent) 5 | FetchContent_Declare(googletest 6 | GIT_REPOSITORY https://github.com/google/googletest.git 7 | GIT_TAG release-1.11.0 8 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest 9 | ) 10 | 11 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 12 | FetchContent_MakeAvailable(googletest) 13 | 14 | -------------------------------------------------------------------------------- /tests/test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nevermore on 2023/11/21. 3 | // Channel test 4 | // Copyright (c) 2023 Nevermore All rights reserved. 5 | // 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include "../Channel.hpp" 12 | 13 | using namespace Async; 14 | using namespace std::literals; 15 | 16 | TEST(ChannelTest, CheckType) { 17 | using type = std::string; 18 | auto [sp, rp] = Channel::create(); 19 | EXPECT_TRUE((std::is_same_v, type>)); 20 | EXPECT_TRUE((std::is_same_v, type>)); 21 | auto ssp = SenderRefPtr(std::move(sp)); 22 | EXPECT_TRUE((std::is_same_v, type>)); 23 | } 24 | 25 | TEST(ChannelTest, PtrSendSingleMessage) { 26 | using type = int; 27 | auto [sp, rp] = Channel::create(); 28 | std::thread t1([rp = std::move(rp)]{ 29 | type value = 0; 30 | int t1, t2; 31 | rp >> t1 >> t2; 32 | EXPECT_EQ(t1, value); 33 | value++; 34 | EXPECT_EQ(t2, value); 35 | value++; 36 | for(;;) { 37 | if (auto res = rp->receive(); res.has_value()) { 38 | EXPECT_EQ(*res, value); 39 | value++; 40 | } else { 41 | break; 42 | } 43 | } 44 | }); 45 | for (std::weakly_incrementable auto i : std::views::iota(0,10)) { 46 | sp << i; 47 | } 48 | sp->done(); 49 | t1.join(); 50 | } 51 | 52 | TEST(ChannelTest, Receive) { 53 | using type = std::string; 54 | auto [sp, rp] = Channel::create(); 55 | std::thread t1([rp = std::move(rp)]{ 56 | int value = 0; 57 | for(;;) { 58 | if (auto res = rp->receive(); res.has_value()) { 59 | EXPECT_EQ(*res, std::to_string(value)); 60 | value++; 61 | } else { 62 | break; 63 | } 64 | } 65 | }); 66 | for(std::weakly_incrementable auto i : std::views::iota(0, 10)) { 67 | sp << std::to_string(i); 68 | } 69 | sp->done(); 70 | t1.join(); 71 | } 72 | 73 | TEST(ChannelTest, PtrSendMultiMessage) { 74 | using type = int; 75 | auto [sp, rp] = Channel::create(); 76 | std::thread t1([rp = std::move(rp)]{ 77 | type value = 0; 78 | for(;;) { 79 | if (auto res = rp->receive(); res.has_value()) { 80 | EXPECT_EQ(*res, value); 81 | value++; 82 | } else { 83 | break; 84 | } 85 | } 86 | }); 87 | std::vector nums(10); 88 | std::iota(nums.begin(), nums.end(), 0); 89 | nums | std::views::take(5) | std::views::sender(sp); 90 | sp << std::move(std::vector{5, 6, 7, 8, 9}) << std::vector{10, 11}; 91 | sp->done(); 92 | t1.join(); 93 | } 94 | 95 | TEST(ChannelTest, RefPtrSendMultiMessage) { 96 | using type = int; 97 | auto [sp, rp] = Channel::create(); 98 | auto ssp = SenderRefPtr(std::move(sp)); 99 | std::thread t1([rp = std::move(rp)]{ 100 | type value = 0; 101 | for(;;) { 102 | if (auto res = rp->receive(); res.has_value()) { 103 | EXPECT_EQ(*res, value); 104 | value++; 105 | } else { 106 | break; 107 | } 108 | } 109 | }); 110 | std::vector nums(10); 111 | std::iota(nums.begin(), nums.end(), 0); 112 | nums | std::views::take(5) | std::views::sender(ssp); 113 | ssp << std::move(std::vector{5, 6, 7, 8, 9}) << std::list{10, 11}; 114 | ssp->done(); 115 | t1.join(); 116 | } 117 | 118 | TEST(ChannelTest, Close) { 119 | { 120 | using type = std::string; 121 | auto [sp, rp] = Channel::create(); 122 | sp->done(); 123 | EXPECT_EQ(sp->isDone(), true); 124 | EXPECT_FALSE(sp->send("123"s)); 125 | } 126 | 127 | { 128 | using type = std::string; 129 | auto [sp, rp] = Channel::create(); 130 | rp.reset(); 131 | EXPECT_EQ(sp->isDone(), true); 132 | EXPECT_FALSE(sp->send("xyz"s)); 133 | } 134 | 135 | { 136 | using type = std::string; 137 | auto [sp, rp] = Channel::create(); 138 | sp->done(); 139 | EXPECT_THROW(sp << "123"s, std::runtime_error); 140 | std::string s; 141 | EXPECT_THROW(rp >> s, std::runtime_error); 142 | } 143 | } 144 | 145 | TEST(ChannelTest, Range) { 146 | using type = int; 147 | auto [sp, rp] = Channel::create(); 148 | std::thread t1([rp = std::move(rp)]{ 149 | type value = 0; 150 | for(auto res : *rp) { 151 | EXPECT_EQ(res, value); 152 | value++; 153 | } 154 | }); 155 | std::vector nums(10); 156 | std::iota(nums.begin(), nums.end(), 0); 157 | nums | std::views::take(5) | std::views::sender(sp); 158 | sp << std::move(std::vector{5, 6, 7, 8, 9}) << std::list{10, 11}; 159 | sp->done(); 160 | t1.join(); 161 | } 162 | 163 | TEST(ChannelTest, MultiThread) { 164 | using type = int; 165 | auto [sp, rp] = Channel::create(); 166 | auto ssp = SenderRefPtr (std::move(sp)); 167 | std::condition_variable cond; 168 | std::mutex mutex; 169 | int count = 0; 170 | std::thread t1([ssp, &cond, &mutex, &count]{ 171 | for(;;) { 172 | std::unique_lock lock(mutex); 173 | cond.wait(lock, [&count, &ssp]{ 174 | return (count % 3) == 0 || ssp->isDone(); 175 | }); 176 | if (ssp->isDone()) { 177 | //std::cout << "--- MultiThread test t1 exit ---" << std::endl; 178 | return; 179 | } 180 | ssp << count; 181 | count++; 182 | if (count > 99) { 183 | ssp->done(); 184 | } 185 | cond.notify_all(); 186 | } 187 | }); 188 | std::thread t2([ssp, &cond, &mutex, &count]{ 189 | for(;;) { 190 | std::unique_lock lock(mutex); 191 | cond.wait(lock, [&count, &ssp]{ 192 | return (count % 3) == 1 || ssp->isDone(); 193 | }); 194 | if (ssp->isDone()) { 195 | //std::cout << "--- MultiThread test t2 exit ---" << std::endl; 196 | return; 197 | } 198 | ssp << count; 199 | count++; 200 | if (count > 99) { 201 | ssp->done(); 202 | } 203 | cond.notify_all(); 204 | } 205 | }); 206 | std::thread t3([ssp, &cond, &mutex, &count]{ 207 | for(;;) { 208 | std::unique_lock lock(mutex); 209 | cond.wait(lock, [&count, &ssp]{ 210 | return (count % 3) == 2 || ssp->isDone(); 211 | }); 212 | if (ssp->isDone()) { 213 | //std::cout << "--- MultiThread test t3 exit ---" << std::endl; 214 | return; 215 | } 216 | ssp << count; 217 | count++; 218 | if (count > 99) { 219 | ssp->done(); 220 | } 221 | cond.notify_all(); 222 | } 223 | }); 224 | std::thread t4([rp = std::move(rp)]{ 225 | type value = 0; 226 | for(;;) { 227 | if (auto res = rp->tryReceive(); res.has_value()) { 228 | EXPECT_EQ(*res, value); 229 | value++; 230 | } else if (res.error() == ChannelEventType::Closed){ 231 | break; 232 | } 233 | } 234 | //std::cout << "--- MultiThread test t4 exit ---" << std::endl; 235 | }); 236 | t1.join(); 237 | t2.join(); 238 | t3.join(); 239 | t4.join(); 240 | } 241 | 242 | struct A { 243 | int value = 0; 244 | virtual ~A() = default; 245 | A() = default; 246 | A(int value_) 247 | : value(value_) { 248 | 249 | } 250 | }; 251 | 252 | struct TestValue: public A { 253 | TestValue() = default; 254 | ~TestValue() override = default; 255 | explicit TestValue(const std::string& key_) 256 | : key(key_) { 257 | 258 | } 259 | 260 | TestValue(TestValue&& rhs) noexcept 261 | : A(rhs.value) 262 | , key(std::move(rhs.key)) { 263 | 264 | } 265 | 266 | TestValue& operator=(TestValue&& rhs) noexcept { 267 | value = rhs.value; 268 | key = std::move(key); 269 | return *this; 270 | } 271 | TestValue(const TestValue&) = default; 272 | TestValue& operator=(const TestValue& rhs) = default; 273 | std::string key = "key"; 274 | }; 275 | 276 | TEST(ChannelTest, BitwiseCopy) { 277 | using type = TestValue; 278 | auto [sp, rp] = Channel::create(); 279 | std::thread t1([rp = std::move(rp)]{ 280 | int value = 0; 281 | for(auto& res : *rp) { 282 | EXPECT_EQ(res.value, value); 283 | EXPECT_EQ(res.key, std::to_string(value)); 284 | value++; 285 | } 286 | }); 287 | TestValue a; 288 | a.key = std::to_string(0); 289 | sp << std::move(a); 290 | sp->done(); 291 | t1.join(); 292 | } 293 | 294 | TEST(ChannelTest, ImplicitConversion) { 295 | using type = A; 296 | auto [sp, rp] = Channel::create(); 297 | std::thread t1([rp = std::move(rp)]{ 298 | int value = 0; 299 | for(auto res : *rp) { 300 | auto k = static_cast(res); 301 | EXPECT_EQ(k->value, value); 302 | EXPECT_EQ(k->key, std::to_string(value)); 303 | value++; 304 | delete res; 305 | } 306 | }); 307 | std::vector values; 308 | auto value = new TestValue(); 309 | value->value = 0; 310 | value->key = std::to_string(0); 311 | sp << value; 312 | for (int i = 1; i < 10; i++) { 313 | auto tmepValue = new TestValue(); 314 | tmepValue->value = i; 315 | tmepValue->key = std::to_string(i); 316 | values.emplace_back(tmepValue); 317 | } 318 | sp << std::move(values); 319 | sp->done(); 320 | t1.join(); 321 | } --------------------------------------------------------------------------------