From 6e45d144317416ec845b8a08cd5bc6e92cf19187 Mon Sep 17 00:00:00 2001 From: Bo Wang Date: Tue, 22 Apr 2014 21:54:16 -0400 Subject: [PATCH] add original source --- source/CCiA_SourceCode.zip | Bin 0 -> 65025 bytes source/LICENSE_1_0.txt | 23 +++++ source/listing_1.1.cpp | 13 +++ source/listing_10.1.cpp | 42 +++++++++ source/listing_2.1.cpp | 35 +++++++ source/listing_2.2.cpp | 46 +++++++++ source/listing_2.3.cpp | 58 ++++++++++++ source/listing_2.4.cpp | 61 ++++++++++++ source/listing_2.5.cpp | 27 ++++++ source/listing_2.6.cpp | 56 +++++++++++ source/listing_2.7.cpp | 23 +++++ source/listing_2.8.cpp | 67 +++++++++++++ source/listing_3.1.cpp | 26 ++++++ source/listing_3.10.cpp | 27 ++++++ source/listing_3.11.cpp | 28 ++++++ source/listing_3.12.cpp | 56 +++++++++++ source/listing_3.13.cpp | 30 ++++++ source/listing_3.2.cpp | 44 +++++++++ source/listing_3.3.cpp | 24 +++++ source/listing_3.4.cpp | 24 +++++ source/listing_3.5.cpp | 68 ++++++++++++++ source/listing_3.6.cpp | 29 ++++++ source/listing_3.7.cpp | 64 +++++++++++++ source/listing_3.8.cpp | 56 +++++++++++ source/listing_3.9.cpp | 29 ++++++ source/listing_4.1.cpp | 64 +++++++++++++ source/listing_4.10.cpp | 27 ++++++ source/listing_4.11.cpp | 18 ++++ source/listing_4.12.cpp | 24 +++++ source/listing_4.13.cpp | 23 +++++ source/listing_4.14.cpp | 12 +++ source/listing_4.15.cpp | 43 +++++++++ source/listing_4.16.cpp | 31 ++++++ source/listing_4.2.cpp | 30 ++++++ source/listing_4.3.cpp | 22 +++++ source/listing_4.4.cpp | 58 ++++++++++++ source/listing_4.5.cpp | 73 +++++++++++++++ source/listing_4.6.cpp | 16 ++++ source/listing_4.7.cpp | 34 +++++++ source/listing_4.8.cpp | 9 ++ source/listing_4.9.cpp | 40 ++++++++ source/listing_5.1.cpp | 16 ++++ source/listing_5.10.cpp | 39 ++++++++ source/listing_5.11.cpp | 41 ++++++++ source/listing_5.12.cpp | 33 +++++++ source/listing_5.13.cpp | 34 +++++++ source/listing_5.2.cpp | 22 +++++ source/listing_5.3.cpp | 17 ++++ source/listing_5.4.cpp | 46 +++++++++ source/listing_5.5.cpp | 31 ++++++ source/listing_5.6.cpp | 74 +++++++++++++++ source/listing_5.7.cpp | 46 +++++++++ source/listing_5.8.cpp | 31 ++++++ source/listing_5.9.cpp | 42 +++++++++ source/listing_6.1.cpp | 56 +++++++++++ source/listing_6.10.cpp | 44 +++++++++ source/listing_6.11.cpp | 108 +++++++++++++++++++++ source/listing_6.12.cpp | 20 ++++ source/listing_6.13.cpp | 101 ++++++++++++++++++++ source/listing_6.2.cpp | 72 ++++++++++++++ source/listing_6.3.cpp | 68 ++++++++++++++ source/listing_6.4.cpp | 56 +++++++++++ source/listing_6.5.cpp | 46 +++++++++ source/listing_6.6.cpp | 64 +++++++++++++ source/listing_6.7.cpp | 29 ++++++ source/listing_6.8.cpp | 15 +++ source/listing_6.9.cpp | 49 ++++++++++ source/listing_7.1.cpp | 18 ++++ source/listing_7.10.cpp | 38 ++++++++ source/listing_7.11.cpp | 46 +++++++++ source/listing_7.12.cpp | 88 +++++++++++++++++ source/listing_7.13.cpp | 62 ++++++++++++ source/listing_7.14.cpp | 20 ++++ source/listing_7.15.cpp | 59 ++++++++++++ source/listing_7.16.cpp | 31 ++++++ source/listing_7.17.cpp | 27 ++++++ source/listing_7.18.cpp | 20 ++++ source/listing_7.19.cpp | 27 ++++++ source/listing_7.2.cpp | 23 +++++ source/listing_7.20.cpp | 33 +++++++ source/listing_7.21.cpp | 55 +++++++++++ source/listing_7.3.cpp | 31 ++++++ source/listing_7.4.cpp | 25 +++++ source/listing_7.5.cpp | 57 +++++++++++ source/listing_7.6.cpp | 36 +++++++ source/listing_7.7.cpp | 49 ++++++++++ source/listing_7.8.cpp | 51 ++++++++++ source/listing_7.9.cpp | 32 +++++++ source/listing_8.1.cpp | 102 ++++++++++++++++++++ source/listing_8.10.cpp | 45 +++++++++ source/listing_8.11.cpp | 93 ++++++++++++++++++ source/listing_8.12.cpp | 24 +++++ source/listing_8.13.cpp | 98 +++++++++++++++++++ source/listing_8.2.cpp | 49 ++++++++++ source/listing_8.3.cpp | 56 +++++++++++ source/listing_8.4.cpp | 44 +++++++++ source/listing_8.5.cpp | 20 ++++ source/listing_8.6.cpp | 50 ++++++++++ source/listing_8.7.cpp | 44 +++++++++ source/listing_8.8.cpp | 24 +++++ source/listing_8.9.cpp | 76 +++++++++++++++ source/listing_9.1.cpp | 53 +++++++++++ source/listing_9.10.cpp | 9 ++ source/listing_9.11.cpp | 57 +++++++++++ source/listing_9.12.cpp | 77 +++++++++++++++ source/listing_9.13.cpp | 38 ++++++++ source/listing_9.2.cpp | 62 ++++++++++++ source/listing_9.3.cpp | 31 ++++++ source/listing_9.4.cpp | 12 +++ source/listing_9.5.cpp | 58 ++++++++++++ source/listing_9.6.cpp | 58 ++++++++++++ source/listing_9.7.cpp | 53 +++++++++++ source/listing_9.8.cpp | 112 ++++++++++++++++++++++ source/listing_9.9.cpp | 31 ++++++ source/listing_a.1.cpp | 23 +++++ source/listing_a.2.cpp | 18 ++++ source/listing_a.3.cpp | 23 +++++ source/listing_a.4.cpp | 8 ++ source/listing_c.1.cpp | 50 ++++++++++ source/listing_c.10.cpp | 51 ++++++++++ source/listing_c.2.cpp | 22 +++++ source/listing_c.3.cpp | 16 ++++ source/listing_c.4.cpp | 65 +++++++++++++ source/listing_c.5.cpp | 71 ++++++++++++++ source/listing_c.6.cpp | 118 +++++++++++++++++++++++ source/listing_c.7.cpp | 187 +++++++++++++++++++++++++++++++++++++ source/listing_c.8.cpp | 73 +++++++++++++++ source/listing_c.9.cpp | 117 +++++++++++++++++++++++ 128 files changed, 5726 insertions(+) create mode 100644 source/CCiA_SourceCode.zip create mode 100644 source/LICENSE_1_0.txt create mode 100644 source/listing_1.1.cpp create mode 100644 source/listing_10.1.cpp create mode 100644 source/listing_2.1.cpp create mode 100644 source/listing_2.2.cpp create mode 100644 source/listing_2.3.cpp create mode 100644 source/listing_2.4.cpp create mode 100644 source/listing_2.5.cpp create mode 100644 source/listing_2.6.cpp create mode 100644 source/listing_2.7.cpp create mode 100644 source/listing_2.8.cpp create mode 100644 source/listing_3.1.cpp create mode 100644 source/listing_3.10.cpp create mode 100644 source/listing_3.11.cpp create mode 100644 source/listing_3.12.cpp create mode 100644 source/listing_3.13.cpp create mode 100644 source/listing_3.2.cpp create mode 100644 source/listing_3.3.cpp create mode 100644 source/listing_3.4.cpp create mode 100644 source/listing_3.5.cpp create mode 100644 source/listing_3.6.cpp create mode 100644 source/listing_3.7.cpp create mode 100644 source/listing_3.8.cpp create mode 100644 source/listing_3.9.cpp create mode 100644 source/listing_4.1.cpp create mode 100644 source/listing_4.10.cpp create mode 100644 source/listing_4.11.cpp create mode 100644 source/listing_4.12.cpp create mode 100644 source/listing_4.13.cpp create mode 100644 source/listing_4.14.cpp create mode 100644 source/listing_4.15.cpp create mode 100644 source/listing_4.16.cpp create mode 100644 source/listing_4.2.cpp create mode 100644 source/listing_4.3.cpp create mode 100644 source/listing_4.4.cpp create mode 100644 source/listing_4.5.cpp create mode 100644 source/listing_4.6.cpp create mode 100644 source/listing_4.7.cpp create mode 100644 source/listing_4.8.cpp create mode 100644 source/listing_4.9.cpp create mode 100644 source/listing_5.1.cpp create mode 100644 source/listing_5.10.cpp create mode 100644 source/listing_5.11.cpp create mode 100644 source/listing_5.12.cpp create mode 100644 source/listing_5.13.cpp create mode 100644 source/listing_5.2.cpp create mode 100644 source/listing_5.3.cpp create mode 100644 source/listing_5.4.cpp create mode 100644 source/listing_5.5.cpp create mode 100644 source/listing_5.6.cpp create mode 100644 source/listing_5.7.cpp create mode 100644 source/listing_5.8.cpp create mode 100644 source/listing_5.9.cpp create mode 100644 source/listing_6.1.cpp create mode 100644 source/listing_6.10.cpp create mode 100644 source/listing_6.11.cpp create mode 100644 source/listing_6.12.cpp create mode 100644 source/listing_6.13.cpp create mode 100644 source/listing_6.2.cpp create mode 100644 source/listing_6.3.cpp create mode 100644 source/listing_6.4.cpp create mode 100644 source/listing_6.5.cpp create mode 100644 source/listing_6.6.cpp create mode 100644 source/listing_6.7.cpp create mode 100644 source/listing_6.8.cpp create mode 100644 source/listing_6.9.cpp create mode 100644 source/listing_7.1.cpp create mode 100644 source/listing_7.10.cpp create mode 100644 source/listing_7.11.cpp create mode 100644 source/listing_7.12.cpp create mode 100644 source/listing_7.13.cpp create mode 100644 source/listing_7.14.cpp create mode 100644 source/listing_7.15.cpp create mode 100644 source/listing_7.16.cpp create mode 100644 source/listing_7.17.cpp create mode 100644 source/listing_7.18.cpp create mode 100644 source/listing_7.19.cpp create mode 100644 source/listing_7.2.cpp create mode 100644 source/listing_7.20.cpp create mode 100644 source/listing_7.21.cpp create mode 100644 source/listing_7.3.cpp create mode 100644 source/listing_7.4.cpp create mode 100644 source/listing_7.5.cpp create mode 100644 source/listing_7.6.cpp create mode 100644 source/listing_7.7.cpp create mode 100644 source/listing_7.8.cpp create mode 100644 source/listing_7.9.cpp create mode 100644 source/listing_8.1.cpp create mode 100644 source/listing_8.10.cpp create mode 100644 source/listing_8.11.cpp create mode 100644 source/listing_8.12.cpp create mode 100644 source/listing_8.13.cpp create mode 100644 source/listing_8.2.cpp create mode 100644 source/listing_8.3.cpp create mode 100644 source/listing_8.4.cpp create mode 100644 source/listing_8.5.cpp create mode 100644 source/listing_8.6.cpp create mode 100644 source/listing_8.7.cpp create mode 100644 source/listing_8.8.cpp create mode 100644 source/listing_8.9.cpp create mode 100644 source/listing_9.1.cpp create mode 100644 source/listing_9.10.cpp create mode 100644 source/listing_9.11.cpp create mode 100644 source/listing_9.12.cpp create mode 100644 source/listing_9.13.cpp create mode 100644 source/listing_9.2.cpp create mode 100644 source/listing_9.3.cpp create mode 100644 source/listing_9.4.cpp create mode 100644 source/listing_9.5.cpp create mode 100644 source/listing_9.6.cpp create mode 100644 source/listing_9.7.cpp create mode 100644 source/listing_9.8.cpp create mode 100644 source/listing_9.9.cpp create mode 100644 source/listing_a.1.cpp create mode 100644 source/listing_a.2.cpp create mode 100644 source/listing_a.3.cpp create mode 100644 source/listing_a.4.cpp create mode 100644 source/listing_c.1.cpp create mode 100644 source/listing_c.10.cpp create mode 100644 source/listing_c.2.cpp create mode 100644 source/listing_c.3.cpp create mode 100644 source/listing_c.4.cpp create mode 100644 source/listing_c.5.cpp create mode 100644 source/listing_c.6.cpp create mode 100644 source/listing_c.7.cpp create mode 100644 source/listing_c.8.cpp create mode 100644 source/listing_c.9.cpp diff --git a/source/CCiA_SourceCode.zip b/source/CCiA_SourceCode.zip new file mode 100644 index 0000000000000000000000000000000000000000..5c95659e4c06d1578439d6429772b25c33a93bac GIT binary patch literal 65025 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 OJz~0XBV_{e>;D7$p`r@_ literal 0 HcmV?d00001 diff --git a/source/LICENSE_1_0.txt b/source/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/source/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/source/listing_1.1.cpp b/source/listing_1.1.cpp new file mode 100644 index 0000000..92170a8 --- /dev/null +++ b/source/listing_1.1.cpp @@ -0,0 +1,13 @@ +#include +#include + +void hello() +{ + std::cout<<"Hello Concurrent World\n"; +} + +int main() +{ + std::thread t(hello); + t.join(); +} diff --git a/source/listing_10.1.cpp b/source/listing_10.1.cpp new file mode 100644 index 0000000..865b94b --- /dev/null +++ b/source/listing_10.1.cpp @@ -0,0 +1,42 @@ +void test_concurrent_push_and_pop_on_empty_queue() +{ + threadsafe_queue q; + + std::promise go,push_ready,pop_ready; + std::shared_future ready(go.get_future()); + + std::future push_done; + std::future pop_done; + + try + { + push_done=std::async(std::launch::async, + [&q,ready,&push_ready]() + { + push_ready.set_value(); + ready.wait(); + q.push(42); + } + ); + pop_done=std::async(std::launch::async, + [&q,ready,&pop_ready]() + { + pop_ready.set_value(); + ready.wait(); + return q.pop(); + } + ); + push_ready.get_future().wait(); + pop_ready.get_future().wait(); + go.set_value(); + + push_done.get(); + assert(pop_done.get()==42); + assert(q.empty()); + } + catch(...) + { + go.set_value(); + throw; + } +} diff --git a/source/listing_2.1.cpp b/source/listing_2.1.cpp new file mode 100644 index 0000000..45d2193 --- /dev/null +++ b/source/listing_2.1.cpp @@ -0,0 +1,35 @@ +#include + +void do_something(int& i) +{ + ++i; +} + +struct func +{ + int& i; + + func(int& i_):i(i_){} + + void operator()() + { + for(unsigned j=0;j<1000000;++j) + { + do_something(i); + } + } +}; + + +void oops() +{ + int some_local_state=0; + func my_func(some_local_state); + std::thread my_thread(my_func); + my_thread.detach(); +} + +int main() +{ + oops(); +} diff --git a/source/listing_2.2.cpp b/source/listing_2.2.cpp new file mode 100644 index 0000000..c952752 --- /dev/null +++ b/source/listing_2.2.cpp @@ -0,0 +1,46 @@ +#include + +void do_something(int& i) +{ + ++i; +} + +struct func +{ + int& i; + + func(int& i_):i(i_){} + + void operator()() + { + for(unsigned j=0;j<1000000;++j) + { + do_something(i); + } + } +}; + +void do_something_in_current_thread() +{} + +void f() +{ + int some_local_state=0; + func my_func(some_local_state); + std::thread t(my_func); + try + { + do_something_in_current_thread(); + } + catch(...) + { + t.join(); + throw; + } + t.join(); +} + +int main() +{ + f(); +} diff --git a/source/listing_2.3.cpp b/source/listing_2.3.cpp new file mode 100644 index 0000000..5c48223 --- /dev/null +++ b/source/listing_2.3.cpp @@ -0,0 +1,58 @@ +#include + +class thread_guard +{ + std::thread& t; +public: + explicit thread_guard(std::thread& t_): + t(t_) + {} + ~thread_guard() + { + if(t.joinable()) + { + t.join(); + } + } + thread_guard(thread_guard const&)=delete; + thread_guard& operator=(thread_guard const&)=delete; +}; + +void do_something(int& i) +{ + ++i; +} + +struct func +{ + int& i; + + func(int& i_):i(i_){} + + void operator()() + { + for(unsigned j=0;j<1000000;++j) + { + do_something(i); + } + } +}; + +void do_something_in_current_thread() +{} + + +void f() +{ + int some_local_state; + func my_func(some_local_state); + std::thread t(my_func); + thread_guard g(t); + + do_something_in_current_thread(); +} + +int main() +{ + f(); +} diff --git a/source/listing_2.4.cpp b/source/listing_2.4.cpp new file mode 100644 index 0000000..702afa7 --- /dev/null +++ b/source/listing_2.4.cpp @@ -0,0 +1,61 @@ +#include +#include + +void open_document_and_display_gui(std::string const& filename) +{} + +bool done_editing() +{ + return true; +} + +enum command_type{ + open_new_document +}; + + +struct user_command +{ + command_type type; + + user_command(): + type(open_new_document) + {} +}; + +user_command get_user_input() +{ + return user_command(); +} + +std::string get_filename_from_user() +{ + return "foo.doc"; +} + +void process_user_input(user_command const& cmd) +{} + +void edit_document(std::string const& filename) +{ + open_document_and_display_gui(filename); + while(!done_editing()) + { + user_command cmd=get_user_input(); + if(cmd.type==open_new_document) + { + std::string const new_name=get_filename_from_user(); + std::thread t(edit_document,new_name); + t.detach(); + } + else + { + process_user_input(cmd); + } + } +} + +int main() +{ + edit_document("bar.doc"); +} diff --git a/source/listing_2.5.cpp b/source/listing_2.5.cpp new file mode 100644 index 0000000..5913bef --- /dev/null +++ b/source/listing_2.5.cpp @@ -0,0 +1,27 @@ +#include + +void some_function() +{} + +void some_other_function(int) +{} + +std::thread f() +{ + void some_function(); + return std::thread(some_function); +} +std::thread g() +{ + void some_other_function(int); + std::thread t(some_other_function,42); + return t; +} + +int main() +{ + std::thread t1=f(); + t1.join(); + std::thread t2=g(); + t2.join(); +} diff --git a/source/listing_2.6.cpp b/source/listing_2.6.cpp new file mode 100644 index 0000000..f08fff6 --- /dev/null +++ b/source/listing_2.6.cpp @@ -0,0 +1,56 @@ +#include +#include + +class scoped_thread +{ + std::thread t; +public: + explicit scoped_thread(std::thread t_): + t(std::move(t_)) + { + if(!t.joinable()) + throw std::logic_error("No thread"); + } + ~scoped_thread() + { + t.join(); + } + scoped_thread(scoped_thread const&)=delete; + scoped_thread& operator=(scoped_thread const&)=delete; +}; + +void do_something(int& i) +{ + ++i; +} + +struct func +{ + int& i; + + func(int& i_):i(i_){} + + void operator()() + { + for(unsigned j=0;j<1000000;++j) + { + do_something(i); + } + } +}; + +void do_something_in_current_thread() +{} + +void f() +{ + int some_local_state; + scoped_thread t(std::thread(func(some_local_state))); + + do_something_in_current_thread(); +} + +int main() +{ + f(); +} diff --git a/source/listing_2.7.cpp b/source/listing_2.7.cpp new file mode 100644 index 0000000..4aa81d2 --- /dev/null +++ b/source/listing_2.7.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +void do_work(unsigned id) +{} + +void f() +{ + std::vector threads; + for(unsigned i=0;i<20;++i) + { + threads.push_back(std::thread(do_work,i)); + } + std::for_each(threads.begin(),threads.end(), + std::mem_fn(&std::thread::join)); +} + +int main() +{ + f(); +} diff --git a/source/listing_2.8.cpp b/source/listing_2.8.cpp new file mode 100644 index 0000000..c2dc415 --- /dev/null +++ b/source/listing_2.8.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +template +struct accumulate_block +{ + void operator()(Iterator first,Iterator last,T& result) + { + result=std::accumulate(first,last,result); + } +}; + +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return init; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::vector results(num_threads); + std::vector threads(num_threads-1); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + threads[i]=std::thread( + accumulate_block(), + block_start,block_end,std::ref(results[i])); + block_start=block_end; + } + accumulate_block()(block_start,last,results[num_threads-1]); + + std::for_each(threads.begin(),threads.end(), + std::mem_fn(&std::thread::join)); + + return std::accumulate(results.begin(),results.end(),init); +} + +int main() +{ + std::vector vi; + for(int i=0;i<10;++i) + { + vi.push_back(10); + } + int sum=parallel_accumulate(vi.begin(),vi.end(),5); + std::cout<<"sum="< +#include +#include + +std::list some_list; +std::mutex some_mutex; + +void add_to_list(int new_value) +{ + std::lock_guard guard(some_mutex); + some_list.push_back(new_value); +} +bool list_contains(int value_to_find) +{ + std::lock_guard guard(some_mutex); + return std::find(some_list.begin(),some_list.end(),value_to_find) + != some_list.end(); +} + +#include + +int main() +{ + add_to_list(42); + std::cout<<"contains(1)="< +class threadsafe_stack +{ +private: + std::stack data; + mutable std::mutex m; +public: + threadsafe_stack(){} + threadsafe_stack(const threadsafe_stack& other) + { + std::lock_guard lock(other.m); + data=other.data; + } + threadsafe_stack& operator=(const threadsafe_stack&) = delete; + + void push(T new_value) + { + std::lock_guard lock(m); + data.push(new_value); + } + std::shared_ptr pop() + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + std::shared_ptr const res(std::make_shared(data.top())); + data.pop(); + return res; + } + void pop(T& value) + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + value=data.top(); + data.pop(); + } + bool empty() const + { + std::lock_guard lock(m); + return data.empty(); + } +}; + +int main() +{ + threadsafe_stack si; + si.push(5); + si.pop(); + if(!si.empty()) + { + int x; + si.pop(x); + } + +} diff --git a/source/listing_3.6.cpp b/source/listing_3.6.cpp new file mode 100644 index 0000000..e2a48fd --- /dev/null +++ b/source/listing_3.6.cpp @@ -0,0 +1,29 @@ +#include + +class some_big_object +{}; + +void swap(some_big_object& lhs,some_big_object& rhs) +{} + +class X +{ +private: + some_big_object some_detail; + mutable std::mutex m; +public: + X(some_big_object const& sd):some_detail(sd){} + + friend void swap(X& lhs, X& rhs) + { + if(&lhs==&rhs) + return; + std::lock(lhs.m,rhs.m); + std::lock_guard lock_a(lhs.m,std::adopt_lock); + std::lock_guard lock_b(rhs.m,std::adopt_lock); + swap(lhs.some_detail,rhs.some_detail); + } +}; + +int main() +{} diff --git a/source/listing_3.7.cpp b/source/listing_3.7.cpp new file mode 100644 index 0000000..002e71b --- /dev/null +++ b/source/listing_3.7.cpp @@ -0,0 +1,64 @@ +#include + +class hierarchical_mutex +{ +public: + explicit hierarchical_mutex(unsigned level) + {} + + void lock() + {} + void unlock() + {} +}; + + +hierarchical_mutex high_level_mutex(10000); +hierarchical_mutex low_level_mutex(5000); + +int do_low_level_stuff() +{ + return 42; +} + + +int low_level_func() +{ + std::lock_guard lk(low_level_mutex); + return do_low_level_stuff(); +} + +void high_level_stuff(int some_param) +{} + + +void high_level_func() +{ + std::lock_guard lk(high_level_mutex); + high_level_stuff(low_level_func()); +} + +void thread_a() +{ + high_level_func(); +} + +hierarchical_mutex other_mutex(100); +void do_other_stuff() +{} + + +void other_stuff() +{ + high_level_func(); + do_other_stuff(); +} + +void thread_b() +{ + std::lock_guard lk(other_mutex); + other_stuff(); +} + +int main() +{} diff --git a/source/listing_3.8.cpp b/source/listing_3.8.cpp new file mode 100644 index 0000000..a1f18aa --- /dev/null +++ b/source/listing_3.8.cpp @@ -0,0 +1,56 @@ +#include +#include + +class hierarchical_mutex +{ + std::mutex internal_mutex; + unsigned long const hierarchy_value; + unsigned long previous_hierarchy_value; + static thread_local unsigned long this_thread_hierarchy_value; + + void check_for_hierarchy_violation() + { + if(this_thread_hierarchy_value <= hierarchy_value) + { + throw std::logic_error("mutex hierarchy violated"); + } + } + void update_hierarchy_value() + { + previous_hierarchy_value=this_thread_hierarchy_value; + this_thread_hierarchy_value=hierarchy_value; + } +public: + explicit hierarchical_mutex(unsigned long value): + hierarchy_value(value), + previous_hierarchy_value(0) + {} + void lock() + { + check_for_hierarchy_violation(); + internal_mutex.lock(); + update_hierarchy_value(); + } + void unlock() + { + this_thread_hierarchy_value=previous_hierarchy_value; + internal_mutex.unlock(); + } + bool try_lock() + { + check_for_hierarchy_violation(); + if(!internal_mutex.try_lock()) + return false; + update_hierarchy_value(); + return true; + } +}; +thread_local unsigned long + hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX); + +int main() +{ + hierarchical_mutex m1(42); + hierarchical_mutex m2(2000); + +} diff --git a/source/listing_3.9.cpp b/source/listing_3.9.cpp new file mode 100644 index 0000000..807acbe --- /dev/null +++ b/source/listing_3.9.cpp @@ -0,0 +1,29 @@ +#include + +class some_big_object +{}; + +void swap(some_big_object& lhs,some_big_object& rhs) +{} + +class X +{ +private: + some_big_object some_detail; + mutable std::mutex m; +public: + X(some_big_object const& sd):some_detail(sd){} + + friend void swap(X& lhs, X& rhs) + { + if(&lhs==&rhs) + return; + std::unique_lock lock_a(lhs.m,std::defer_lock); + std::unique_lock lock_b(rhs.m,std::defer_lock); + std::lock(lock_a,lock_b); + swap(lhs.some_detail,rhs.some_detail); + } +}; + +int main() +{} diff --git a/source/listing_4.1.cpp b/source/listing_4.1.cpp new file mode 100644 index 0000000..5ede423 --- /dev/null +++ b/source/listing_4.1.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +bool more_data_to_prepare() +{ + return false; +} + +struct data_chunk +{}; + +data_chunk prepare_data() +{ + return data_chunk(); +} + +void process(data_chunk&) +{} + +bool is_last_chunk(data_chunk&) +{ + return true; +} + +std::mutex mut; +std::queue data_queue; +std::condition_variable data_cond; + +void data_preparation_thread() +{ + while(more_data_to_prepare()) + { + data_chunk const data=prepare_data(); + std::lock_guard lk(mut); + data_queue.push(data); + data_cond.notify_one(); + } +} + +void data_processing_thread() +{ + while(true) + { + std::unique_lock lk(mut); + data_cond.wait(lk,[]{return !data_queue.empty();}); + data_chunk data=data_queue.front(); + data_queue.pop(); + lk.unlock(); + process(data); + if(is_last_chunk(data)) + break; + } +} + +int main() +{ + std::thread t1(data_preparation_thread); + std::thread t2(data_processing_thread); + + t1.join(); + t2.join(); +} diff --git a/source/listing_4.10.cpp b/source/listing_4.10.cpp new file mode 100644 index 0000000..9eadddd --- /dev/null +++ b/source/listing_4.10.cpp @@ -0,0 +1,27 @@ +#include +void process_connections(connection_set& connections) +{ + while(!done(connections)) + { + for(connection_iterator + connection=connections.begin(),end=connections.end(); + connection!=end; + ++connection) + { + if(connection->has_incoming_data()) + { + data_packet data=connection->incoming(); + std::promise& p= + connection->get_promise(data.id); + p.set_value(data.payload); + } + if(connection->has_outgoing_data()) + { + outgoing_packet data= + connection->top_of_outgoing_queue(); + connection->send(data.payload); + data.promise.set_value(true); + } + } + } +} diff --git a/source/listing_4.11.cpp b/source/listing_4.11.cpp new file mode 100644 index 0000000..8d22cb6 --- /dev/null +++ b/source/listing_4.11.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +std::condition_variable cv; +bool done; +std::mutex m; +bool wait_loop() +{ + auto const timeout= std::chrono::steady_clock::now()+ + std::chrono::milliseconds(500); + std::unique_lock lk(m); + while(!done) + { + if(cv.wait_until(lk,timeout)==std::cv_status::timeout) + break; + } + return done; +} diff --git a/source/listing_4.12.cpp b/source/listing_4.12.cpp new file mode 100644 index 0000000..f4592ff --- /dev/null +++ b/source/listing_4.12.cpp @@ -0,0 +1,24 @@ +template +std::list sequential_quick_sort(std::list input) +{ + if(input.empty()) + { + return input; + } + std::list result; + result.splice(result.begin(),input,input.begin()); + T const& pivot=*result.begin(); + auto divide_point=std::partition(input.begin(),input.end(), + [&](T const& t){return t lower_part; + lower_part.splice(lower_part.end(),input,input.begin(), + divide_point); + auto new_lower( + sequential_quick_sort(std::move(lower_part))); + auto new_higher( + sequential_quick_sort(std::move(input))); + result.splice(result.end(),new_higher); + Using synchronization of operations to simplify code + result.splice(result.begin(),new_lower); + return result; +} diff --git a/source/listing_4.13.cpp b/source/listing_4.13.cpp new file mode 100644 index 0000000..ae87d08 --- /dev/null +++ b/source/listing_4.13.cpp @@ -0,0 +1,23 @@ +template +std::list parallel_quick_sort(std::list input) +{ + if(input.empty()) + { + return input; + } + std::list result; + result.splice(result.begin(),input,input.begin()); + T const& pivot=*result.begin(); + auto divide_point=std::partition(input.begin(),input.end(), + [&](T const& t){return t lower_part; + lower_part.splice(lower_part.end(),input,input.begin(), + divide_point); + std::future > new_lower( + std::async(¶llel_quick_sort,std::move(lower_part))); + auto new_higher( + parallel_quick_sort(std::move(input))); + result.splice(result.end(),new_higher); + result.splice(result.begin(),new_lower.get()); + return result; +} diff --git a/source/listing_4.14.cpp b/source/listing_4.14.cpp new file mode 100644 index 0000000..e70c58d --- /dev/null +++ b/source/listing_4.14.cpp @@ -0,0 +1,12 @@ +template +std::future::type> +spawn_task(F&& f,A&& a) +{ + typedef std::result_of::type result_type; + std::packaged_task + task(std::move(f)); + std::future res(task.get_future()); + std::thread t(std::move(task),std::move(a)); + t.detach(); + return res; +} diff --git a/source/listing_4.15.cpp b/source/listing_4.15.cpp new file mode 100644 index 0000000..9868de8 --- /dev/null +++ b/source/listing_4.15.cpp @@ -0,0 +1,43 @@ +struct card_inserted +{ + std::string account; +}; +class atm +{ + messaging::receiver incoming; + messaging::sender bank; + messaging::sender interface_hardware; + void (atm::*state)(); + std::string account; + std::string pin; + void waiting_for_card() + { + interface_hardware.send(display_enter_card()); + incoming.wait() + .handle( + [&](card_inserted const& msg) + { + account=msg.account; + pin=""; + interface_hardware.send(display_enter_pin()); + state=&atm::getting_pin; + } + ); + } + void getting_pin(); +public: + void run() + { + state=&atm::waiting_for_card; + try + { + for(;;) + { + (this->*state)(); + } + } + catch(messaging::close_queue const&) + { + } + } +}; diff --git a/source/listing_4.16.cpp b/source/listing_4.16.cpp new file mode 100644 index 0000000..499bb91 --- /dev/null +++ b/source/listing_4.16.cpp @@ -0,0 +1,31 @@ +void atm::getting_pin() +{ + incoming.wait() + .handle( + [&](digit_pressed const& msg) + { + unsigned const pin_length=4; + pin+=msg.digit; + if(pin.length()==pin_length) + { + bank.send(verify_pin(account,pin,incoming)); + state=&atm::verifying_pin; + } + } + ) + .handle( + [&](clear_last_pressed const& msg) + { + if(!pin.empty()) + { + pin.resize(pin.length()-1); + } + } + ) + .handle( + [&](cancel_pressed const& msg) + { + state=&atm::done_processing; + } + ); +} diff --git a/source/listing_4.2.cpp b/source/listing_4.2.cpp new file mode 100644 index 0000000..a413a22 --- /dev/null +++ b/source/listing_4.2.cpp @@ -0,0 +1,30 @@ +template > +class queue { +public: + explicit queue(const Container&); + explicit queue(Container&& = Container()); + queue(queue&& q); + + template explicit queue(const Alloc&); + template queue(const Container&, const Alloc&); + template queue(Container&&, const Alloc&); + template queue(queue&&, const Alloc&); + + queue& operator=(queue&& q); + void swap(queue&& q); + + bool empty() const; + size_type size() const; + + T& front(); + const T& front() const; + T& back(); + const T& back() const; + + void push(const T& x); + void push(T&& x); + void pop(); +}; + +int main() +{} diff --git a/source/listing_4.3.cpp b/source/listing_4.3.cpp new file mode 100644 index 0000000..d2ea1d4 --- /dev/null +++ b/source/listing_4.3.cpp @@ -0,0 +1,22 @@ +#include +template +class threadsafe_queue +{ +public: + threadsafe_queue(); + threadsafe_queue(const threadsafe_queue&); + threadsafe_queue& operator=(const threadsafe_queue&) = delete; + + void push(T new_value); + + bool try_pop(T& value); + std::shared_ptr try_pop(); + + void wait_and_pop(T& value); + std::shared_ptr wait_and_pop(); + + bool empty() const; +}; + +int main() +{} diff --git a/source/listing_4.4.cpp b/source/listing_4.4.cpp new file mode 100644 index 0000000..672d141 --- /dev/null +++ b/source/listing_4.4.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +template +class threadsafe_queue +{ +private: + std::mutex mut; + std::queue data_queue; + std::condition_variable data_cond; +public: + void push(T new_value) + { + std::lock_guard lk(mut); + data_queue.push(new_value); + data_cond.notify_one(); + } + + void wait_and_pop(T& value) + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + value=data_queue.front(); + data_queue.pop(); + } +}; + + +struct data_chunk +{}; + +data_chunk prepare_data(); +bool more_data_to_prepare(); +void process(data_chunk); +bool is_last_chunk(data_chunk); + +threadsafe_queue data_queue; + +void data_preparation_thread() +{ + while(more_data_to_prepare()) + { + data_chunk const data=prepare_data(); + data_queue.push(data); + } +} + +void data_processing_thread() +{ + while(true) + { + data_chunk data; + data_queue.wait_and_pop(data); + process(data); + if(is_last_chunk(data)) + break; + } +} diff --git a/source/listing_4.5.cpp b/source/listing_4.5.cpp new file mode 100644 index 0000000..0e78979 --- /dev/null +++ b/source/listing_4.5.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +template +class threadsafe_queue +{ +private: + mutable std::mutex mut; + std::queue data_queue; + std::condition_variable data_cond; +public: + threadsafe_queue() + {} + threadsafe_queue(threadsafe_queue const& other) + { + std::lock_guard lk(other.mut); + data_queue=other.data_queue; + } + + void push(T new_value) + { + std::lock_guard lk(mut); + data_queue.push(new_value); + data_cond.notify_one(); + } + + void wait_and_pop(T& value) + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + value=data_queue.front(); + data_queue.pop(); + } + + std::shared_ptr wait_and_pop() + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + std::shared_ptr res(std::make_shared(data_queue.front())); + data_queue.pop(); + return res; + } + + bool try_pop(T& value) + { + std::lock_guard lk(mut); + if(data_queue.empty) + return false; + value=data_queue.front(); + data_queue.pop(); + } + + std::shared_ptr try_pop() + { + std::lock_guard lk(mut); + if(data_queue.empty()) + return std::shared_ptr(); + std::shared_ptr res(std::make_shared(data_queue.front())); + data_queue.pop(); + return res; + } + + bool empty() const + { + std::lock_guard lk(mut); + return data_queue.empty(); + } +}; + +int main() +{} diff --git a/source/listing_4.6.cpp b/source/listing_4.6.cpp new file mode 100644 index 0000000..3965eb1 --- /dev/null +++ b/source/listing_4.6.cpp @@ -0,0 +1,16 @@ +#include +#include +int find_the_answer_to_ltuae() +{ + return 42; +} + +void do_other_stuff() +{} + +int main() +{ + std::future the_answer=std::async(find_the_answer_to_ltuae); + do_other_stuff(); + std::cout<<"The answer is "< +#include + +struct X +{ + void foo(int,std::string const&); + std::string bar(std::string const&); +}; + + +X x; +auto f1=std::async(&X::foo,&x,42,"hello"); +auto f2=std::async(&X::bar,x,"goodbye"); + +struct Y +{ + double operator()(double); +}; +Y y; +auto f3=std::async(Y(),3.141); +auto f4=std::async(std::ref(y),2.718); +X baz(X&); +auto f6=std::async(baz,std::ref(x)); +class move_only +{ +public: + move_only(); + move_only(move_only&&); + move_only(move_only const&) = delete; + move_only& operator=(move_only&&); + move_only& operator=(move_only const&) = delete; + void operator()(); +}; +auto f5=std::async(move_only()); diff --git a/source/listing_4.8.cpp b/source/listing_4.8.cpp new file mode 100644 index 0000000..a3f46c9 --- /dev/null +++ b/source/listing_4.8.cpp @@ -0,0 +1,9 @@ +template<> +class packaged_task*,int)> +{ +public: + template + explicit packaged_task(Callable&& f); + std::future get_future(); + void operator()(std::vector*,int); +}; diff --git a/source/listing_4.9.cpp b/source/listing_4.9.cpp new file mode 100644 index 0000000..0b86a3f --- /dev/null +++ b/source/listing_4.9.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + +std::mutex m; +std::deque > tasks; + +bool gui_shutdown_message_received(); +void get_and_process_gui_message(); + +void gui_thread() +{ + while(!gui_shutdown_message_received()) + { + get_and_process_gui_message(); + std::packaged_task task; + { + std::lock_guard lk(m); + if(tasks.empty()) + continue; + task=std::move(tasks.front()); + tasks.pop_front(); + } + task(); + } +} + +std::thread gui_bg_thread(gui_thread); + +template +std::future post_task_for_gui_thread(Func f) +{ + std::packaged_task task(f); + std::future res=task.get_future(); + std::lock_guard lk(m); + tasks.push_back(std::move(task)); + return res; +} diff --git a/source/listing_5.1.cpp b/source/listing_5.1.cpp new file mode 100644 index 0000000..e2441b3 --- /dev/null +++ b/source/listing_5.1.cpp @@ -0,0 +1,16 @@ +class spinlock_mutex +{ + std::atomic_flag flag; +public: + spinlock_mutex(): + flag(ATOMIC_FLAG_INIT) + {} + void lock() + { + while(flag.test_and_set(std::memory_order_acquire)); + } + void unlock() + { + flag.clear(std::memory_order_release); + } +}; diff --git a/source/listing_5.10.cpp b/source/listing_5.10.cpp new file mode 100644 index 0000000..2de655f --- /dev/null +++ b/source/listing_5.10.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +struct X +{ + int i; + std::string s; +}; + +std::atomic p; +std::atomic a; + +void create_x() +{ + X* x=new X; + x->i=42; + x->s="hello"; + a.store(99,std::memory_order_relaxed); + p.store(x,std::memory_order_release); +} + +void use_x() +{ + X* x; + while(!(x=p.load(std::memory_order_consume))) + std::this_thread::sleep_for(std::chrono::microseconds(1)); + assert(x->i==42); + assert(x->s=="hello"); + assert(a.load(std::memory_order_relaxed)==99); +} +int main() +{ + std::thread t1(create_x); + std::thread t2(use_x); + t1.join(); + t2.join(); +} + diff --git a/source/listing_5.11.cpp b/source/listing_5.11.cpp new file mode 100644 index 0000000..deef694 --- /dev/null +++ b/source/listing_5.11.cpp @@ -0,0 +1,41 @@ +#include +#include + +std::vector queue_data; +std::atomic count; + +void populate_queue() +{ + unsigned const number_of_items=20; + queue_data.clear(); + for(unsigned i=0;i +#include +#include + +std::atomic x,y; +std::atomic z; + +void write_x_then_y() +{ + x.store(true,std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_release); + y.store(true,std::memory_order_relaxed); +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_relaxed)); + std::atomic_thread_fence(std::memory_order_acquire); + if(x.load(std::memory_order_relaxed)) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x_then_y); + std::thread b(read_y_then_x); + a.join(); + b.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.13.cpp b/source/listing_5.13.cpp new file mode 100644 index 0000000..a5de692 --- /dev/null +++ b/source/listing_5.13.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +bool x=false; +std::atomic y; +std::atomic z; + +void write_x_then_y() +{ + x=true; + std::atomic_thread_fence(std::memory_order_release); + y.store(true,std::memory_order_relaxed); +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_relaxed)); + std::atomic_thread_fence(std::memory_order_acquire); + if(x) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x_then_y); + std::thread b(read_y_then_x); + a.join(); + b.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.2.cpp b/source/listing_5.2.cpp new file mode 100644 index 0000000..616bc71 --- /dev/null +++ b/source/listing_5.2.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +std::vector data; +std::atomic_bool data_ready(false); + +void reader_thread() +{ + while(!data_ready.load()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + std::cout<<"The answer="< + +void foo(int a,int b) +{ + std::cout< +#include +#include + +std::atomic x,y; +std::atomic z; + +void write_x() +{ + x.store(true,std::memory_order_seq_cst); +} + +void write_y() +{ + y.store(true,std::memory_order_seq_cst); +} + +void read_x_then_y() +{ + while(!x.load(std::memory_order_seq_cst)); + if(y.load(std::memory_order_seq_cst)) + ++z; +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_seq_cst)); + if(x.load(std::memory_order_seq_cst)) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x); + std::thread b(write_y); + std::thread c(read_x_then_y); + std::thread d(read_y_then_x); + a.join(); + b.join(); + c.join(); + d.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.5.cpp b/source/listing_5.5.cpp new file mode 100644 index 0000000..057d03b --- /dev/null +++ b/source/listing_5.5.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +std::atomic x,y; +std::atomic z; + +void write_x_then_y() +{ + x.store(true,std::memory_order_relaxed); + y.store(true,std::memory_order_relaxed); +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_relaxed)); + if(x.load(std::memory_order_relaxed)) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x_then_y); + std::thread b(read_y_then_x); + a.join(); + b.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.6.cpp b/source/listing_5.6.cpp new file mode 100644 index 0000000..e6045a5 --- /dev/null +++ b/source/listing_5.6.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +std::atomic x(0),y(0),z(0); +std::atomic go(false); +unsigned const loop_count=10; + +struct read_values +{ + int x,y,z; +}; +read_values values1[loop_count]; +read_values values2[loop_count]; +read_values values3[loop_count]; +read_values values4[loop_count]; +read_values values5[loop_count]; +void increment(std::atomic* var_to_inc,read_values* values) +{ + while(!go) + std::this_thread::yield(); + for(unsigned i=0;istore(i+1,std::memory_order_relaxed); + std::this_thread::yield(); + } +} + +void read_vals(read_values* values) +{ + while(!go) + std::this_thread::yield(); + for(unsigned i=0;i +#include +#include + +std::atomic x,y; +std::atomic z; + +void write_x() +{ + x.store(true,std::memory_order_release); +} + +void write_y() +{ + y.store(true,std::memory_order_release); +} + +void read_x_then_y() +{ + while(!x.load(std::memory_order_acquire)); + if(y.load(std::memory_order_acquire)) + ++z; +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_acquire)); + if(x.load(std::memory_order_acquire)) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x); + std::thread b(write_y); + std::thread c(read_x_then_y); + std::thread d(read_y_then_x); + a.join(); + b.join(); + c.join(); + d.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.8.cpp b/source/listing_5.8.cpp new file mode 100644 index 0000000..19c1612 --- /dev/null +++ b/source/listing_5.8.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +std::atomic x,y; +std::atomic z; + +void write_x_then_y() +{ + x.store(true,std::memory_order_relaxed); + y.store(true,std::memory_order_release); +} + +void read_y_then_x() +{ + while(!y.load(std::memory_order_acquire)); + if(x.load(std::memory_order_relaxed)) + ++z; +} + +int main() +{ + x=false; + y=false; + z=0; + std::thread a(write_x_then_y); + std::thread b(read_y_then_x); + a.join(); + b.join(); + assert(z.load()!=0); +} diff --git a/source/listing_5.9.cpp b/source/listing_5.9.cpp new file mode 100644 index 0000000..992ae56 --- /dev/null +++ b/source/listing_5.9.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +std::atomic data[5]; +std::atomic sync1(false),sync2(false); + +void thread_1() +{ + data[0].store(42,std::memory_order_relaxed); + data[1].store(97,std::memory_order_relaxed); + data[2].store(17,std::memory_order_relaxed); + data[3].store(-141,std::memory_order_relaxed); + data[4].store(2003,std::memory_order_relaxed); + sync1.store(true,std::memory_order_release); +} + +void thread_2() +{ + while(!sync1.load(std::memory_order_acquire)); + sync2.store(std::memory_order_release); +} + +void thread_3() +{ + while(!sync2.load(std::memory_order_acquire)); + assert(data[0].load(std::memory_order_relaxed)==42); + assert(data[1].load(std::memory_order_relaxed)==97); + assert(data[2].load(std::memory_order_relaxed)==17); + assert(data[3].load(std::memory_order_relaxed)==-141); + assert(data[4].load(std::memory_order_relaxed)==2003); +} + +int main() +{ + std::thread t1(thread_1); + std::thread t2(thread_2); + std::thread t3(thread_3); + t1.join(); + t2.join(); + t3.join(); +} diff --git a/source/listing_6.1.cpp b/source/listing_6.1.cpp new file mode 100644 index 0000000..011938a --- /dev/null +++ b/source/listing_6.1.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +struct empty_stack: std::exception +{ + const char* what() const throw() + { + return "empty stack"; + } +}; + +template +class threadsafe_stack +{ +private: + std::stack data; + mutable std::mutex m; +public: + threadsafe_stack(){} + threadsafe_stack(const threadsafe_stack& other) + { + std::lock_guard lock(other.m); + data=other.data; + } + threadsafe_stack& operator=(const threadsafe_stack&) = delete; + + void push(T new_value) + { + std::lock_guard lock(m); + data.push(std::move(new_value)); + } + std::shared_ptr pop() + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + std::shared_ptr const res( + std::make_shared(std::move(data.top()))); + data.pop(); + return res; + } + void pop(T& value) + { + std::lock_guard lock(m); + if(data.empty()) throw empty_stack(); + value=std::move(data.top()); + data.pop(); + } + bool empty() const + { + std::lock_guard lock(m); + return data.empty(); + } +}; + diff --git a/source/listing_6.10.cpp b/source/listing_6.10.cpp new file mode 100644 index 0000000..79f57f0 --- /dev/null +++ b/source/listing_6.10.cpp @@ -0,0 +1,44 @@ +template +class threadsafe_queue +{ +private: + std::unique_ptr try_pop_head() + { + std::lock_guard head_lock(head_mutex); + if(head.get()==get_tail()) + { + return std::unique_ptr(); + } + return pop_head(); + } + + std::unique_ptr try_pop_head(T& value) + { + std::lock_guard head_lock(head_mutex); + if(head.get()==get_tail()) + { + return std::unique_ptr(); + } + value=std::move(*head->data); + return pop_head(); + } + +public: + std::shared_ptr try_pop() + { + std::unique_ptr const old_head=try_pop_head(); + return old_head?old_head->data:std::shared_ptr(); + } + + bool try_pop(T& value) + { + std::unique_ptr const old_head=try_pop_head(value); + return old_head; + } + + void empty() + { + std::lock_guard head_lock(head_mutex); + return (head==get_tail()); + } +}; diff --git a/source/listing_6.11.cpp b/source/listing_6.11.cpp new file mode 100644 index 0000000..ff1492a --- /dev/null +++ b/source/listing_6.11.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include + +template > +class threadsafe_lookup_table +{ +private: + class bucket_type + { + private: + typedef std::pair bucket_value; + typedef std::list bucket_data; + typedef typename bucket_data::iterator bucket_iterator; + + bucket_data data; + mutable boost::shared_mutex mutex; + + bucket_iterator find_entry_for(Key const& key) const + { + return std::find_if(data.begin(),data.end(), + [&](bucket_value const& item) + {return item.first==key;}); + } + public: + Value value_for(Key const& key,Value const& default_value) const + { + boost::shared_lock lock(mutex); + bucket_iterator const found_entry=find_entry_for(key); + return (found_entry==data.end())? + default_value : found_entry->second; + } + + void add_or_update_mapping(Key const& key,Value const& value) + { + std::unique_lock lock(mutex); + bucket_iterator const found_entry=find_entry_for(key); + if(found_entry==data.end()) + { + data.push_back(bucket_value(key,value)); + } + else + { + found_entry->second=value; + } + } + + void remove_mapping(Key const& key) + { + std::unique_lock lock(mutex); + bucket_iterator const found_entry=find_entry_for(key); + if(found_entry!=data.end()) + { + data.erase(found_entry); + } + } + }; + + std::vector > buckets; + Hash hasher; + + bucket_type& get_bucket(Key const& key) const + { + std::size_t const bucket_index=hasher(key)%buckets.size(); + return *buckets[bucket_index]; + } + +public: + typedef Key key_type; + typedef Value mapped_type; + typedef Hash hash_type; + + threadsafe_lookup_table( + unsigned num_buckets=19, Hash const& hasher_=Hash()): + buckets(num_buckets),hasher(hasher_) + { + for(unsigned i=0;i threadsafe_lookup_table::get_map() const +{ + std::vector > locks; + for(unsigned i=0;i(buckets[i].mutex)); + } + std::map res; + for(unsigned i=0;i +#include + +template +class threadsafe_list +{ + struct node + { + std::mutex m; + std::shared_ptr data; + std::unique_ptr next; + + node(): + next() + {} + + node(T const& value): + data(std::make_shared(value)) + {} + }; + + node head; + +public: + threadsafe_list() + {} + + ~threadsafe_list() + { + remove_if([](T const&){return true;}); + } + + threadsafe_list(threadsafe_list const& other)=delete; + threadsafe_list& operator=(threadsafe_list const& other)=delete; + + void push_front(T const& value) + { + std::unique_ptr new_node(new node(value)); + std::lock_guard lk(head.m); + new_node->next=std::move(head.next); + head.next=std::move(new_node); + } + + template + void for_each(Function f) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + lk.unlock(); + f(*next->data); + current=next; + lk=std::move(next_lk); + } + } + + template + std::shared_ptr find_first_if(Predicate p) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + lk.unlock(); + if(p(*next->data)) + { + return next->data; + } + current=next; + lk=std::move(next_lk); + } + return std::shared_ptr(); + } + + template + void remove_if(Predicate p) + { + node* current=&head; + std::unique_lock lk(head.m); + while(node* const next=current->next.get()) + { + std::unique_lock next_lk(next->m); + if(p(*next->data)) + { + std::unique_ptr old_next=std::move(current->next); + current->next=std::move(next->next); + next_lk.unlock(); + } + else + { + lk.unlock(); + current=next; + lk=std::move(next_lk); + } + } + } +}; + diff --git a/source/listing_6.2.cpp b/source/listing_6.2.cpp new file mode 100644 index 0000000..c0aed9e --- /dev/null +++ b/source/listing_6.2.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +template +class threadsafe_queue +{ +private: + mutable std::mutex mut; + std::queue data_queue; + std::condition_variable data_cond; +public: + threadsafe_queue() + {} + + void push(T new_value) + { + std::lock_guard lk(mut); + data_queue.push(std::move(new_value)); + data_cond.notify_one(); + } + + void wait_and_pop(T& value) + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + value=std::move(data_queue.front()); + data_queue.pop(); + } + + std::shared_ptr wait_and_pop() + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + std::shared_ptr res( + std::make_shared(std::move(data_queue.front()))); + data_queue.pop(); + return res; + } + + bool try_pop(T& value) + { + std::lock_guard lk(mut); + if(data_queue.empty()) + return false; + value=std::move(data_queue.front()); + data_queue.pop(); + } + + std::shared_ptr try_pop() + { + std::lock_guard lk(mut); + if(data_queue.empty()) + return std::shared_ptr(); + std::shared_ptr res( + std::make_shared(std::move(data_queue.front()))); + data_queue.pop(); + return res; + } + + bool empty() const + { + std::lock_guard lk(mut); + return data_queue.empty(); + } +}; + +int main() +{ + threadsafe_queue rq; +} diff --git a/source/listing_6.3.cpp b/source/listing_6.3.cpp new file mode 100644 index 0000000..35c0519 --- /dev/null +++ b/source/listing_6.3.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +template +class threadsafe_queue +{ +private: + mutable std::mutex mut; + std::queue > data_queue; + std::condition_variable data_cond; +public: + threadsafe_queue() + {} + + void wait_and_pop(T& value) + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + value=std::move(*data_queue.front()); + data_queue.pop(); + } + + bool try_pop(T& value) + { + std::lock_guard lk(mut); + if(data_queue.empty()) + return false; + value=std::move(*data_queue.front()); + data_queue.pop(); + } + + std::shared_ptr wait_and_pop() + { + std::unique_lock lk(mut); + data_cond.wait(lk,[this]{return !data_queue.empty();}); + std::shared_ptr res=data_queue.front(); + data_queue.pop(); + return res; + } + + std::shared_ptr try_pop() + { + std::lock_guard lk(mut); + if(data_queue.empty()) + return std::shared_ptr(); + std::shared_ptr res=data_queue.front(); + data_queue.pop(); + return res; + } + + bool empty() const + { + std::lock_guard lk(mut); + return data_queue.empty(); + } + + void push(T new_value) + { + std::shared_ptr data( + std::make_shared(std::move(new_value))); + std::lock_guard lk(mut); + data_queue.push(data); + data_cond.notify_one(); + } + +}; diff --git a/source/listing_6.4.cpp b/source/listing_6.4.cpp new file mode 100644 index 0000000..187b151 --- /dev/null +++ b/source/listing_6.4.cpp @@ -0,0 +1,56 @@ +#include + +template +class queue +{ +private: + struct node + { + T data; + std::unique_ptr next; + + node(T data_): + data(std::move(data_)) + {} + }; + + std::unique_ptr head; + node* tail; + +public: + queue(): + tail(nullptr) + {} + + queue(const queue& other)=delete; + queue& operator=(const queue& other)=delete; + + std::shared_ptr try_pop() + { + if(!head) + { + return std::shared_ptr(); + } + std::shared_ptr const res( + std::make_shared(std::move(head->data))); + std::unique_ptr const old_head=std::move(head); + head=std::move(old_head->next); + return res; + } + + void push(T new_value) + { + std::unique_ptr p(new node(std::move(new_value))); + node* const new_tail=p.get(); + if(tail) + { + tail->next=std::move(p); + } + else + { + head=std::move(p); + } + tail=new_tail; + } +}; + diff --git a/source/listing_6.5.cpp b/source/listing_6.5.cpp new file mode 100644 index 0000000..661114b --- /dev/null +++ b/source/listing_6.5.cpp @@ -0,0 +1,46 @@ +#include +template +class queue +{ +private: + struct node + { + std::shared_ptr data; + std::unique_ptr next; + }; + + std::unique_ptr head; + node* tail; + +public: + queue(): + head(new node),tail(head.get()) + {} + + queue(const queue& other)=delete; + queue& operator=(const queue& other)=delete; + + std::shared_ptr try_pop() + { + if(head.get()==tail) + { + return std::shared_ptr(); + } + std::shared_ptr const res(head->data); + std::unique_ptr const old_head=std::move(head); + head=std::move(old_head->next); + return res; + } + + void push(T new_value) + { + std::shared_ptr new_data( + std::make_shared(std::move(new_value))); + std::unique_ptr p(new node); + tail->data=new_data; + node* const new_tail=p.get(); + tail->next=std::move(p); + tail=new_tail; + } +}; + diff --git a/source/listing_6.6.cpp b/source/listing_6.6.cpp new file mode 100644 index 0000000..6a6eb54 --- /dev/null +++ b/source/listing_6.6.cpp @@ -0,0 +1,64 @@ +#include +#include + +template +class threadsafe_queue +{ +private: + struct node + { + std::shared_ptr data; + std::unique_ptr next; + }; + + std::mutex head_mutex; + std::unique_ptr head; + std::mutex tail_mutex; + node* tail; + + node* get_tail() + { + std::lock_guard tail_lock(tail_mutex); + return tail; + } + + std::unique_ptr pop_head() + { + std::lock_guard head_lock(head_mutex); + if(head.get()==get_tail()) + { + return nullptr; + } + std::unique_ptr const old_head=std::move(head); + head=std::move(old_head->next); + return old_head; + } + + +public: + threadsafe_queue(): + head(new node),tail(head.get()) + {} + + threadsafe_queue(const threadsafe_queue& other)=delete; + threadsafe_queue& operator=(const threadsafe_queue& other)=delete; + + std::shared_ptr try_pop() + { + std::unique_ptr old_head=pop_head(); + return old_head?old_head->data:std::shared_ptr(); + } + + void push(T new_value) + { + std::shared_ptr new_data( + std::make_shared(std::move(new_value))); + std::unique_ptr p(new node); + node* const new_tail=p.get(); + std::lock_guard tail_lock(tail_mutex); + tail->data=new_data; + tail->next=std::move(p); + tail=new_tail; + } +}; + diff --git a/source/listing_6.7.cpp b/source/listing_6.7.cpp new file mode 100644 index 0000000..7497a72 --- /dev/null +++ b/source/listing_6.7.cpp @@ -0,0 +1,29 @@ +template +class threadsafe_queue +{ +private: + struct node + { + std::shared_ptr data; + std::unique_ptr next; + }; + + std::mutex head_mutex; + std::unique_ptr head; + std::mutex tail_mutex; + node* tail; + std::condition_variable data_cond; +public: + threadsafe_queue(): + head(new node),tail(head.get()) + {} + threadsafe_queue(const threadsafe_queue& other)=delete; + threadsafe_queue& operator=(const threadsafe_queue& other)=delete; + + std::shared_ptr try_pop(); + bool try_pop(T& value); + std::shared_ptr wait_and_pop(); + void wait_and_pop(T& value); + void push(T new_value); + void empty(); +}; diff --git a/source/listing_6.8.cpp b/source/listing_6.8.cpp new file mode 100644 index 0000000..c042254 --- /dev/null +++ b/source/listing_6.8.cpp @@ -0,0 +1,15 @@ +template +void threadsafe_queue::push(T new_value) +{ + std::shared_ptr new_data( + std::make_shared(std::move(new_value))); + std::unique_ptr p(new node); + { + std::lock_guard tail_lock(tail_mutex); + tail->data=new_data; + node* const new_tail=p.get(); + tail->next=std::move(p); + tail=new_tail; + } + data_cond.notify_one(); +} diff --git a/source/listing_6.9.cpp b/source/listing_6.9.cpp new file mode 100644 index 0000000..d18a15e --- /dev/null +++ b/source/listing_6.9.cpp @@ -0,0 +1,49 @@ +template +class threadsafe_queue +{ +private: + node* get_tail() + { + std::lock_guard tail_lock(tail_mutex); + return tail; + } + + std::unique_ptr pop_head() + { + std::unique_ptr const old_head=std::move(head); + head=std::move(old_head->next); + return old_head; + } + + std::unique_lock wait_for_data() + { + std::unique_lock head_lock(head_mutex); + data_cond.wait(head_lock,[&]{return head!=get_tail();}); + return std::move(head_lock); + } + + std::unique_ptr wait_pop_head() + { + std::unique_lock head_lock(wait_for_data()); + return pop_head(); + } + + std::unique_ptr wait_pop_head(T& value) + { + std::unique_lock head_lock(wait_for_data()); + value=std::move(*head->data); + return pop_head(); + } + +public: + std::shared_ptr wait_and_pop() + { + std::unique_ptr const old_head=wait_pop_head(); + return old_head->data; + } + + void wait_and_pop(T& value) + { + std::unique_ptr const old_head=wait_pop_head(value); + } +}; diff --git a/source/listing_7.1.cpp b/source/listing_7.1.cpp new file mode 100644 index 0000000..8d141a8 --- /dev/null +++ b/source/listing_7.1.cpp @@ -0,0 +1,18 @@ +#include + +class spinlock_mutex +{ + std::atomic_flag flag; +public: + spinlock_mutex(): + flag(ATOMIC_FLAG_INIT) + {} + void lock() + { + while(flag.test_and_set(std::memory_order_acquire)); + } + void unlock() + { + flag.clear(std::memory_order_release); + } +}; diff --git a/source/listing_7.10.cpp b/source/listing_7.10.cpp new file mode 100644 index 0000000..3f05227 --- /dev/null +++ b/source/listing_7.10.cpp @@ -0,0 +1,38 @@ +#include +#include + +template +class lock_free_stack +{ +private: + struct node; + struct counted_node_ptr + { + int external_count; + node* ptr; + }; + struct node + { + std::shared_ptr data; + std::atomic internal_count; + counted_node_ptr next; + node(T const& data_): + data(std::make_shared(data_)), + internal_count(0) + {} + }; + std::atomic head; +public: + ~lock_free_stack() + { + while(pop()); + } + void push(T const& data) + { + counted_node_ptr new_node; + new_node.ptr=new node(data); + new_node.external_count=1; + new_node.ptr->next=head.load(); + while(!head.compare_exchange_weak(new_node.ptr->next,new_node)); + } +}; diff --git a/source/listing_7.11.cpp b/source/listing_7.11.cpp new file mode 100644 index 0000000..72837d0 --- /dev/null +++ b/source/listing_7.11.cpp @@ -0,0 +1,46 @@ +template +class lock_free_stack +{ +private: + void increase_head_count(counted_node_ptr& old_counter) + { + counted_node_ptr new_counter; + do + { + new_counter=old_counter; + ++new_counter.external_count; + } + while(!head.compare_exchange_strong(old_counter,new_counter)); + old_counter.external_count=new_counter.external_count; + } +public: + std::shared_ptr pop() + { + counted_node_ptr old_head=head.load(); + for(;;) + { + increase_head_count(old_head); + node* const ptr=old_head.ptr; + if(!ptr) + { + return std::shared_ptr(); + } + if(head.compare_exchange_strong(old_head,ptr->next)) + { + std::shared_ptr res; + res.swap(ptr->data); + int const count_increase=old_head.external_count-2; + if(ptr->internal_count.fetch_add(count_increase)== + -count_increase) + { + delete ptr; + } + return res; + } + else if(ptr->internal_count.fetch_sub(1)==1) + { + delete ptr; + } + } + } +}; diff --git a/source/listing_7.12.cpp b/source/listing_7.12.cpp new file mode 100644 index 0000000..03756e5 --- /dev/null +++ b/source/listing_7.12.cpp @@ -0,0 +1,88 @@ +#include +#include + +template +class lock_free_stack +{ +private: + struct node; + struct counted_node_ptr + { + int external_count; + node* ptr; + }; + struct node + { + std::shared_ptr data; + std::atomic internal_count; + counted_node_ptr next; + node(T const& data_): + data(std::make_shared(data_)), + internal_count(0) + {} + }; + std::atomic head; + void increase_head_count(counted_node_ptr& old_counter) + { + counted_node_ptr new_counter; + do + { + new_counter=old_counter; + ++new_counter.external_count; + } + while(!head.compare_exchange_strong( + old_counter,new_counter, + std::memory_order_acquire, + std::memory_order_relaxed)); + old_counter.external_count=new_counter.external_count; + } +public: + ~lock_free_stack() + { + while(pop()); + } + void push(T const& data) + { + counted_node_ptr new_node; + new_node.ptr=new node(data); + new_node.external_count=1; + new_node.ptr->next=head.load(std::memory_order_relaxed) + while(!head.compare_exchange_weak( + new_node.ptr->next,new_node, + std::memory_order_release, + std::memory_order_relaxed)); + } + std::shared_ptr pop() + { + counted_node_ptr old_head= + head.load(std::memory_order_relaxed); + for(;;) + { + increase_head_count(old_head); + node* const ptr=old_head.ptr; + if(!ptr) + { + return std::shared_ptr(); + } + if(head.compare_exchange_strong( + old_head,ptr->next,std::memory_order_relaxed)) + { + std::shared_ptr res; + res.swap(ptr->data); + int const count_increase=old_head.external_count-2; + if(ptr->internal_count.fetch_add( + count_increase,std::memory_order_release)==-count_increase) + { + delete ptr; + } + return res; + } + else if(ptr->internal_count.fetch_add( + -1,std::memory_order_relaxed)==1) + { + ptr->internal_count.load(std::memory_order_acquire); + delete ptr; + } + } + } +}; diff --git a/source/listing_7.13.cpp b/source/listing_7.13.cpp new file mode 100644 index 0000000..f4a4f42 --- /dev/null +++ b/source/listing_7.13.cpp @@ -0,0 +1,62 @@ +#include +#include + +template +class lock_free_queue +{ +private: + struct node + { + std::shared_ptr data; + node* next; + node(): + next(nullptr) + {} + }; + std::atomic head; + std::atomic tail; + node* pop_head() + { + node* const old_head=head.load(); + if(old_head==tail.load()) + { + return nullptr; + } + head.store(old_head->next); + return old_head; + } +public: + lock_free_queue(): + head(new node),tail(head.load()) + {} + lock_free_queue(const lock_free_queue& other)=delete; + lock_free_queue& operator=(const lock_free_queue& other)=delete; + ~lock_free_queue() + { + while(node* const old_head=head.load()) + { + head.store(old_head->next); + delete old_head; + } + } + std::shared_ptr pop() + { + node* old_head=pop_head(); + if(!old_head) + { + return std::shared_ptr(); + } + std::shared_ptr const res(old_head->data); + delete old_head; + return res; + } + void push(T new_value) + { + std::shared_ptr new_data(std::make_shared(new_value)); + node* p=new node; + node* const old_tail=tail.load(); + old_tail->data.swap(new_data); + old_tail->next=p; + tail.store(p); + } +}; diff --git a/source/listing_7.14.cpp b/source/listing_7.14.cpp new file mode 100644 index 0000000..0aced25 --- /dev/null +++ b/source/listing_7.14.cpp @@ -0,0 +1,20 @@ +void push(T new_value) +{ + std::unique_ptr new_data(new T(new_value)); + counted_node_ptr new_next; + new_next.ptr=new node; + new_next.external_count=1; + for(;;) + { + node* const old_tail=tail.load(); + T* old_data=nullptr; + if(old_tail->data.compare_exchange_strong( + old_data,new_data.get())) + { + old_tail->next=new_next; + tail.store(new_next.ptr); + new_data.release(); + break; + } + } +} diff --git a/source/listing_7.15.cpp b/source/listing_7.15.cpp new file mode 100644 index 0000000..2342ad3 --- /dev/null +++ b/source/listing_7.15.cpp @@ -0,0 +1,59 @@ +#include + +template +class lock_free_queue +{ +private: + struct node; + struct counted_node_ptr + { + int external_count; + node* ptr; + }; + std::atomic head; + std::atomic tail; + struct node_counter + { + unsigned internal_count:30; + unsigned external_counters:2; + }; + struct node + { + std::atomic data; + std::atomic count; + counted_node_ptr next; + node() + { + node_counter new_count; + new_count.internal_count=0; + new_count.external_counters=2; + count.store(new_count); + next.ptr=nullptr; + next.external_count=0; + } + }; +public: + void push(T new_value) + { + std::unique_ptr new_data(new T(new_value)); + counted_node_ptr new_next; + new_next.ptr=new node; + new_next.external_count=1; + counted_node_ptr old_tail=tail.load(); + for(;;) + { + increase_external_count(tail,old_tail); + T* old_data=nullptr; + if(old_tail.ptr->data.compare_exchange_strong( + old_data,new_data.get())) + { + old_tail.ptr->next=new_next; + old_tail=tail.exchange(new_next); + free_external_counter(old_tail); + new_data.release(); + break; + } + old_tail.ptr->release_ref(); + } + } +}; diff --git a/source/listing_7.16.cpp b/source/listing_7.16.cpp new file mode 100644 index 0000000..a3d74bc --- /dev/null +++ b/source/listing_7.16.cpp @@ -0,0 +1,31 @@ +template +class lock_free_queue +{ +private: + struct node + { + void release_ref(); + }; +public: + std::unique_ptr pop() + { + counted_node_ptr old_head=head.load(std::memory_order_relaxed); + for(;;) + { + increase_external_count(head,old_head); + node* const ptr=old_head.ptr; + if(ptr==tail.load().ptr) + { + ptr->release_ref(); + return std::unique_ptr(); + } + if(head.compare_exchange_strong(old_head,ptr->next)) + { + T* const res=ptr->data.exchange(nullptr); + free_external_counter(old_head); + return std::unique_ptr(res); + } + ptr->release_ref(); + } + } +}; diff --git a/source/listing_7.17.cpp b/source/listing_7.17.cpp new file mode 100644 index 0000000..3262a51 --- /dev/null +++ b/source/listing_7.17.cpp @@ -0,0 +1,27 @@ +template +class lock_free_queue +{ +private: + struct node + { + void release_ref() + { + node_counter old_counter= + count.load(std::memory_order_relaxed); + node_counter new_counter; + do + { + new_counter=old_counter; + --new_counter.internal_count; + } + while(!count.compare_exchange_strong( + old_counter,new_counter, + std::memory_order_acquire,std::memory_order_relaxed)); + if(!new_counter.internal_count && + !new_counter.external_counters) + { + delete this; + } + } + }; +}; diff --git a/source/listing_7.18.cpp b/source/listing_7.18.cpp new file mode 100644 index 0000000..60f2291 --- /dev/null +++ b/source/listing_7.18.cpp @@ -0,0 +1,20 @@ +template +class lock_free_queue +{ +private: + static void increase_external_count( + std::atomic& counter, + counted_node_ptr& old_counter) + { + counted_node_ptr new_counter; + do + { + new_counter=old_counter; + ++new_counter.external_count; + } + while(!counter.compare_exchange_strong( + old_counter,new_counter, + std::memory_order_acquire,std::memory_order_relaxed)); + old_counter.external_count=new_counter.external_count; + } +}; diff --git a/source/listing_7.19.cpp b/source/listing_7.19.cpp new file mode 100644 index 0000000..0667b55 --- /dev/null +++ b/source/listing_7.19.cpp @@ -0,0 +1,27 @@ +template +class lock_free_queue +{ +private: + static void free_external_counter(counted_node_ptr &old_node_ptr) + { + node* const ptr=old_node_ptr.ptr; + int const count_increase=old_node_ptr.external_count-2; + node_counter old_counter= + ptr->count.load(std::memory_order_relaxed); + node_counter new_counter; + do + { + new_counter=old_counter; + --new_counter.external_counters; + new_counter.internal_count+=count_increase; + } + while(!ptr->count.compare_exchange_strong( + old_counter,new_counter, + std::memory_order_acquire,std::memory_order_relaxed)); + if(!new_counter.internal_count && + !new_counter.external_counters) + { + delete ptr; + } + } +}; diff --git a/source/listing_7.2.cpp b/source/listing_7.2.cpp new file mode 100644 index 0000000..f16271c --- /dev/null +++ b/source/listing_7.2.cpp @@ -0,0 +1,23 @@ +#include + +template +class lock_free_stack +{ +private: + struct node + { + T data; + node* next; + node(T const& data_): + data(data_) + {} + }; + std::atomic head; +public: + void push(T const& data) + { + node* const new_node=new node(data); + new_node->next=head.load(); + while(!head.compare_exchange_weak(new_node->next,new_node)); + } +}; diff --git a/source/listing_7.20.cpp b/source/listing_7.20.cpp new file mode 100644 index 0000000..c7de500 --- /dev/null +++ b/source/listing_7.20.cpp @@ -0,0 +1,33 @@ +template +class lock_free_queue +{ +private: + struct node + { + std::atomic data; + std::atomic count; + std::atomic next; + }; +public: + std::unique_ptr pop() + { + counted_node_ptr old_head=head.load(std::memory_order_relaxed); + for(;;) + { + increase_external_count(head,old_head); + node* const ptr=old_head.ptr; + if(ptr==tail.load().ptr) + { + return std::unique_ptr(); + } + counted_node_ptr next=ptr->next.load(); + if(head.compare_exchange_strong(old_head,next)) + { + T* const res=ptr->data.exchange(nullptr); + free_external_counter(old_head); + return std::unique_ptr(res); + } + ptr->release_ref(); + } + } +}; diff --git a/source/listing_7.21.cpp b/source/listing_7.21.cpp new file mode 100644 index 0000000..c13bca1 --- /dev/null +++ b/source/listing_7.21.cpp @@ -0,0 +1,55 @@ +template +class lock_free_queue +{ +private: + void set_new_tail(counted_node_ptr &old_tail, + counted_node_ptr const &new_tail) + { + node* const current_tail_ptr=old_tail.ptr; + while(!tail.compare_exchange_weak(old_tail,new_tail) && + old_tail.ptr==current_tail_ptr); + if(old_tail.ptr==current_tail_ptr) + free_external_counter(old_tail); + else + current_tail_ptr->release_ref(); + } +public: + void push(T new_value) + { + std::unique_ptr new_data(new T(new_value)); + counted_node_ptr new_next; + new_next.ptr=new node; + new_next.external_count=1; + counted_node_ptr old_tail=tail.load(); + for(;;) + { + increase_external_count(tail,old_tail); + T* old_data=nullptr; + if(old_tail.ptr->data.compare_exchange_strong( + old_data,new_data.get())) + { + counted_node_ptr old_next={0}; + if(!old_tail.ptr->next.compare_exchange_strong( + old_next,new_next)) + { + delete new_next.ptr; + new_next=old_next; + } + set_new_tail(old_tail, new_next); + new_data.release(); + break; + } + else + { + counted_node_ptr old_next={0}; + if(old_tail.ptr->next.compare_exchange_strong( + old_next,new_next)) + { + old_next=new_next; + new_next.ptr=new node; + } + set_new_tail(old_tail, old_next); + } + } + } +}; diff --git a/source/listing_7.3.cpp b/source/listing_7.3.cpp new file mode 100644 index 0000000..7568466 --- /dev/null +++ b/source/listing_7.3.cpp @@ -0,0 +1,31 @@ +#include +#include + +template +class lock_free_stack +{ +private: + struct node + { + std::shared_ptr data; + node* next; + node(T const& data_): + data(std::make_shared(data_)) + {} + }; + std::atomic head; +public: + void push(T const& data) + { + node* const new_node=new node(data); + new_node->next=head.load(); + while(!head.compare_exchange_weak(new_node->next,new_node)); + } + std::shared_ptr pop() + { + node* old_head=head.load(); + while(old_head && + !head.compare_exchange_weak(old_head,old_head->next)); + return old_head ? old_head->data : std::shared_ptr(); + } +}; diff --git a/source/listing_7.4.cpp b/source/listing_7.4.cpp new file mode 100644 index 0000000..c5cf97c --- /dev/null +++ b/source/listing_7.4.cpp @@ -0,0 +1,25 @@ +#include +#include + +template +class lock_free_stack +{ +private: + std::atomic threads_in_pop; + void try_reclaim(node* old_head); +public: + std::shared_ptr pop() + { + ++threads_in_pop; + node* old_head=head.load(); + while(old_head && + !head.compare_exchange_weak(old_head,old_head->next)); + std::shared_ptr res; + if(old_head) + { + res.swap(old_head->data); + } + try_reclaim(old_head); + return res; + } +}; diff --git a/source/listing_7.5.cpp b/source/listing_7.5.cpp new file mode 100644 index 0000000..34d6ff6 --- /dev/null +++ b/source/listing_7.5.cpp @@ -0,0 +1,57 @@ +#include + +template +class lock_free_stack +{ +private: + std::atomic to_be_deleted; + static void delete_nodes(node* nodes) + { + while(nodes) + { + node* next=nodes->next; + delete nodes; + nodes=next; + } + } + void try_reclaim(node* old_head) + { + if(threads_in_pop==1) + { + node* nodes_to_delete=to_be_deleted.exchange(nullptr); + if(!--threads_in_pop) + { + delete_nodes(nodes_to_delete); + } + else if(nodes_to_delete) + { + chain_pending_nodes(nodes_to_delete); + } + delete old_head; + } + else + { + chain_pending_node(old_head); + --threads_in_pop; + } + } + void chain_pending_nodes(node* nodes) + { + node* last=nodes; + while(node* const next=last->next) + { + last=next; + } + chain_pending_nodes(nodes,last); + } + void chain_pending_nodes(node* first,node* last) + { + last->next=to_be_deleted; + while(!to_be_deleted.compare_exchange_weak( + last->next,first)); + } + void chain_pending_node(node* n) + { + chain_pending_nodes(n,n); + } +}; diff --git a/source/listing_7.6.cpp b/source/listing_7.6.cpp new file mode 100644 index 0000000..3a2606b --- /dev/null +++ b/source/listing_7.6.cpp @@ -0,0 +1,36 @@ +#include +#include + +std::shared_ptr pop() +{ + std::atomic& hp=get_hazard_pointer_for_current_thread(); + node* old_head=head.load(); + do + { + node* temp; + do + { + temp=old_head; + hp.store(old_head); + old_head=head.load(); + } while(old_head!=temp); + } + while(old_head && + !head.compare_exchange_strong(old_head,old_head->next)); + hp.store(nullptr); + std::shared_ptr res; + if(old_head) + { + res.swap(old_head->data); + if(outstanding_hazard_pointers_for(old_head)) + { + reclaim_later(old_head); + } + else + { + delete old_head; + } + delete_nodes_with_no_hazards(); + } + return res; +} diff --git a/source/listing_7.7.cpp b/source/listing_7.7.cpp new file mode 100644 index 0000000..70603c8 --- /dev/null +++ b/source/listing_7.7.cpp @@ -0,0 +1,49 @@ +#include +#include + +unsigned const max_hazard_pointers=100; +struct hazard_pointer +{ + std::atomic id; + std::atomic pointer; +}; +hazard_pointer hazard_pointers[max_hazard_pointers]; +class hp_owner +{ + hazard_pointer* hp; +public: + hp_owner(hp_owner const&)=delete; + hp_owner operator=(hp_owner const&)=delete; + hp_owner(): + hp(nullptr) + { + for(unsigned i=0;i& get_pointer() + { + return hp->pointer; + } + ~hp_owner() + { + hp->pointer.store(nullptr); + hp->id.store(std::thread::id()); + } +}; +std::atomic& get_hazard_pointer_for_current_thread() +{ + thread_local static hp_owner hazard; + return hazard.get_pointer(); +} diff --git a/source/listing_7.8.cpp b/source/listing_7.8.cpp new file mode 100644 index 0000000..f7ba86c --- /dev/null +++ b/source/listing_7.8.cpp @@ -0,0 +1,51 @@ +#include + +template +void do_delete(void* p) +{ + delete static_cast(p); +} +struct data_to_reclaim +{ + void* data; + std::function deleter; + data_to_reclaim* next; + template + data_to_reclaim(T* p): + data(p), + deleter(&do_delete), + next(0) + {} + ~data_to_reclaim() + { + deleter(data); + } +}; +std::atomic nodes_to_reclaim; +void add_to_reclaim_list(data_to_reclaim* node) +{ + node->next=nodes_to_reclaim.load(); + while(!nodes_to_reclaim.compare_exchange_weak(node->next,node)); +} +template +void reclaim_later(T* data) +{ + add_to_reclaim_list(new data_to_reclaim(data)); +} +void delete_nodes_with_no_hazards() +{ + data_to_reclaim* current=nodes_to_reclaim.exchange(nullptr); + while(current) + { + data_to_reclaim* const next=current->next; + if(!outstanding_hazard_pointers_for(current->data)) + { + delete current; + } + else + { + add_to_reclaim_list(current); + } + current=next; + } +} diff --git a/source/listing_7.9.cpp b/source/listing_7.9.cpp new file mode 100644 index 0000000..d3c09b7 --- /dev/null +++ b/source/listing_7.9.cpp @@ -0,0 +1,32 @@ +#include +#include + +template +class lock_free_stack +{ +private: + struct node + { + std::shared_ptr data; + std::shared_ptr next; + node(T const& data_): + data(std::make_shared(data_)) + {} + }; + std::shared_ptr head; +public: + void push(T const& data) + { + std::shared_ptr const new_node=std::make_shared(data); + new_node->next=head.load(); + while(!std::atomic_compare_exchange_weak( + &head,&new_node->next,new_node)); + } + std::shared_ptr pop() + { + std::shared_ptr old_head=std::atomic_load(&head); + while(old_head && !std::atomic_compare_exchange_weak( + &head,&old_head,old_head->next)); + return old_head ? old_head->data : std::shared_ptr(); + } +}; diff --git a/source/listing_8.1.cpp b/source/listing_8.1.cpp new file mode 100644 index 0000000..e345302 --- /dev/null +++ b/source/listing_8.1.cpp @@ -0,0 +1,102 @@ +template +struct sorter +{ + struct chunk_to_sort + { + std::list data; + std::promise > promise; + }; + + thread_safe_stack chunks; + std::vector threads; + unsigned const max_thread_count; + std::atomic end_of_data; + + sorter(): + max_thread_count(std::thread::hardware_concurrency()-1), + end_of_data(false) + {} + + ~sorter() + { + end_of_data=true; + for(unsigned i=0;i chunk=chunks.pop(); + if(chunk) + { + sort_chunk(chunk); + } + } + + std::list do_sort(std::list& chunk_data) + { + if(chunk_data.empty()) + { + return chunk_data; + } + + std::list result; + result.splice(result.begin(),chunk_data,chunk_data.begin()); + T const& partition_val=*result.begin(); + + typename std::list::iterator divide_point= + std::partition(chunk_data.begin(),chunk_data.end(), + [&](T const& val){return val > new_lower= + new_lower_chunk.promise.get_future(); + chunks.push(std::move(new_lower_chunk)); + if(threads.size()::sort_thread,this)); + } + + std::list new_higher(do_sort(chunk_data)); + + result.splice(result.end(),new_higher); + while(new_lower.wait_for(std::chrono::seconds(0)) != + std::future_status::ready) + { + try_sort_chunk(); + } + + result.splice(result.begin(),new_lower.get()); + return result; + } + + void sort_chunk(boost::shared_ptr const& chunk) + { + chunk->promise.set_value(do_sort(chunk->data)); + } + + void sort_thread() + { + while(!end_of_data) + { + try_sort_chunk(); + std::this_thread::yield(); + } + } +}; + +template +std::list parallel_quick_sort(std::list input) +{ + if(input.empty()) + { + return input; + } + sorter s; + return s.do_sort(input); +} diff --git a/source/listing_8.10.cpp b/source/listing_8.10.cpp new file mode 100644 index 0000000..07241ea --- /dev/null +++ b/source/listing_8.10.cpp @@ -0,0 +1,45 @@ +template +Iterator parallel_find_impl(Iterator first,Iterator last,MatchType match, + std::atomic& done) +{ + try + { + unsigned long const length=std::distance(first,last); + unsigned long const min_per_thread=25; + if(length<(2*min_per_thread)) + { + for(;(first!=last) && !done.load();++first) + { + if(*first==match) + { + done=true; + return first; + } + } + return last; + } + else + { + Iterator const mid_point=first+(length/2); + std::future async_result= + std::async(¶llel_find_impl, + mid_point,last,match,std::ref(done)); + Iterator const direct_result= + parallel_find_impl(first,mid_point,match,done); + return (direct_result==mid_point)? + async_result.get():direct_result; + } + } + catch(...) + { + done=true; + throw; + } +} + +template +Iterator parallel_find(Iterator first,Iterator last,MatchType match) +{ + std::atomic done(false); + return parallel_find_impl(first,last,match,done); +} diff --git a/source/listing_8.11.cpp b/source/listing_8.11.cpp new file mode 100644 index 0000000..8595714 --- /dev/null +++ b/source/listing_8.11.cpp @@ -0,0 +1,93 @@ +template +void parallel_partial_sum(Iterator first,Iterator last) +{ + typedef typename Iterator::value_type value_type; + struct process_chunk + { + void operator()(Iterator begin,Iterator last, + std::future* previous_end_value, + std::promise* end_value) + { + try + { + Iterator end=last; + ++end; + std::partial_sum(begin,end,begin); + if(previous_end_value) + { + value_type& addend=previous_end_value->get(); + *last+=addend; + if(end_value) + { + end_value->set_value(*last); + } + std::for_each(begin,last,[addend](value_type& item) + { + item+=addend; + }); + } + else if(end_value) + { + end_value->set_value(*last); + } + } + catch(...) + { + if(end_value) + { + end_value->set_exception(std::current_exception()); + } + else + { + throw; + } + } + } + }; + + unsigned long const length=std::distance(first,last); + + if(!length) + return last; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + typedef typename Iterator::value_type value_type; + + std::vector threads(num_threads-1); + std::vector > + end_values(num_threads-1); + std::vector > + previous_end_values; + previous_end_values.reserve(num_threads-1); + join_threads joiner(threads); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_last=block_start; + std::advance(block_last,block_size-1); + threads[i]=std::thread(process_chunk(), + block_start,block_last, + (i!=0)?&previous_end_values[i-1]:0, + &end_values[i]); + block_start=block_last; + ++block_start; + previous_end_values.push_back(end_values[i].get_future()); + } + Iterator final_element=block_start; + std::advance(final_element,std::distance(block_start,last)-1); + process_chunk()(block_start,final_element, + (num_threads>1)?&previous_end_values.back():0, + 0); +} diff --git a/source/listing_8.12.cpp b/source/listing_8.12.cpp new file mode 100644 index 0000000..45b2d00 --- /dev/null +++ b/source/listing_8.12.cpp @@ -0,0 +1,24 @@ +class barrier +{ + unsigned const count; + std::atomic spaces; + std::atomic generation; +public: + explicit barrier(unsigned count_): + count(count_),spaces(count),generation(0) + {} + void wait() + { + unsigned const my_generation=generation; + if(!--spaces) + { + spaces=count; + ++generation; + } + else + { + while(generation==my_generation) + std::this_thread::yield(); + } + } +}; diff --git a/source/listing_8.13.cpp b/source/listing_8.13.cpp new file mode 100644 index 0000000..7e6b366 --- /dev/null +++ b/source/listing_8.13.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +struct join_threads +{ + join_threads(std::vector&) + {} +}; + + +struct barrier +{ + std::atomic count; + std::atomic spaces; + std::atomic generation; + barrier(unsigned count_): + count(count_),spaces(count_),generation(0) + {} + void wait() + { + unsigned const gen=generation.load(); + if(!--spaces) + { + spaces=count.load(); + ++generation; + } + else + { + while(generation.load()==gen) + { + std::this_thread::yield(); + } + } + } + + void done_waiting() + { + --count; + if(!--spaces) + { + spaces=count.load(); + ++generation; + } + } +}; + +template +void parallel_partial_sum(Iterator first,Iterator last) +{ + typedef typename Iterator::value_type value_type; + + struct process_element + { + void operator()(Iterator first,Iterator last, + std::vector& buffer, + unsigned i,barrier& b) + { + value_type& ith_element=*(first+i); + bool update_source=false; + for(unsigned step=0,stride=1;stride<=i;++step,stride*=2) + { + value_type const& source=(step%2)? + buffer[i]:ith_element; + value_type& dest=(step%2)? + ith_element:buffer[i]; + value_type const& addend=(step%2)? + buffer[i-stride]:*(first+i-stride); + dest=source+addend; + update_source=!(step%2); + b.wait(); + } + if(update_source) + { + ith_element=buffer[i]; + } + b.done_waiting(); + } + }; + + unsigned long const length=std::distance(first,last); + + if(length<=1) + return; + + std::vector buffer(length); + barrier b(length); + std::vector threads(length-1); + join_threads joiner(threads); + + Iterator block_start=first; + for(unsigned long i=0;i<(length-1);++i) + { + threads[i]=std::thread(process_element(),first,last, + std::ref(buffer),i,std::ref(b)); + } + process_element()(first,last,buffer,length-1,b); +} diff --git a/source/listing_8.2.cpp b/source/listing_8.2.cpp new file mode 100644 index 0000000..559f1ad --- /dev/null +++ b/source/listing_8.2.cpp @@ -0,0 +1,49 @@ +template +struct accumulate_block +{ + void operator()(Iterator first,Iterator last,T& result) + { + result=std::accumulate(first,last,result); + } +}; + +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return init; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::vector results(num_threads); + std::vector threads(num_threads-1); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + threads[i]=std::thread( + accumulate_block(), + block_start,block_end,std::ref(results[i])); + block_start=block_end; + } + accumulate_block()(block_start,last,results[num_threads-1]); + + std::for_each(threads.begin(),threads.end(), + std::mem_fn(&std::thread::join)); + + return std::accumulate(results.begin(),results.end(),init); +} diff --git a/source/listing_8.3.cpp b/source/listing_8.3.cpp new file mode 100644 index 0000000..30e0386 --- /dev/null +++ b/source/listing_8.3.cpp @@ -0,0 +1,56 @@ +template +struct accumulate_block +{ + T operator()(Iterator first,Iterator last) + { + return std::accumulate(first,last,T()); + } +}; + +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return init; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::vector > futures(num_threads-1); + std::vector threads(num_threads-1); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + std::packaged_task task( + accumulate_block()); + futures[i]=task.get_future(); + threads[i]=std::thread(std::move(task),block_start,block_end); + block_start=block_end; + } + T last_result=accumulate_block()(block_start,last); + + std::for_each(threads.begin(),threads.end(), + std::mem_fn(&std::thread::join)); + + T result=init; + for(unsigned long i=0;i<(num_threads-1);++i) + { + result+=futures[i].get(); + } + result += last_result; + return result; +} diff --git a/source/listing_8.4.cpp b/source/listing_8.4.cpp new file mode 100644 index 0000000..852af26 --- /dev/null +++ b/source/listing_8.4.cpp @@ -0,0 +1,44 @@ +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return init; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::vector > futures(num_threads-1); + std::vector threads(num_threads-1); + join_threads joiner(threads); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + std::packaged_task task( + accumulate_block()); + futures[i]=task.get_future(); + threads[i]=std::thread(std::move(task),block_start,block_end); + block_start=block_end; + } + T last_result=accumulate_block()(block_start,last); + T result=init; + for(unsigned long i=0;i<(num_threads-1);++i) + { + result+=futures[i].get(); + } + result += last_result; + return result; +} diff --git a/source/listing_8.5.cpp b/source/listing_8.5.cpp new file mode 100644 index 0000000..a5248a5 --- /dev/null +++ b/source/listing_8.5.cpp @@ -0,0 +1,20 @@ +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + unsigned long const max_chunk_size=25; + if(length<=max_chunk_size) + { + return std::accumulate(first,last,init); + } + else + { + Iterator mid_point=first; + std::advance(mid_point,length/2); + std::future first_half_result= + std::async(parallel_accumulate, + first,mid_point,init); + T second_half_result=parallel_accumulate(mid_point,last,T()); + return first_half_result.get()+second_half_result; + } +} diff --git a/source/listing_8.6.cpp b/source/listing_8.6.cpp new file mode 100644 index 0000000..09751ef --- /dev/null +++ b/source/listing_8.6.cpp @@ -0,0 +1,50 @@ +std::thread task_thread; +std::atomic task_cancelled(false); + +void gui_thread() +{ + while(true) + { + event_data event=get_event(); + if(event.type==quit) + break; + process(event); + } +} + +void task() +{ + while(!task_complete() && !task_cancelled) + { + do_next_operation(); + } + if(task_cancelled) + { + perform_cleanup(); + } + else + { + post_gui_event(task_complete); + } +} + +void process(event_data const& event) +{ + switch(event.type) + { + case start_task: + task_cancelled=false; + task_thread=std::thread(task); + break; + case stop_task: + task_cancelled=true; + task_thread.join(); + break; + case task_complete: + task_thread.join(); + display_results(); + break; + default: + //... + } +} diff --git a/source/listing_8.7.cpp b/source/listing_8.7.cpp new file mode 100644 index 0000000..0a1a916 --- /dev/null +++ b/source/listing_8.7.cpp @@ -0,0 +1,44 @@ +template +void parallel_for_each(Iterator first,Iterator last,Func f) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::vector > futures(num_threads-1); + std::vector threads(num_threads-1); + join_threads joiner(threads); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + std::packaged_task task( + [=]() + { + std::for_each(block_start,block_end,f); + }); + futures[i]=task.get_future(); + threads[i]=std::thread(std::move(task)); + block_start=block_end; + } + std::for_each(block_start,last,f); + for(unsigned long i=0;i<(num_threads-1);++i) + { + futures[i].get(); + } +} diff --git a/source/listing_8.8.cpp b/source/listing_8.8.cpp new file mode 100644 index 0000000..f8ff7b0 --- /dev/null +++ b/source/listing_8.8.cpp @@ -0,0 +1,24 @@ +template +void parallel_for_each(Iterator first,Iterator last,Func f) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return; + + unsigned long const min_per_thread=25; + + if(length<(2*min_per_thread)) + { + std::for_each(first,last,f); + } + else + { + Iterator const mid_point=first+length/2; + std::future first_half= + std::async(¶llel_for_each, + first,mid_point,f); + parallel_for_each(mid_point,last,f); + first_half.get(); + } +} diff --git a/source/listing_8.9.cpp b/source/listing_8.9.cpp new file mode 100644 index 0000000..1750296 --- /dev/null +++ b/source/listing_8.9.cpp @@ -0,0 +1,76 @@ +template +Iterator parallel_find(Iterator first,Iterator last,MatchType match) +{ + struct find_element + { + void operator()(Iterator begin,Iterator end, + MatchType match, + std::promise* result, + std::atomic* done_flag) + { + try + { + for(;(begin!=end) && !done_flag->load();++begin) + { + if(*begin==match) + { + result->set_value(begin); + done_flag->store(true); + return; + } + } + } + catch(...) + { + try + { + result->set_exception(std::current_exception()); + done_flag->store(true); + } + catch(...) + {} + } + } + }; + + unsigned long const length=std::distance(first,last); + + if(!length) + return last; + + unsigned long const min_per_thread=25; + unsigned long const max_threads= + (length+min_per_thread-1)/min_per_thread; + + unsigned long const hardware_threads= + std::thread::hardware_concurrency(); + + unsigned long const num_threads= + std::min(hardware_threads!=0?hardware_threads:2,max_threads); + + unsigned long const block_size=length/num_threads; + + std::promise result; + std::atomic done_flag(false); + std::vector threads(num_threads-1); + { + join_threads joiner(threads); + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + threads[i]=std::thread(find_element(), + block_start,block_end,match, + &result,&done_flag); + block_start=block_end; + } + find_element()(block_start,last,match,&result,&done_flag); + } + if(!done_flag.load()) + { + return last; + } + return result.get_future().get(); +} diff --git a/source/listing_9.1.cpp b/source/listing_9.1.cpp new file mode 100644 index 0000000..dd3ca84 --- /dev/null +++ b/source/listing_9.1.cpp @@ -0,0 +1,53 @@ +class thread_pool +{ + std::atomic_bool done; + thread_safe_queue > work_queue; + std::vector threads; + join_threads joiner; + + void worker_thread() + { + while(!done) + { + std::function task; + if(work_queue.try_pop(task)) + { + task(); + } + else + { + std::this_thread::yield(); + } + } + } +public: + thread_pool(): + done(false),joiner(threads) + { + unsigned const thread_count=std::thread::hardware_concurrency(); + try + { + for(unsigned i=0;i + void submit(FunctionType f) + { + work_queue.push(std::function(f)); + } +}; diff --git a/source/listing_9.10.cpp b/source/listing_9.10.cpp new file mode 100644 index 0000000..f9641ad --- /dev/null +++ b/source/listing_9.10.cpp @@ -0,0 +1,9 @@ +void interruptible_wait(std::condition_variable& cv, + std::unique_lock& lk) +{ + interruption_point(); + this_thread_interrupt_flag.set_condition_variable(cv); + cv.wait(lk); + this_thread_interrupt_flag.clear_condition_variable(); + interruption_point(); +} diff --git a/source/listing_9.11.cpp b/source/listing_9.11.cpp new file mode 100644 index 0000000..ea05a8a --- /dev/null +++ b/source/listing_9.11.cpp @@ -0,0 +1,57 @@ +class interrupt_flag +{ + std::atomic flag; + std::condition_variable* thread_cond; + std::mutex set_clear_mutex; + +public: + interrupt_flag(): + thread_cond(0) + {} + + void set() + { + flag.store(true,std::memory_order_relaxed); + std::lock_guard lk(set_clear_mutex); + if(thread_cond) + { + thread_cond->notify_all(); + } + } + + bool is_set() const + { + return flag.load(std::memory_order_relaxed); + } + + void set_condition_variable(std::condition_variable& cv) + { + std::lock_guard lk(set_clear_mutex); + thread_cond=&cv; + } + + void clear_condition_variable() + { + std::lock_guard lk(set_clear_mutex); + thread_cond=0; + } + + struct clear_cv_on_destruct + { + ~clear_cv_on_destruct() + { + this_thread_interrupt_flag.clear_condition_variable(); + } + }; +}; + +void interruptible_wait(std::condition_variable& cv, + std::unique_lock& lk) +{ + interruption_point(); + this_thread_interrupt_flag.set_condition_variable(cv); + interrupt_flag::clear_cv_on_destruct guard; + interruption_point(); + cv.wait_for(lk,std::chrono::milliseconds(1)); + interruption_point(); +} diff --git a/source/listing_9.12.cpp b/source/listing_9.12.cpp new file mode 100644 index 0000000..29101ce --- /dev/null +++ b/source/listing_9.12.cpp @@ -0,0 +1,77 @@ +class interrupt_flag +{ + std::atomic flag; + std::condition_variable* thread_cond; + std::condition_variable_any* thread_cond_any; + std::mutex set_clear_mutex; + +public: + interrupt_flag(): + Interrupting threads + thread_cond(0),thread_cond_any(0) + {} + + void set() + { + flag.store(true,std::memory_order_relaxed); + std::lock_guard lk(set_clear_mutex); + if(thread_cond) + { + thread_cond->notify_all(); + } + else if(thread_cond_any) + { + thread_cond_any->notify_all(); + } + } + + template + void wait(std::condition_variable_any& cv,Lockable& lk) + { + struct custom_lock + { + interrupt_flag* self; + Lockable& lk; + + custom_lock(interrupt_flag* self_, + std::condition_variable_any& cond, + Lockable& lk_): + self(self_),lk(lk_) + { + self->set_clear_mutex.lock(); + self->thread_cond_any=&cond; + } + + void unlock() + { + lk.unlock(); + self->set_clear_mutex.unlock(); + } + + void lock() + { + std::lock(self->set_clear_mutex,lk); + } + + ~custom_lock() + { + self->thread_cond_any=0; + self->set_clear_mutex.unlock(); + } + }; + + custom_lock cl(this,cv,lk); + interruption_point(); + cv.wait(cl); + interruption_point(); + } + + // rest as before +}; + +template +void interruptible_wait(std::condition_variable_any& cv, + Lockable& lk) +{ + this_thread_interrupt_flag.wait(cv,lk); +} diff --git a/source/listing_9.13.cpp b/source/listing_9.13.cpp new file mode 100644 index 0000000..5ec1da0 --- /dev/null +++ b/source/listing_9.13.cpp @@ -0,0 +1,38 @@ +std::mutex config_mutex; +std::vector background_threads; + +void background_thread(int disk_id) +{ + while(true) + { + interruption_point(); + fs_change fsc=get_fs_changes(disk_id); + if(fsc.has_changes()) + { + update_index(fsc); + } + } +} + +void start_background_processing() +{ + background_threads.push_back( + interruptible_thread(background_thread,disk_1)); + background_threads.push_back( + interruptible_thread(background_thread,disk_2)); +} + +int main() +{ + start_background_processing(); + process_gui_until_exit(); + std::unique_lock lk(config_mutex); + for(unsigned i=0;i +#include +#include +#include +#include +#include + +class function_wrapper +{ + struct impl_base { + virtual void call()=0; + virtual ~impl_base() {} + }; + std::unique_ptr impl; + template + struct impl_type: impl_base + { + F f; + impl_type(F&& f_): f(std::move(f_)) {} + void call() { f(); } + }; +public: + template + function_wrapper(F&& f): + impl(new impl_type(std::move(f))) + {} + + void call() { impl->call(); } + + function_wrapper(function_wrapper&& other): + impl(std::move(other.impl)) + {} + + function_wrapper& operator=(function_wrapper&& other) + { + impl=std::move(other.impl); + return *this; + } + + function_wrapper(const function_wrapper&)=delete; + function_wrapper(function_wrapper&)=delete; + function_wrapper& operator=(const function_wrapper&)=delete; +}; + +class thread_pool +{ +public: + std::deque work_queue; + + template + std::future::type> + submit(FunctionType f) + { + typedef typename std::result_of::type result_type; + + std::packaged_task task(std::move(f)); + std::future res(task.get_future()); + work_queue.push_back(std::move(task)); + return res; + } + // rest as before +}; diff --git a/source/listing_9.3.cpp b/source/listing_9.3.cpp new file mode 100644 index 0000000..9a16073 --- /dev/null +++ b/source/listing_9.3.cpp @@ -0,0 +1,31 @@ +template +T parallel_accumulate(Iterator first,Iterator last,T init) +{ + unsigned long const length=std::distance(first,last); + + if(!length) + return init; + + unsigned long const block_size=25; + unsigned long const num_blocks=(length+block_size-1)/block_size; + + std::vector > futures(num_blocks-1); + thread_pool pool; + + Iterator block_start=first; + for(unsigned long i=0;i<(num_threads-1);++i) + { + Iterator block_end=block_start; + std::advance(block_end,block_size); + futures[i]=pool.submit(accumulate_block()); + block_start=block_end; + } + T last_result=accumulate_block()(block_start,last); + T result=init; + for(unsigned long i=0;i<(num_blocks-1);++i) + { + result+=futures[i].get(); + } + result += last_result; + return result; +} diff --git a/source/listing_9.4.cpp b/source/listing_9.4.cpp new file mode 100644 index 0000000..9d1beea --- /dev/null +++ b/source/listing_9.4.cpp @@ -0,0 +1,12 @@ +void thread_pool::run_pending_task() +{ + function_wrapper task; + if(work_queue.try_pop(task)) + { + task(); + } + else + { + std::this_thread::yield(); + } +} diff --git a/source/listing_9.5.cpp b/source/listing_9.5.cpp new file mode 100644 index 0000000..83b83b4 --- /dev/null +++ b/source/listing_9.5.cpp @@ -0,0 +1,58 @@ +template +struct sorter +{ + thread_pool pool; + + std::list do_sort(std::list& chunk_data) + { + if(chunk_data.empty()) + { + return chunk_data; + } + + std::list result; + result.splice(result.begin(),chunk_data,chunk_data.begin()); + T const& partition_val=*result.begin(); + + typename std::list::iterator divide_point= + std::partition( + chunk_data.begin(),chunk_data.end(), + [&](T const& val){return val new_lower_chunk; + new_lower_chunk.splice( + new_lower_chunk.end(), + chunk_data,chunk_data.begin(), + divide_point); + + thread_pool::task_handle > new_lower= + pool.submit( + std::bind( + &sorter::do_sort,this, + std::move(new_lower_chunk))); + + std::list new_higher(do_sort(chunk_data)); + + result.splice(result.end(),new_higher); + while(!new_lower.is_ready()) + { + pool.run_pending_task(); + } + + result.splice(result.begin(),new_lower.get()); + return result; + } +}; + + +template +std::list parallel_quick_sort(std::list input) +{ + if(input.empty()) + { + return input; + } + sorter s; + + return s.do_sort(input); +} diff --git a/source/listing_9.6.cpp b/source/listing_9.6.cpp new file mode 100644 index 0000000..840382d --- /dev/null +++ b/source/listing_9.6.cpp @@ -0,0 +1,58 @@ +class thread_pool +{ + thread_safe_queue pool_work_queue; + + typedef std::queue local_queue_type; + static thread_local std::unique_ptr + local_work_queue; + + void worker_thread() + { + local_work_queue.reset(new local_queue_type); + + while(!done) + { + run_pending_task(); + } + } + +public: + template + std::future::type> + submit(FunctionType f) + { + typedef std::result_of::type result_type; + + std::packaged_task task(f); + std::future res(task.get_future()); + if(local_work_queue) + { + local_work_queue->push(std::move(task)); + } + else + { + pool_work_queue.push(std::move(task)); + } + return res; + } + + void run_pending_task() + { + function_wrapper task; + if(local_work_queue && !local_work_queue->empty()) + { + task=std::move(local_work_queue->front()); + local_work_queue->pop(); + task(); + } + else if(pool_work_queue.try_pop(task)) + { + task(); + } + else + { + std::this_thread::yield(); + } + } + // rest as before +}; diff --git a/source/listing_9.7.cpp b/source/listing_9.7.cpp new file mode 100644 index 0000000..2ebdb29 --- /dev/null +++ b/source/listing_9.7.cpp @@ -0,0 +1,53 @@ +class work_stealing_queue +{ +private: + typedef function_wrapper data_type; + std::deque the_queue; + mutable std::mutex the_mutex; + +public: + work_stealing_queue() + {} + + work_stealing_queue(const work_stealing_queue& other)=delete; + work_stealing_queue& operator=( + const work_stealing_queue& other)=delete; + + void push(data_type data) + { + std::lock_guard lock(the_mutex); + the_queue.push_front(std::move(data)); + } + + bool empty() const + { + std::lock_guard lock(the_mutex); + return the_queue.empty(); + } + + bool try_pop(data_type& res) + { + std::lock_guard lock(the_mutex); + if(the_queue.empty()) + { + return false; + } + + res=std::move(the_queue.front()); + the_queue.pop_front(); + return true; + } + + bool try_steal(data_type& res) + { + std::lock_guard lock(the_mutex); + if(the_queue.empty()) + { + return false; + } + + res=std::move(the_queue.back()); + the_queue.pop_back(); + return true; + } +}; diff --git a/source/listing_9.8.cpp b/source/listing_9.8.cpp new file mode 100644 index 0000000..f2bbf09 --- /dev/null +++ b/source/listing_9.8.cpp @@ -0,0 +1,112 @@ +class thread_pool +{ + typedef function_wrapper task_type; + + std::atomic_bool done; + thread_safe_queue pool_work_queue; + std::vector > queues; + std::vector threads; + join_threads joiner; + + static thread_local work_stealing_queue* local_work_queue; + static thread_local unsigned my_index; + + void worker_thread(unsigned my_index_) + { + my_index=my_index_; + local_work_queue=queues[my_index].get(); + while(!done) + { + run_pending_task(); + } + } + + bool pop_task_from_local_queue(task_type& task) + { + return local_work_queue && local_work_queue->try_pop(task); + } + + bool pop_task_from_pool_queue(task_type& task) + { + return pool_work_queue.try_pop(task); + } + + bool pop_task_from_other_thread_queue(task_type& task) + { + for(unsigned i=0;itry_steal(task)) + { + return true; + } + } + + return false; + } + +public: + thread_pool(): + joiner(threads),done(false) + { + unsigned const thread_count=std::thread::hardware_concurrency(); + + try + { + for(unsigned i=0;i( + new work_stealing_queue)); + threads.push_back( + std::thread(&thread_pool::worker_thread,this,i)); + } + } + catch(...) + { + done=true; + throw; + } + } + + ~thread_pool() + { + done=true; + } + + template + using task_handle=std::unique_future; + + template + task_handle::type> submit( + FunctionType f) + { + typedef std::result_of::type result_type; + + std::packaged_task task(f); + task_handle res(task.get_future()); + if(local_work_queue) + { + local_work_queue->push(std::move(task)); + } + else + { + pool_work_queue.push(std::move(task)); + } + return res; + } + + void run_pending_task() + { + task_type task; + if(pop_task_from_local_queue(task) || + pop_task_from_pool_queue(task) || + pop_task_from_other_thread_queue(task)) + { + task(); + } + else + { + std::this_thread::yield(); + } + } +}; diff --git a/source/listing_9.9.cpp b/source/listing_9.9.cpp new file mode 100644 index 0000000..ffd7835 --- /dev/null +++ b/source/listing_9.9.cpp @@ -0,0 +1,31 @@ +class interrupt_flag +{ +public: + void set(); + bool is_set() const; +}; +thread_local interrupt_flag this_thread_interrupt_flag; + +class interruptible_thread +{ + std::thread internal_thread; + interrupt_flag* flag; +public: + template + interruptible_thread(FunctionType f) + { + std::promise p; + internal_thread=std::thread([f,&p]{ + p.set_value(&this_thread_interrupt_flag); + f(); + }); + flag=p.get_future().get(); + } + void interrupt() + { + if(flag) + { + flag->set(); + } + } +}; diff --git a/source/listing_a.1.cpp b/source/listing_a.1.cpp new file mode 100644 index 0000000..5a5956e --- /dev/null +++ b/source/listing_a.1.cpp @@ -0,0 +1,23 @@ +class X +{ +private: + int* data; +public: + X(): + data(new int[1000000]) + {} + ~X() + { + delete [] data; + } + X(const X& other): + data(new int[1000000]) + { + std::copy(other.data,other.data+1000000,data); + } + X(X&& other): + data(other.data) + { + other.data=nullptr; + } +}; diff --git a/source/listing_a.2.cpp b/source/listing_a.2.cpp new file mode 100644 index 0000000..647b826 --- /dev/null +++ b/source/listing_a.2.cpp @@ -0,0 +1,18 @@ +class move_only +{ + std::unique_ptr data; +public: + move_only(const move_only&) = delete; + move_only(move_only&& other): + data(std::move(other.data)) + {} + move_only& operator=(const move_only&) = delete; + move_only& operator=(move_only&& other) + { + data=std::move(other.data); + return *this; + } +}; +move_only m1; +move_only m2(m1); +move_only m3(std::move(m1)); diff --git a/source/listing_a.3.cpp b/source/listing_a.3.cpp new file mode 100644 index 0000000..fd85c29 --- /dev/null +++ b/source/listing_a.3.cpp @@ -0,0 +1,23 @@ +class CX +{ +private: + int a; + int b; +public: + CX() = default; + CX(int a_, int b_): + a(a_),b(b_) + {} + int get_a() const + { + return a; + } + int get_b() const + { + return b; + } + int foo() const + { + return a+b; + } +}; diff --git a/source/listing_a.4.cpp b/source/listing_a.4.cpp new file mode 100644 index 0000000..24a43f9 --- /dev/null +++ b/source/listing_a.4.cpp @@ -0,0 +1,8 @@ +std::condition_variable cond; +bool data_ready; +std::mutex m; +void wait_for_data() +{ + std::unique_lock lk(m); + cond.wait(lk,[]{return data_ready;}); +} diff --git a/source/listing_c.1.cpp b/source/listing_c.1.cpp new file mode 100644 index 0000000..bb66f44 --- /dev/null +++ b/source/listing_c.1.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include + + + + +namespace messaging +{ + struct message_base + { + virtual ~message_base() + {} + }; + + template + struct wrapped_message: + message_base + { + Msg contents; + explicit wrapped_message(Msg const& contents_): + contents(contents_) + {} + }; + + class queue + { + std::mutex m; + std::condition_variable c; + message_base + std::queue > q; + public: + template + void push(T const& msg) + { + std::lock_guard lk(m); + q.push(std::make_shared >(msg)); + c.notify_all(); + } + std::shared_ptr wait_and_pop() + { + std::unique_lock lk(m); + c.wait(lk,[&]{return !q.empty();}); + auto res=q.front(); + q.pop(); + return res; + } + }; +} diff --git a/source/listing_c.10.cpp b/source/listing_c.10.cpp new file mode 100644 index 0000000..3a3af51 --- /dev/null +++ b/source/listing_c.10.cpp @@ -0,0 +1,51 @@ +int main() +{ + bank_machine bank; + interface_machine interface_hardware; + atm machine(bank.get_sender(),interface_hardware.get_sender()); + std::thread bank_thread(&bank_machine::run,&bank); + std::thread if_thread(&interface_machine::run,&interface_hardware); + std::thread atm_thread(&atm::run,&machine); + messaging::sender atmqueue(machine.get_sender()); + bool quit_pressed=false; + while(!quit_pressed) + { + char c=getchar(); + switch(c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + atmqueue.send(digit_pressed(c)); + break; + case 'b': + atmqueue.send(balance_pressed()); + break; + case 'w': + atmqueue.send(withdraw_pressed(50)); + break; + case 'c': + atmqueue.send(cancel_pressed()); + break; + case 'q': + quit_pressed=true; + break; + case 'i': + atmqueue.send(card_inserted("acc1234")); + break; + } + } + bank.done(); + machine.done(); + interface_hardware.done(); + atm_thread.join(); + bank_thread.join(); + if_thread.join(); +} diff --git a/source/listing_c.2.cpp b/source/listing_c.2.cpp new file mode 100644 index 0000000..822b509 --- /dev/null +++ b/source/listing_c.2.cpp @@ -0,0 +1,22 @@ +namespace messaging +{ + class sender + { + queue*q; + public: + sender(): + q(nullptr) + {} + explicit sender(queue*q_): + q(q_) + {} + template + void send(Message const& msg) + { + if(q) + { + q->push(msg); + } + } + }; +} diff --git a/source/listing_c.3.cpp b/source/listing_c.3.cpp new file mode 100644 index 0000000..6396f50 --- /dev/null +++ b/source/listing_c.3.cpp @@ -0,0 +1,16 @@ +namespace messaging +{ + class receiver + { + queue q; + public: + operator sender() + { + return sender(&q); + } + dispatcher wait() + { + return dispatcher(&q); + } + }; +} diff --git a/source/listing_c.4.cpp b/source/listing_c.4.cpp new file mode 100644 index 0000000..2ca84a1 --- /dev/null +++ b/source/listing_c.4.cpp @@ -0,0 +1,65 @@ +namespace messaging +{ + class close_queue + {}; + + class dispatcher + { + queue* q; + bool chained; + + dispatcher(dispatcher const&)=delete; + dispatcher& operator=(dispatcher const&)=delete; + + template< + typename Dispatcher, + typename Msg, + typename Func> + friend class TemplateDispatcher; + + void wait_and_dispatch() + { + for(;;) + { + auto msg=q->wait_and_pop(); + dispatch(msg); + } + } + + bool dispatch( + std::shared_ptr const& msg) + { + if(dynamic_cast*>(msg.get())) + { + throw close_queue(); + } + return false; + } + public: + dispatcher(dispatcher&& other): + q(other.q),chained(other.chained) + { + other.chained=true; + } + + explicit dispatcher(queue* q_): + q(q_),chained(false) + {} + + template + TemplateDispatcher + handle(Func&& f) + { + return TemplateDispatcher( + q,this,std::forward(f)); + } + + ~dispatcher() noexcept(false) + { + if(!chained) + { + wait_and_dispatch(); + } + } + }; +} diff --git a/source/listing_c.5.cpp b/source/listing_c.5.cpp new file mode 100644 index 0000000..41fa481 --- /dev/null +++ b/source/listing_c.5.cpp @@ -0,0 +1,71 @@ +namespace messaging +{ + template + class TemplateDispatcher + { + queue* q; + PreviousDispatcher* prev; + Func f; + bool chained; + + TemplateDispatcher(TemplateDispatcher const&)=delete; + TemplateDispatcher& operator=(TemplateDispatcher const&)=delete; + + template + friend class TemplateDispatcher; + + void wait_and_dispatch() + { + for(;;) + { + auto msg=q->wait_and_pop(); + if(dispatch(msg)) + break; + } + } + + bool dispatch(std::shared_ptr const& msg) + { + if(wrapped_message* wrapper= + dynamic_cast*>(msg.get())) + { + f(wrapper->contents); + return true; + } + else + { + return prev->dispatch(msg); + } + } + public: + TemplateDispatcher(TemplateDispatcher&& other): + q(other.q),prev(other.prev),f(std::move(other.f)), + chained(other.chained) + { + other.chained=true; + } + + TemplateDispatcher(queue* q_,PreviousDispatcher* prev_,Func&& f_): + q(q_),prev(prev_),f(std::forward(f_)),chained(false) + { + prev_->chained=true; + } + + template + TemplateDispatcher + handle(OtherFunc&& of) + { + return TemplateDispatcher< + TemplateDispatcher,OtherMsg,OtherFunc>( + q,this,std::forward(of)); + } + + ~TemplateDispatcher() noexcept(false) + { + if(!chained) + { + wait_and_dispatch(); + } + } + }; +} diff --git a/source/listing_c.6.cpp b/source/listing_c.6.cpp new file mode 100644 index 0000000..14d05fa --- /dev/null +++ b/source/listing_c.6.cpp @@ -0,0 +1,118 @@ +struct withdraw +{ + std::string account; + unsigned amount; + mutable messaging::sender atm_queue; + withdraw(std::string const& account_, + unsigned amount_, + messaging::sender atm_queue_): + account(account_),amount(amount_), + atm_queue(atm_queue_) + {} +}; +struct withdraw_ok +{}; +struct withdraw_denied +{}; +struct cancel_withdrawal +{ + std::string account; + unsigned amount; + cancel_withdrawal(std::string const& account_, + unsigned amount_): + account(account_),amount(amount_) + {} +}; +struct withdrawal_processed +{ + std::string account; + unsigned amount; + withdrawal_processed(std::string const& account_, + unsigned amount_): + account(account_),amount(amount_) + {} +}; +struct card_inserted +{ + std::string account; + explicit card_inserted(std::string const& account_): + account(account_) + {} +}; +struct digit_pressed +{ + char digit; + explicit digit_pressed(char digit_): + digit(digit_) + {} +}; +struct clear_last_pressed +{}; +struct eject_card +{}; +struct withdraw_pressed +{ + unsigned amount; + explicit withdraw_pressed(unsigned amount_): + amount(amount_) + {} +}; +struct cancel_pressed +{}; +struct issue_money +{ + unsigned amount; + issue_money(unsigned amount_): + amount(amount_) + {} +}; +struct verify_pin +{ + std::string account; + std::string pin; + mutable messaging::sender atm_queue; + verify_pin(std::string const& account_,std::string const& pin_, + messaging::sender atm_queue_): + account(account_),pin(pin_),atm_queue(atm_queue_) + {} +}; +struct pin_verified +{}; +struct pin_incorrect +{}; +struct display_enter_pin +{}; +struct display_enter_card +{}; +struct display_insufficient_funds +{}; +struct display_withdrawal_cancelled +{}; +struct display_pin_incorrect_message +{}; +struct display_withdrawal_options +{}; +struct get_balance +{ + std::string account; + mutable messaging::sender atm_queue; + get_balance(std::string const& account_,messaging::sender atm_queue_): + account(account_),atm_queue(atm_queue_) + {} +}; +struct balance +{ + unsigned amount; + explicit balance(unsigned amount_): + amount(amount_) + {} +}; +struct display_balance +{ + unsigned amount; + explicit display_balance(unsigned amount_): + amount(amount_) + {} +}; +struct balance_pressed +{}; diff --git a/source/listing_c.7.cpp b/source/listing_c.7.cpp new file mode 100644 index 0000000..6ce0508 --- /dev/null +++ b/source/listing_c.7.cpp @@ -0,0 +1,187 @@ +class atm +{ + messaging::receiver incoming; + messaging::sender bank; + messaging::sender interface_hardware; + void (atm::*state)(); + std::string account; + unsigned withdrawal_amount; + std::string pin; + void process_withdrawal() + { + incoming.wait() + .handle( + [&](withdraw_ok const& msg) + { + interface_hardware.send( + issue_money(withdrawal_amount)); + bank.send( + withdrawal_processed(account,withdrawal_amount)); + state=&atm::done_processing; + } + ) + .handle( + [&](withdraw_denied const& msg) + { + interface_hardware.send(display_insufficient_funds()); + state=&atm::done_processing; + } + ) + .handle( + [&](cancel_pressed const& msg) + { + bank.send( + cancel_withdrawal(account,withdrawal_amount)); + interface_hardware.send( + display_withdrawal_cancelled()); + state=&atm::done_processing; + } + ); + } + void process_balance() + { + incoming.wait() + .handle( + [&](balance const& msg) + { + interface_hardware.send(display_balance(msg.amount)); + state=&atm::wait_for_action; + } + ) + .handle( + [&](cancel_pressed const& msg) + { + state=&atm::done_processing; + } + ); + } + void wait_for_action() + { + interface_hardware.send(display_withdrawal_options()); + incoming.wait() + .handle( + [&](withdraw_pressed const& msg) + { + withdrawal_amount=msg.amount; + bank.send(withdraw(account,msg.amount,incoming)); + state=&atm::process_withdrawal; + } + ) + .handle( + [&](balance_pressed const& msg) + { + bank.send(get_balance(account,incoming)); + state=&atm::process_balance; + } + ) + .handle( + [&](cancel_pressed const& msg) + { + state=&atm::done_processing; + } + ); + } + void verifying_pin() + { + incoming.wait() + .handle( + [&](pin_verified const& msg) + { + state=&atm::wait_for_action; + } + ) + .handle( + [&](pin_incorrect const& msg) + { + interface_hardware.send( + display_pin_incorrect_message()); + state=&atm::done_processing; + } + ) + .handle( + [&](cancel_pressed const& msg) + { + state=&atm::done_processing; + } + ); + } + void getting_pin() + { + incoming.wait() + .handle( + [&](digit_pressed const& msg) + { + unsigned const pin_length=4; + pin+=msg.digit; + if(pin.length()==pin_length) + { + bank.send(verify_pin(account,pin,incoming)); + state=&atm::verifying_pin; + } + } + ) + .handle( + [&](clear_last_pressed const& msg) + { + if(!pin.empty()) + { + pin.pop_back(); + } + } + ) + .handle( + [&](cancel_pressed const& msg) + { + state=&atm::done_processing; + } + ); + } + void waiting_for_card() + { + interface_hardware.send(display_enter_card()); + incoming.wait() + .handle( + [&](card_inserted const& msg) + { + account=msg.account; + pin=""; + interface_hardware.send(display_enter_pin()); + state=&atm::getting_pin; + } + ); + } + void done_processing() + { + interface_hardware.send(eject_card()); + state=&atm::waiting_for_card; + } + atm(atm const&)=delete; + atm& operator=(atm const&)=delete; +public: + atm(messaging::sender bank_, + messaging::sender interface_hardware_): + bank(bank_),interface_hardware(interface_hardware_) + {} + void done() + { + get_sender().send(messaging::close_queue()); + } + void run() + { + state=&atm::waiting_for_card; + try + { + for(;;) + { + (this->*state)(); + } + } + catch(messaging::close_queue const&) + { + } + } + messaging::sender get_sender() + { + return incoming; + } +}; diff --git a/source/listing_c.8.cpp b/source/listing_c.8.cpp new file mode 100644 index 0000000..a8e5566 --- /dev/null +++ b/source/listing_c.8.cpp @@ -0,0 +1,73 @@ +class bank_machine +{ + messaging::receiver incoming; + unsigned balance; +public: + bank_machine(): + balance(199) + {} + void done() + { + get_sender().send(messaging::close_queue()); + } + void run() + { + try + { + for(;;) + { + incoming.wait() + .handle( + [&](verify_pin const& msg) + { + if(msg.pin=="1937") + { + msg.atm_queue.send(pin_verified()); + } + else + { + msg.atm_queue.send(pin_incorrect()); + } + } + ) + .handle( + [&](withdraw const& msg) + { + if(balance>=msg.amount) + { + msg.atm_queue.send(withdraw_ok()); + balance-=msg.amount; + } + else + { + msg.atm_queue.send(withdraw_denied()); + } + } + ) + .handle( + [&](get_balance const& msg) + { + msg.atm_queue.send(::balance(balance)); + } + ) + .handle( + [&](withdrawal_processed const& msg) + { + } + ) + .handle( + [&](cancel_withdrawal const& msg) + { + } + ); + } + } + catch(messaging::close_queue const&) + { + } + } + messaging::sender get_sender() + { + return incoming; + } +}; diff --git a/source/listing_c.9.cpp b/source/listing_c.9.cpp new file mode 100644 index 0000000..3d1a24a --- /dev/null +++ b/source/listing_c.9.cpp @@ -0,0 +1,117 @@ +class interface_machine +{ + messaging::receiver incoming; +public: + void done() + { + get_sender().send(messaging::close_queue()); + } + void run() + { + try + { + for(;;) + { + incoming.wait() + .handle( + [&](issue_money const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Issuing " + <( + [&](display_insufficient_funds const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Insufficient funds"<( + [&](display_enter_pin const& msg) + { + { + std::lock_guard lk(iom); + std::cout + <<"Please enter your PIN (0-9)" + <( + [&](display_enter_card const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Please enter your card (I)" + <( + [&](display_balance const& msg) + { + { + std::lock_guard lk(iom); + std::cout + <<"The balance of your account is " + <( + [&](display_withdrawal_options const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Withdraw 50? (w)"<( + [&](display_withdrawal_cancelled const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Withdrawal cancelled" + <( + [&](display_pin_incorrect_message const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"PIN incorrect"<( + [&](eject_card const& msg) + { + { + std::lock_guard lk(iom); + std::cout<<"Ejecting card"<