Skip to content

Commit a2c25d2

Browse files
Static channel (#69)
* Add static channel * Add CPP_CHANNEL_SANITIZE_THREADS * Simplify conditions * Extract nodiscard * Set version
1 parent 65a88c5 commit a2c25d2

13 files changed

Lines changed: 554 additions & 29 deletions

.github/workflows/cmake.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ jobs:
7676

7777
- name: Build
7878
working-directory: ${{github.workspace}}/build
79-
run: cmake --build . --config ${{ matrix.config.build_type }} --target tests -j
79+
run: cmake --build . --config ${{ matrix.config.build_type }} --target channel_tests -j
8080

8181
- name: Test
8282
working-directory: ${{github.workspace}}/build
83-
run: ctest -C ${{ matrix.config.build_type }} --verbose -R channel_test --output-on-failure -j
83+
run: ctest -C ${{ matrix.config.build_type }} --verbose -L channel_tests --output-on-failure -j
8484

8585
- name: Run examples
8686
working-directory: ${{github.workspace}}/build
@@ -102,11 +102,11 @@ jobs:
102102

103103
- name: Build
104104
working-directory: ${{github.workspace}}/build
105-
run: cmake --build . --config Debug --target tests -j
105+
run: cmake --build . --config Debug --target channel_tests -j
106106

107107
- name: Test
108108
working-directory: ${{github.workspace}}/build
109-
run: ctest -C Debug --verbose -R channel_test -j
109+
run: ctest -C Debug --verbose -L channel_tests -j
110110

111111
- name: Upload coverage reports to Codecov
112112
uses: codecov/codecov-action@v5

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"-DCPP_CHANNEL_BUILD_TESTS=ON",
55
"-DCPP_CHANNEL_COVERAGE=ON",
66
"-DCPP_CHANNEL_SANITIZERS=ON",
7+
"-DCPP_CHANNEL_SANITIZE_THREADS=OFF",
78
"-DCMAKE_CXX_STANDARD=11",
89
"-DCMAKE_INSTALL_PREFIX=${workspaceFolder}/install",
910
],

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.12)
22
project(cpp_channel)
3-
set(PROJECT_VERSION 1.0.1)
3+
set(PROJECT_VERSION 1.1.0)
44

55
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard")
66
set(CMAKE_CXX_STANDARD_REQUIRED YES)
@@ -18,6 +18,7 @@ option(CPP_CHANNEL_BUILD_TESTS "Build all of cpp_channel's own tests." OFF)
1818
option(CPP_CHANNEL_BUILD_EXAMPLES "Build cpp_channel's example programs." OFF)
1919
option(CPP_CHANNEL_COVERAGE "Generate test coverage." OFF)
2020
option(CPP_CHANNEL_SANITIZERS "Build with sanitizers." OFF)
21+
option(CPP_CHANNEL_SANITIZE_THREADS "Build with thread sanitizer." OFF)
2122

2223
if (CPP_CHANNEL_BUILD_TESTS)
2324
enable_testing()

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Close to prevent pushing and stop waiting to fetch.
1414
* Integrates well with STL algorithms in some cases. Eg: std::move(ch.begin(), ch.end(), ...).
1515
* Tested with GCC, Clang, and MSVC.
16+
* Includes stack-based, exception-free alternative (static channel).
1617

1718
## Requirements
1819

