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#include "tbb/spin_mutex.h"
18#include "tbb/queuing_mutex.h"
19#include "tbb/queuing_rw_mutex.h"
20#include "tbb/spin_rw_mutex.h"
21#include "tbb/mutex.h"
22
23#include "tbb/tick_count.h"
24#include "tbb/atomic.h"
25
26#include "harness.h"
27
28// This test deliberately avoids a "using tbb" statement,
29// so that the error of putting types in the wrong namespace will be caught.
30
31template<typename M>
32struct Counter {
33 typedef M mutex_type;
34 M mutex;
35 volatile long value;
36 void flog_once( size_t mode );
37};
38
39template<typename M>
40void Counter<M>::flog_once(size_t mode)
41/** Increments counter once for each iteration in the iteration space. */
42{
43 if( mode&1 ) {
44 // Try implicit acquire and explicit release
45 typename mutex_type::scoped_lock lock(mutex);
46 value = value+1;
47 lock.release();
48 } else {
49 // Try explicit acquire and implicit release
50 typename mutex_type::scoped_lock lock;
51 lock.acquire(mutex);
52 value = value+1;
53 }
54}
55
56template<typename M, long N>
57struct Invariant {
58 typedef M mutex_type;
59 M mutex;
60 const char* mutex_name;
61 volatile long value[N];
62 Invariant( const char* mutex_name_ ) :
63 mutex_name(mutex_name_)
64 {
65 for( long k=0; k<N; ++k )
66 value[k] = 0;
67 }
68 void update() {
69 for( long k=0; k<N; ++k )
70 ++value[k];
71 }
72 bool value_is( long expected_value ) const {
73 long tmp;
74 for( long k=0; k<N; ++k )
75 if( (tmp=value[k])!=expected_value ) {
76 REPORT("ERROR: %ld!=%ld\n", tmp, expected_value);
77 return false;
78 }
79 return true;
80 }
81 bool is_okay() {
82 return value_is( value[0] );
83 }
84 void flog_once( size_t mode );
85};
86
87template<typename M, long N>
88void Invariant<M,N>::flog_once( size_t mode )
89{
90 //! Every 8th access is a write access
91 bool write = (mode%8)==7;
92 bool okay = true;
93 bool lock_kept = true;
94 if( (mode/8)&1 ) {
95 // Try implicit acquire and explicit release
96 typename mutex_type::scoped_lock lock(mutex,write);
97 if( write ) {
98 long my_value = value[0];
99 update();
100 if( mode%16==7 ) {
101 lock_kept = lock.downgrade_to_reader();
102 if( !lock_kept )
103 my_value = value[0] - 1;
104 okay = value_is(my_value+1);
105 }
106 } else {
107 okay = is_okay();
108 if( mode%8==3 ) {
109 long my_value = value[0];
110 lock_kept = lock.upgrade_to_writer();
111 if( !lock_kept )
112 my_value = value[0];
113 update();
114 okay = value_is(my_value+1);
115 }
116 }
117 lock.release();
118 } else {
119 // Try explicit acquire and implicit release
120 typename mutex_type::scoped_lock lock;
121 lock.acquire(mutex,write);
122 if( write ) {
123 long my_value = value[0];
124 update();
125 if( mode%16==7 ) {
126 lock_kept = lock.downgrade_to_reader();
127 if( !lock_kept )
128 my_value = value[0] - 1;
129 okay = value_is(my_value+1);
130 }
131 } else {
132 okay = is_okay();
133 if( mode%8==3 ) {
134 long my_value = value[0];
135 lock_kept = lock.upgrade_to_writer();
136 if( !lock_kept )
137 my_value = value[0];
138 update();
139 okay = value_is(my_value+1);
140 }
141 }
142 }
143 if( !okay ) {
144 REPORT( "ERROR for %s at %ld: %s %s %s %s\n",mutex_name, long(mode),
145 write?"write,":"read,", write?(mode%16==7?"downgrade,":""):(mode%8==3?"upgrade,":""),
146 lock_kept?"lock kept,":"lock not kept,", (mode/8)&1?"imp/exp":"exp/imp" );
147 }
148}
149
150static tbb::atomic<size_t> Order;
151
152template<typename State, long TestSize>
153struct Work: NoAssign {
154 static const size_t chunk = 100;
155 State& state;
156 Work( State& state_ ) : state(state_) {}
157 void operator()( int ) const {
158 size_t step;
159 while( (step=Order.fetch_and_add<tbb::acquire>(chunk))<TestSize )
160 for( size_t i=0; i<chunk && step<TestSize; ++i, ++step )
161 state.flog_once(step);
162 }
163};
164
165//! Generic test of a TBB Mutex type M.
166/** Does not test features specific to reader-writer locks. */
167template<typename M>
168void Test( const char * name, int nthread ) {
169 REMARK("testing %s\n",name);
170 Counter<M> counter;
171 counter.value = 0;
172 Order = 0;
173 // use the macro because of a gcc 4.6 bug
174#define TEST_SIZE 100000
175 tbb::tick_count t0 = tbb::tick_count::now();
176 NativeParallelFor( nthread, Work<Counter<M>, TEST_SIZE>(counter) );
177 tbb::tick_count t1 = tbb::tick_count::now();
178
179 REMARK("%s time = %g usec\n",name, (t1-t0).seconds() );
180 if( counter.value!=TEST_SIZE )
181 REPORT("ERROR for %s: counter.value=%ld != %ld=test_size\n",name,counter.value,TEST_SIZE);
182#undef TEST_SIZE
183}
184
185
186//! Generic test of TBB ReaderWriterMutex type M
187template<typename M>
188void TestReaderWriter( const char * mutex_name, int nthread ) {
189 REMARK("testing %s\n",mutex_name);
190 Invariant<M,8> invariant(mutex_name);
191 Order = 0;
192 // use the macro because of a gcc 4.6 bug
193#define TEST_SIZE 1000000
194 tbb::tick_count t0 = tbb::tick_count::now();
195 NativeParallelFor( nthread, Work<Invariant<M,8>, TEST_SIZE>(invariant) );
196 tbb::tick_count t1 = tbb::tick_count::now();
197 // There is either a writer or a reader upgraded to a writer for each 4th iteration
198 long expected_value = TEST_SIZE/4;
199 if( !invariant.value_is(expected_value) )
200 REPORT("ERROR for %s: final invariant value is wrong\n",mutex_name);
201 REMARK("%s readers & writers time = %g usec\n",mutex_name,(t1-t0).seconds());
202#undef TEST_SIZE
203}
204
205int TestMain () {
206 for( int p=MinThread; p<=MaxThread; ++p ) {
207 REMARK( "testing with %d threads\n", p );
208 Test<tbb::spin_mutex>( "spin_mutex", p );
209 Test<tbb::queuing_mutex>( "queuing_mutex", p );
210 Test<tbb::queuing_rw_mutex>( "queuing_rw_mutex", p );
211 Test<tbb::spin_rw_mutex>( "spin_rw_mutex", p );
212 Test<tbb::mutex>( "mutex", p );
213 TestReaderWriter<tbb::queuing_rw_mutex>( "queuing_rw_mutex", p );
214 TestReaderWriter<tbb::spin_rw_mutex>( "spin_rw_mutex", p );
215 }
216 return Harness::Done;
217}
218