C++ Library Extensions 2022.12.09
To help learn modern C++ programming
035-parallel_synchronization.cpp
Go to the documentation of this file.
1#include <tpf_output.hpp>
3
4#include <sstream>
5#include <set>
6#include <string>
7#include <mutex>
8#include <thread>
9#include <execution>
10
13
15{
16 size_t count = 10;
17 using element_t = double;
18 std::vector<element_t> v(count);
19
20 // this is a random number generator
21 // that generates elements for vector v, of type element_t or int,
22 // ranging from 0 up to count, inclusive.
23 auto generator = tpf::chrono_random::random_generator<element_t>(0, count);
24
25 // we generate random numbers and initialize container v
26 for(size_t i = 0; i < count; ++i)
27 v[i] = generator();
28
29 stream << "Before: " << v << endl;
30
32 using lock_type = std::lock_guard<std::mutex>;
33
34 std::set<std::string> threads_set;
35
36 std::for_each(std::execution::par_unseq, v.begin(), v.end(),
37 [lambda_init = int{}, &mutex, &threads_set](auto&& e)
38 {
39 std::stringstream os; // os is a local variable
40 // we do not need synchronize local variables
41
42 int local_int;
43
44 os << "Thread Id = " << std::this_thread::get_id()
45 << "\t &lambda_init: " << &lambda_init;
46
47 // we should always narrow down the locked scope
48 // to prevent dead-lock as well as to improve the performance
49 {
50 // std::lock_guard<std::mutex> lock(mutex)
51 lock_type lock(mutex);
52
53 threads_set.insert( os.str() );
54
55 // std::cout is a global console output stream
56 // this cout is shared among multiple threads
57 // so, it should be enclosed in the locked scope
58 std::cout << os.str() << std::endl;
59 }
60 } );
61
62 // this is out of parallel algorithm
63 stream << "\n Number of threads used: " << threads_set.size() << endl;
64
65 stream << "Threads Info: " << threads_set << endl;
66
67}
68
70{
71 size_t count = 10;
72 using element_t = double;
73 std::vector<element_t> v(count);
74
75 // this is a random number generator
76 // that generates elements for vector v, of type element_t or int,
77 // ranging from 0 up to count, inclusive.
78 auto generator = tpf::chrono_random::random_generator<element_t>(0, count);
79
80 // we generate random numbers and initialize container v
81 for(size_t i = 0; i < count; ++i)
82 v[i] = generator();
83
84 stream << "Before: " << v << endl;
85
87 using lock_type = std::lock_guard<std::mutex>;
88
89 std::set<std::string> threads_set;
90
91 std::for_each(std::execution::par_unseq, v.begin(), v.end(),
92 [lambda_init = int{}, &mutex, &threads_set](auto&& e)
93 {
94 std::stringstream os; // os is a local variable
95 // we do not need synchronize local variables
96
97 int local_int; // each thread has its own copy of local_int for all compilers
98
99 // In case of lambda_init
100 // microsoft compiler - lambda_init is shared among threads
101 // g++ and clang++ - each thread has its copy of lambda_init
102
103 os << "Thread Id = " << std::this_thread::get_id()
104 << "\t &lambda_init: " << &lambda_init
105 << "\t&local_int: " << &local_int;
106
107 // we should always narrow down the locked scope
108 // to prevent dead-lock as well as to improve the performance
109 {
110 // std::lock_guard<std::mutex> lock(mutex)
111 lock_type lock(mutex);
112
113 threads_set.insert( os.str() );
114
115 // std::cout is a global console output stream
116 // this cout is shared among multiple threads
117 // so, it should be enclosed in the locked scope
118 std::cout << os.str() << std::endl;
119 }
120 } );
121
122 // this is out of parallel algorithm
123 stream << "\n Number of threads used: " << threads_set.size() << endl;
124
125 stream << "Threads Info: " << threads_set << endl;
126
127}
128
130
131class functor
132{
133 private:
134 int lambda_init;
135
136
137 inline static std::mutex mutex;
138
139 std::set<std::string> &threads_set;
140 // inline static is introduced to C++17 standard
141
142 using lock_type = std::lock_guard<std::mutex>;
143
144 public:
145 functor(std::set<std::string>& threads): threads_set{threads}
146 {
147 lock_type lock(mutex);
148
149 std::cout << "Default constructor is called" << std::endl;
150 ++thread_count;
151 }
152 functor(const functor& rhs): threads_set{rhs.threads_set}
153 {
154 lock_type lock(mutex);
155
156 ++thread_count;
157 std::cout << "Copy constructor is called" << std::endl;
158 }
159
160 functor(functor&& rhs): threads_set{rhs.threads_set}
161 {
162 lock_type lock(mutex);
163 // ++thread_count;
164 std::cout << "Move constructor is called" << std::endl;
165 }
166
167 template<typename Type>
168 void operator()(Type&& e) const // this trailing const specifier is IMPORTANT
169 {
170 std::stringstream os; // os is a local variable
171 // we do not need synchronize local variables
172
173 int local_int; // each thread has its own copy of local_int for all compilers
174
175 // In case of lambda_init
176 // microsoft compiler - lambda_init is shared among threads
177 // g++ and clang++ - each thread has its copy of lambda_init
178
179 os << "Thread Id = " << std::this_thread::get_id()
180 << "\t &lambda_init: " << &lambda_init
181 << "\t&local_int: " << &local_int;
182
183 // we should always narrow down the locked scope
184 // to prevent dead-lock as well as to improve the performance
185 {
186 // std::lock_guard<std::mutex> lock(mutex)
187 lock_type lock(mutex);
188
189 threads_set.insert( os.str() );
190
191 // std::cout is a global console output stream
192 // this cout is shared among multiple threads
193 // so, it should be enclosed in the locked scope
194 std::cout << os.str() << std::endl;
195 }
196 }
197
199 {
200 // --thread_count
201 }
202};
203
205{
206 size_t count = 10;
207 using element_t = double;
208 std::vector<element_t> v(count);
209
210 // this is a random number generator
211 // that generates elements for vector v, of type element_t or int,
212 // ranging from 0 up to count, inclusive.
213 auto generator = tpf::chrono_random::random_generator<element_t>(0, count);
214
215 // we generate random numbers and initialize container v
216 for(size_t i = 0; i < count; ++i)
217 v[i] = generator();
218
219 stream << "Before: " << v << endl;
220
222 using lock_type = std::lock_guard<std::mutex>;
223
224 std::set<std::string> threads_set;
225
226 std::for_each(std::execution::par_unseq, v.begin(), v.end(), functor{threads_set});
227
228 // // this is out of parallel algorithm
229 stream << "\n Number of threads used: " << threads_set.size() << endl;
230 stream << "Threads Info: " << threads_set << endl;
231
232 stream << "thread_count - constructor count - " << thread_count << endl;
233
234}
235
236int main()
237{
238 // test_threads_in_parallel_algorithm();
239 // test_threads_in_parallel_algorithm_for_locals();
241}
std::mutex mutex
Definition: 022-mutex.cpp:12
std::atomic< int > count
Definition: 022-mutex.cpp:10
tpf::sstream stream
void test_threads_in_parallel_algorithm()
void test_threads_in_parallel_algorithm_for_locals()
void test_threads_in_parallel_algorithm_with_functor()
auto & cout
constexpr auto endl
Definition: tpf_output.hpp:973
functor(std::set< std::string > &threads)
void operator()(Type &&e) const
functor(const functor &rhs)
Stream output operators << are implemented.