├── README.md ├── channel.h ├── example ├── Makefile ├── main.cc └── main2.cc ├── waitgroup.cc └── waitgroup.h /README.md: -------------------------------------------------------------------------------- 1 | # C++ Channel & WaitGroup 2 | 3 | An implementation of Channel & WaitGroup inspired by Go. 4 | 5 | __WARNING:__ Don't use it in production (see: https://github.com/dragonquest/cpp-channels/issues/1) 6 | 7 | ## Example (Channel & WaitGroup in action) 8 | 9 | Examples can be found in the directory "./example": 10 | 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "../waitgroup.h" 18 | #include "../channel.h" 19 | 20 | int main() { 21 | Channel chan; 22 | WaitGroup wg; 23 | wg.Add(2); 24 | 25 | std::thread first([&]() { 26 | for (int i = 0; i < 8; i++) { 27 | // Sending the message to the channel: 28 | chan << "from thread first: hello"; 29 | std::this_thread::sleep_for(std::chrono::seconds(1)); 30 | chan.Send("from thread first: world"); 31 | } 32 | wg.Done(); 33 | }); 34 | 35 | std::thread second([&]() { 36 | for (int i = 0; i < 5; i++) { 37 | chan.Send("from thread second: hello"); 38 | std::this_thread::sleep_for(std::chrono::seconds(1)); 39 | chan.Send("from thread second: world"); 40 | } 41 | wg.Done(); 42 | }); 43 | 44 | // checker waits for all threads to finish their 45 | // work and closes the channel. 46 | std::thread checker([&]() { 47 | wg.Wait(); 48 | chan.Close(); 49 | }); 50 | 51 | while (1) { 52 | auto item = chan.TryRecv(); 53 | if (!item) { 54 | break; 55 | } 56 | std::cout << *item << "\n"; 57 | } 58 | 59 | std::cout << "All work has been done\n"; 60 | 61 | first.join(); 62 | second.join(); 63 | checker.join(); 64 | 65 | return 0; 66 | } 67 | ``` 68 | 69 | Output: 70 | 71 | ``` 72 | $ ./main2 73 | from thread first: hello 74 | from thread second: hello 75 | from thread first: world 76 | from thread first: hello 77 | from thread second: world 78 | from thread second: hello 79 | from thread first: world 80 | from thread second: world 81 | from thread first: hello 82 | from thread second: hello 83 | from thread first: world 84 | from thread second: world 85 | from thread first: hello 86 | from thread second: hello 87 | from thread second: world 88 | from thread first: world 89 | from thread second: hello 90 | from thread first: hello 91 | from thread second: world 92 | from thread first: world 93 | from thread first: hello 94 | from thread first: world 95 | from thread first: hello 96 | from thread first: world 97 | from thread first: hello 98 | from thread first: world 99 | All work has been done 100 | ``` 101 | 102 | In order to compile the examples simply go to the './example' directory and enter ```make```. 103 | 104 | ## Links 105 | 106 | * [Go Channels](https://golang.org/ref/spec#Channel_types) 107 | * [Go WaitGroup](https://golang.org/pkg/sync/#WaitGroup) 108 | 109 | ## Motivation 110 | 111 | This is one of my first C++ library and I wrote it mainly to learn more about C++. 112 | -------------------------------------------------------------------------------- /channel.h: -------------------------------------------------------------------------------- 1 | #ifndef CHANNEL_ 2 | #define CHANNEL_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class Channel { 11 | public: 12 | bool Send(T item) { 13 | std::lock_guard l(mu_); 14 | 15 | // FIXME(an): Use maybe DCHECK from glog -> DCHECK(closed_) 16 | if (closed_) { 17 | return false; 18 | } 19 | 20 | list_.push(item); 21 | cv_.notify_all(); 22 | return true; 23 | } 24 | 25 | std::experimental::optional TryRecv() { 26 | std::unique_lock l(mu_); 27 | 28 | cv_.wait(l, [&] { return list_.size() > 0 || closed_; }); 29 | 30 | if (list_.empty()) { 31 | return {}; 32 | } 33 | 34 | T entry = list_.front(); 35 | list_.pop(); 36 | 37 | return std::experimental::make_optional(entry); 38 | } 39 | 40 | void operator<<(T entry) { Send(entry); } 41 | 42 | void Close() { 43 | std::lock_guard l(mu_); 44 | cv_.notify_all(); 45 | closed_ = true; 46 | } 47 | 48 | bool Finished() { 49 | std::lock_guard l(mu_); 50 | return closed_ && list_.empty(); 51 | } 52 | 53 | class Iterator { 54 | public: 55 | explicit Iterator(Channel* chan) : chan_(chan) {} 56 | 57 | std::experimental::optional operator*() { 58 | return chan_->TryRecv(); 59 | } 60 | 61 | void operator++() { 62 | // does nothing on purpose since this 63 | // iterator cannot advance. 64 | } 65 | 66 | bool operator!=(Iterator it) { 67 | return !chan_->Finished(); 68 | } 69 | 70 | private: 71 | Channel* chan_; 72 | }; 73 | 74 | Iterator begin() { 75 | return Iterator(this); 76 | } 77 | 78 | Iterator end() { 79 | return Iterator(this); 80 | } 81 | 82 | private: 83 | std::queue list_; 84 | std::mutex mu_; 85 | std::condition_variable cv_; 86 | bool closed_ = false; 87 | }; 88 | 89 | #endif // CHANNEL_ 90 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | clang++ -Wall -stdlib=libc++ -std=c++1z -o main main.cc ../waitgroup.cc -ggdb -O0 4 | clang++ -Wall -stdlib=libc++ -std=c++1z -o main2 main2.cc ../waitgroup.cc -ggdb -O0 5 | 6 | clean: 7 | rm main 8 | rm -rf main.dSYM/ 9 | rm main2 10 | rm -rf main2.dSYM/ 11 | -------------------------------------------------------------------------------- /example/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../waitgroup.h" 8 | #include "../channel.h" 9 | 10 | struct Result { 11 | int result; 12 | int id; // of the worker who provided the result 13 | }; 14 | 15 | // Worker receives a number from the jobs/input channel and calculates +1, 16 | // then it sends the calculated result to the results channel 17 | void Worker(int id, Channel* jobs, Channel* results, WaitGroup* wg) { 18 | for(auto job : *jobs) { 19 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 20 | 21 | auto val = *job; // XXX(an): optional.value() is not impl at the time of coding :(- 22 | Result result{val + 1, id}; 23 | results->Send(result); 24 | } 25 | wg->Done(); 26 | return; 27 | } 28 | 29 | int main() { 30 | Channel jobs; 31 | Channel results; 32 | WaitGroup wg; 33 | std::vector workers; 34 | 35 | // Spawning 20 Workers. 36 | for (int i = 1; i <= 20; i++) { 37 | wg.Add(1); 38 | 39 | std::thread([&, i]() { 40 | Worker(i, &jobs, &results, &wg); 41 | }).detach(); 42 | } 43 | 44 | // Sending work to the Worker: 45 | for (int i = 1; i <= 5000; i++) { 46 | jobs.Send(i); 47 | } 48 | jobs.Close(); 49 | 50 | // Waiting for all threads to finish its 51 | // work and then close the results channel. 52 | std::thread([&]() { 53 | wg.Wait(); 54 | results.Close(); 55 | }).detach(); 56 | 57 | while (1) { 58 | auto result = results.TryRecv(); 59 | 60 | if (!result) { 61 | break; 62 | } 63 | 64 | auto resp = result; 65 | std::cout << "Result: " << resp->result << " from #" << resp->id << "\n"; 66 | } 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /example/main2.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../waitgroup.h" 7 | #include "../channel.h" 8 | 9 | int main() { 10 | Channel chan; 11 | WaitGroup wg; 12 | wg.Add(2); 13 | 14 | std::thread first([&]() { 15 | for (int i = 0; i < 8; i++) { 16 | // Sending the message to the channel: 17 | chan << "from thread first: hello"; 18 | std::this_thread::sleep_for(std::chrono::seconds(1)); 19 | chan.Send("from thread first: world"); 20 | } 21 | wg.Done(); 22 | }); 23 | 24 | std::thread second([&]() { 25 | for (int i = 0; i < 5; i++) { 26 | chan.Send("from thread second: hello"); 27 | std::this_thread::sleep_for(std::chrono::seconds(1)); 28 | chan.Send("from thread second: world"); 29 | } 30 | wg.Done(); 31 | }); 32 | 33 | // checker waits for all threads to finish their 34 | // work and closes the channel. 35 | std::thread checker([&]() { 36 | wg.Wait(); 37 | chan.Close(); 38 | }); 39 | 40 | while (1) { 41 | auto item = chan.TryRecv(); 42 | if (!item) { 43 | break; 44 | } 45 | std::cout << *item << "\n"; 46 | } 47 | 48 | std::cout << "All work has been done\n"; 49 | 50 | first.join(); 51 | second.join(); 52 | checker.join(); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /waitgroup.cc: -------------------------------------------------------------------------------- 1 | #include "waitgroup.h" 2 | 3 | void WaitGroup::Add(int i) { 4 | counter_ += i; 5 | } 6 | 7 | void WaitGroup::Done() { 8 | counter_--; 9 | cv_.notify_all(); 10 | } 11 | 12 | void WaitGroup::Wait() { 13 | std::unique_lock l(mu_); 14 | cv_.wait(l, [&] { return counter_ <= 0; }); 15 | } 16 | -------------------------------------------------------------------------------- /waitgroup.h: -------------------------------------------------------------------------------- 1 | #ifndef WAITGROUP_ 2 | #define WAITGROUP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class WaitGroup { 9 | public: 10 | void Add(int i = 1); 11 | void Done(); 12 | void Wait(); 13 | 14 | private: 15 | std::mutex mu_; 16 | std::atomic counter_; 17 | std::condition_variable cv_; 18 | }; 19 | 20 | #endif // WAITGROUP_ 21 | --------------------------------------------------------------------------------