@@ -26,7 +27,7 @@ Choose one of the methods:
2627
* [CMake FetchContent](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples/cmake-project)
2728
* [CMake install](https://cmake.org/cmake/help/latest/command/install.html)
2829
```shell
29-
VERSION=1.0.1 \
30+
VERSION=1.1.0 \
3031
&& wget https://github.com/andreiavrammsd/cpp-channel/archive/refs/tags/v$VERSION.zip \
3132
&& unzip v$VERSION.zip \
3233
&& cd cpp-channel-$VERSION \
@@ -111,6 +112,26 @@ int main() {
111112
}
112113
```
113114

115+
```c++
116+
#include <msd/static_channel.hpp>
117+
118+
int main() {
119+
msd::static_channel<int, 2> chan{}; // always buffered
120+
121+
int in = 1;
122+
int out = 0;
123+
124+
// Send to channel
125+
chan.write(in);
126+
chan.write(in);
127+
128+
// Read from channel
129+
chan.read(out);
130+
chan.read(out);
131+
chan.read(out); // blocking because channel is empty (and no one writes on it)
132+
}
133+
```
134+
114135
See [examples](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples) and [documentation](https://andreiavrammsd.github.io/cpp-channel/).
115136

116137
<br>

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ add_example(example_multithreading multithreading.cpp)
3636

3737
add_example(example_streaming streaming.cpp)
3838
run_example(example_streaming)
39+
40+
add_example(example_multithreading_static_channel multithreading_static_channel.cpp)
41+
run_example(example_multithreading_static_channel)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <future>
2+
#include <iostream>
3+
#include <sstream>
4+
#include <thread>
5+
6+
#include "msd/static_channel.hpp"
7+
8+
int main()
9+
{
10+
msd::static_channel<int, 50> chan{}; // always buffered
11+
12+
// Send to channel
13+
const auto writer = [&chan](int begin, int end) {
14+
for (int i = begin; i <= end; ++i) {
15+
chan.write(i);
16+
17+
std::stringstream msg;
18+
msg << "Sent " << i << " from " << std::this_thread::get_id() << "\n";
19+
std::cout << msg.str();
20+
21+
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // simulate work
22+
}
23+
chan.close();
24+
};
25+
26+
const auto reader = [&chan]() {
27+
for (const auto out : chan) { // blocking until channel is drained (closed and empty)
28+
std::stringstream msg;
29+
msg << "Received " << out << " on " << std::this_thread::get_id() << "\n";
30+
std::cout << msg.str();
31+
32+
std::this_thread::sleep_for(std::chrono::milliseconds(200)); // simulate work
33+
}
34+
};
35+
36+
const auto reader_1 = std::async(std::launch::async, reader);
37+
const auto reader_2 = std::async(std::launch::async, reader);
38+
const auto reader_3 = std::async(std::launch::async, reader);
39+
const auto writer_1 = std::async(std::launch::async, writer, 1, 50);
40+
const auto writer_2 = std::async(std::launch::async, writer, 51, 100);
41+
42+
reader_1.wait();
43+
reader_2.wait();
44+
reader_3.wait();
45+
writer_1.wait();
46+
writer_2.wait();
47+
}

include/msd/blocking_iterator.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class blocking_iterator {
6464
*/
6565
reference operator*()
6666
{
67-
chan_ >> value_;
67+
chan_.read(value_);
6868

6969
return value_;
7070
}

include/msd/channel.hpp

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,10 @@
1111
#include <type_traits>
1212

1313
#include "blocking_iterator.hpp"
14+
#include "nodiscard.hpp"
1415

1516
namespace msd {
1617

17-
#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
18-
#define NODISCARD [[nodiscard]]
19-
#else
20-
#define NODISCARD
21-
#endif
22-
2318
/**
2419
* @brief Exception thrown if trying to write on closed channel.
2520
*/
@@ -134,11 +129,9 @@ class channel {
134129
return false;
135130
}
136131

137-
if (!(size_ == 0)) {
138-
out = std::move(queue_.front());
139-
queue_.pop();
140-
--size_;
141-
}
132+
out = std::move(queue_.front());
133+
queue_.pop();
134+
--size_;
142135
}
143136

144137
cnd_.notify_one();
@@ -202,7 +195,7 @@ class channel {
202195
NODISCARD bool drained() noexcept
203196
{
204197
std::unique_lock<std::mutex> lock{mtx_};
205-
return is_closed_ && size_ == 0;
198+
return size_ == 0 && is_closed_;
206199
}
207200

208201
/**
@@ -238,7 +231,7 @@ class channel {
238231

239232
void waitBeforeRead(std::unique_lock<std::mutex>& lock)
240233
{
241-
cnd_.wait(lock, [this]() { return !(size_ == 0) || is_closed_; });
234+
cnd_.wait(lock, [this]() { return size_ > 0 || is_closed_; });
242235
};
243236

244237
void waitBeforeWrite(std::unique_lock<std::mutex>& lock)
@@ -247,8 +240,6 @@ class channel {
247240
cnd_.wait(lock, [this]() { return size_ < cap_; });
248241
}
249242
}
250-
251-
friend class blocking_iterator<channel>;
252243
};
253244

254245
template <typename T>

include/msd/nodiscard.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (C) 2020-2025 Andrei Avram
2+
3+
#ifndef MSD_NODISCARD_HPP_
4+
#define MSD_NODISCARD_HPP_
5+
6+
namespace msd {
7+
8+
#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
9+
#define NODISCARD [[nodiscard]]
10+
#else
11+
#define NODISCARD
12+
#endif
13+
14+
} // namespace msd
15+
16+
#endif // MSD_NODISCARD_HPP_

0 commit comments

Comments
 (0)