1/*
2 Copyright (c) 2005-2019 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15*/
16
17#define HARNESS_DEFAULT_MIN_THREADS 1
18#define HARNESS_DEFAULT_MAX_THREADS 3
19
20//------------------------------------------------------------------------
21// Test TBB mutexes when used with parallel_for.h
22//
23// Usage: test_Mutex.exe [-v] nthread
24//
25// The -v option causes timing information to be printed.
26//
27// Compile with _OPENMP and -openmp
28//------------------------------------------------------------------------
29#include "../test/harness_defs.h"
30#include "tbb/atomic.h"
31#include "tbb/blocked_range.h"
32#include "tbb/parallel_for.h"
33#include "tbb/tick_count.h"
34#include "../test/harness.h"
35#include "spin_rw_mutex_v2.h"
36#include <cstdlib>
37#include <cstdio>
38
39// This test deliberately avoids a "using tbb" statement,
40// so that the error of putting types in the wrong namespace will be caught.
41
42template<typename M>
43struct Counter {
44 typedef M mutex_type;
45 M mutex;
46 volatile long value;
47};
48
49//! Function object for use with parallel_for.h.
50template<typename C>
51struct AddOne: NoAssign {
52 C& counter;
53 /** Increments counter once for each iteration in the iteration space. */
54 void operator()( tbb::blocked_range<size_t>& range ) const {
55 for( size_t i=range.begin(); i!=range.end(); ++i ) {
56 if( i&1 ) {
57 // Try implicit acquire and explicit release
58 typename C::mutex_type::scoped_lock lock(counter.mutex);
59 counter.value = counter.value+1;
60 lock.release();
61 } else {
62 // Try explicit acquire and implicit release
63 typename C::mutex_type::scoped_lock lock;
64 lock.acquire(counter.mutex);
65 counter.value = counter.value+1;
66 }
67 }
68 }
69 AddOne( C& counter_ ) : counter(counter_) {}
70};
71
72//! Generic test of a TBB mutex type M.
73/** Does not test features specific to reader-writer locks. */
74template<typename M>
75void Test( const char * name ) {
76 if( Verbose ) {
77 printf("%s time = ",name);
78 fflush(stdout);
79 }
80 Counter<M> counter;
81 counter.value = 0;
82 const int n = 100000;
83 tbb::tick_count t0 = tbb::tick_count::now();
84 tbb::parallel_for(tbb::blocked_range<size_t>(0,n,n/10),AddOne<Counter<M> >(counter));
85 tbb::tick_count t1 = tbb::tick_count::now();
86 if( Verbose )
87 printf("%g usec\n",(t1-t0).seconds());
88 if( counter.value!=n )
89 printf("ERROR for %s: counter.value=%ld\n",name,counter.value);
90}
91
92template<typename M, size_t N>
93struct Invariant {
94 typedef M mutex_type;
95 M mutex;
96 const char* mutex_name;
97 volatile long value[N];
98 Invariant( const char* mutex_name_ ) :
99 mutex_name(mutex_name_)
100 {
101 for( size_t k=0; k<N; ++k )
102 value[k] = 0;
103 }
104 void update() {
105 for( size_t k=0; k<N; ++k )
106 ++value[k];
107 }
108 bool value_is( long expected_value ) const {
109 long tmp;
110 for( size_t k=0; k<N; ++k )
111 if( (tmp=value[k])!=expected_value ) {
112 printf("ERROR: %ld!=%ld\n", tmp, expected_value);
113 return false;
114 }
115 return true;
116 }
117 bool is_okay() {
118 return value_is( value[0] );
119 }
120};
121
122//! Function object for use with parallel_for.h.
123template<typename I>
124struct TwiddleInvariant: NoAssign {
125 I& invariant;
126 TwiddleInvariant( I& invariant_ ) : invariant(invariant_) {}
127
128 /** Increments counter once for each iteration in the iteration space. */
129 void operator()( tbb::blocked_range<size_t>& range ) const {
130 for( size_t i=range.begin(); i!=range.end(); ++i ) {
131 //! Every 8th access is a write access
132 const bool write = (i%8)==7;
133 bool okay = true;
134 bool lock_kept = true;
135 if( (i/8)&1 ) {
136 // Try implicit acquire and explicit release
137 typename I::mutex_type::scoped_lock lock(invariant.mutex,write);
138 execute_aux(lock, i, write, okay, lock_kept);
139 lock.release();
140 } else {
141 // Try explicit acquire and implicit release
142 typename I::mutex_type::scoped_lock lock;
143 lock.acquire(invariant.mutex,write);
144 execute_aux(lock, i, write, okay, lock_kept);
145 }
146 if( !okay ) {
147 printf( "ERROR for %s at %ld: %s %s %s %s\n",invariant.mutex_name, long(i),
148 write ? "write," : "read,",
149 write ? (i%16==7?"downgrade,":"") : (i%8==3?"upgrade,":""),
150 lock_kept ? "lock kept," : "lock not kept,", // TODO: only if downgrade/upgrade
151 (i/8)&1 ? "impl/expl" : "expl/impl" );
152 }
153 }
154 }
155private:
156 void execute_aux(typename I::mutex_type::scoped_lock & lock, const size_t i, const bool write, bool & okay, bool & lock_kept) const {
157 if( write ) {
158 long my_value = invariant.value[0];
159 invariant.update();
160 if( i%16==7 ) {
161 lock_kept = lock.downgrade_to_reader();
162 if( !lock_kept )
163 my_value = invariant.value[0] - 1;
164 okay = invariant.value_is(my_value+1);
165 }
166 } else {
167 okay = invariant.is_okay();
168 if( i%8==3 ) {
169 long my_value = invariant.value[0];
170 lock_kept = lock.upgrade_to_writer();
171 if( !lock_kept )
172 my_value = invariant.value[0];
173 invariant.update();
174 okay = invariant.value_is(my_value+1);
175 }
176 }
177 }
178};
179
180/** This test is generic so that we can test any other kinds of ReaderWriter locks we write later. */
181template<typename M>
182void TestReaderWriterLock( const char * mutex_name ) {
183 if( Verbose ) {
184 printf("%s readers & writers time = ",mutex_name);
185 fflush(stdout);
186 }
187 Invariant<M,8> invariant(mutex_name);
188 const size_t n = 500000;
189 tbb::tick_count t0 = tbb::tick_count::now();
190 tbb::parallel_for(tbb::blocked_range<size_t>(0,n,n/100),TwiddleInvariant<Invariant<M,8> >(invariant));
191 tbb::tick_count t1 = tbb::tick_count::now();
192 // There is either a writer or a reader upgraded to a writer for each 4th iteration
193 long expected_value = n/4;
194 if( !invariant.value_is(expected_value) )
195 printf("ERROR for %s: final invariant value is wrong\n",mutex_name);
196 if( Verbose )
197 printf("%g usec\n", (t1-t0).seconds());
198}
199
200/** Test try_acquire functionality of a non-reenterable mutex */
201template<typename M>
202void TestTryAcquire_OneThread( const char * mutex_name ) {
203 M tested_mutex;
204 typename M::scoped_lock lock1;
205 if( lock1.try_acquire(tested_mutex) )
206 lock1.release();
207 else
208 printf("ERROR for %s: try_acquire failed though it should not\n", mutex_name);
209 {
210 typename M::scoped_lock lock2(tested_mutex);
211 if( lock1.try_acquire(tested_mutex) )
212 printf("ERROR for %s: try_acquire succeeded though it should not\n", mutex_name);
213 }
214 if( lock1.try_acquire(tested_mutex) )
215 lock1.release();
216 else
217 printf("ERROR for %s: try_acquire failed though it should not\n", mutex_name);
218}
219
220#include "tbb/task_scheduler_init.h"
221
222int TestMain () {
223 for( int p=MinThread; p<=MaxThread; ++p ) {
224 tbb::task_scheduler_init init( p );
225 if( Verbose )
226 printf( "testing with %d workers\n", static_cast<int>(p) );
227 const int n = 3;
228 // Run each test several times.
229 for( int i=0; i<n; ++i ) {
230 Test<tbb::spin_rw_mutex>( "Spin RW Mutex" );
231 TestTryAcquire_OneThread<tbb::spin_rw_mutex>("Spin RW Mutex"); // only tests try_acquire for writers
232 TestReaderWriterLock<tbb::spin_rw_mutex>( "Spin RW Mutex" );
233 }
234 if( Verbose )
235 printf( "calling destructor for task_scheduler_init\n" );
236 }
237 return Harness::Done;
238}
239