libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
locking.hpp
Go to the documentation of this file.
1 #ifndef LIBJMMCG_CORE_LOCKING_HPP
2 #define LIBJMMCG_CORE_LOCKING_HPP
3 
4 /******************************************************************************
5 ** Copyright © 2002 by J.M.McGuiness, coder@hussar.me.uk
6 **
7 ** This library is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU Lesser General Public
9 ** License as published by the Free Software Foundation; either
10 ** version 2.1 of the License, or (at your option) any later version.
11 **
12 ** This library is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ** Lesser General Public License for more details.
16 **
17 ** You should have received a copy of the GNU Lesser General Public
18 ** License along with this library; if not, write to the Free Software
19 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 
23 #include "trace.hpp"
24 
25 #include <boost/mpl/assert.hpp>
26 #include <boost/static_assert.hpp>
27 #include <boost/thread/locks.hpp>
28 
29 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE { namespace ppd { namespace lock {
30 
31  template<class LkT>
32  struct lockable {
33  using lock_traits=LkT;
34  typedef typename lock_traits::atomic_state_type atomic_state_type;
35  typedef typename lock_traits::timeout_type timeout_type;
36 
37  virtual atomic_state_type __fastcall lock() noexcept(false)=0;
38  virtual atomic_state_type __fastcall lock(const timeout_type) noexcept(false)=0;
39  virtual atomic_state_type __fastcall try_lock() noexcept(true) FORCE_INLINE;
40  virtual atomic_state_type __fastcall unlock() noexcept(true)=0;
41  };
42 
43  template<class LkT>
44  struct settable {
45  using lock_traits=LkT;
46  typedef typename lock_traits::atomic_state_type atomic_state_type;
47 
48  virtual atomic_state_type __fastcall set() noexcept(true)=0;
49  virtual atomic_state_type __fastcall reset() noexcept(true)=0;
50  };
51 
52  template<class LkT>
53  struct lockable_settable : public lockable<LkT>, public settable<LkT> {
54  using lock_traits=LkT;
55  typedef typename lock_traits::atomic_state_type atomic_state_type;
56 
57  /**
58  To assist in allowing compile-time computation of the algorithmic order of the threading model.
59  */
61  };
62 
63  /// An adaptor to turn ppd::lockable in boost::lockable objects.
64  /**
65  Mainly this required because boost assumes (naughtily, in my opinion) the calling convention, whereas libjmmcg specifies a calling convention, which is particularly important on Windows as on that platform the default calling convention is ... erm ... variable, at best it is good, but on old compilers, technically speaking, utter pants.
66  */
67  template<class Lk>
69  public:
70  typedef Lk atomic_t; ///< The type of the lock to be adapted.
71  typedef typename atomic_t::lock_traits lock_traits;
72 
73  private:
74  atomic_t &lk;
75 
76  public:
77  explicit __stdcall boost_lk_compat(atomic_t &l) noexcept(true) FORCE_INLINE;
79 
80  bool try_lock() noexcept(noexcept(lk.try_lock())) FORCE_INLINE;
81  void lock() noexcept(noexcept(lk.lock())) FORCE_INLINE;
82  void unlock() noexcept(noexcept(lk.unlock())) FORCE_INLINE;
83  };
84 
85  template<class LockObject>
86  class scope_lock : public lockable<typename LockObject::lock_traits> {
87  public:
88  typedef lockable<typename LockObject::lock_traits> base_t;
89  typedef LockObject atomic_t;
90  typedef typename atomic_t::lock_traits lock_traits;
94 
95  /**
96  To assist in allowing compile-time computation of the algorithmic order of the threading model.
97  */
98  static constexpr generic_traits::memory_access_modes memory_access_mode=atomic_t::memory_access_mode;
99 
100  protected:
101  explicit __stdcall scope_lock(atomic_t &lo) noexcept(true) FORCE_INLINE;
102  __stdcall scope_lock(atomic_t &lo,const timeout_type period) noexcept(true) FORCE_INLINE;
103  scope_lock(scope_lock const &)=delete;
104  virtual __stdcall ~scope_lock() noexcept(true) FORCE_INLINE;
105 
106  lock_result_type __fastcall try_lock() noexcept(true) override FORCE_INLINE;
107  lock_result_type __fastcall lock() noexcept(false) override FORCE_INLINE;
108  lock_result_type __fastcall lock(const timeout_type period) noexcept(false) override FORCE_INLINE;
109 
110  lock_result_type __fastcall unlock() noexcept(true) override FORCE_INLINE;
111  void decay() noexcept(true) FORCE_INLINE;
112 
113  protected:
115  };
116 
117  /// A class to control access to resource, possibly using the stack, using the RAII idiom.
118  /**
119  Use this lock for multiple threads in the same process space.
120  Class to manipulate an in-process lock on the stack, with locking on
121  construction, release on destruction.
122  (The "LockObject &" will be valid.)
123  */
124  template<class LockObject>
125  class in_process : public scope_lock<LockObject> {
126  public:
127  typedef scope_lock<LockObject> base_t;
128  typedef typename base_t::atomic_t atomic_t;
129  typedef typename atomic_t::lock_traits lock_traits;
133 
134  /// Get control of the lock for the resource.
135  /**
136  Note that this may lock the resource.
137 
138  \param lo The lock for the resource.
139  \param period The period that should be waited for attempting to gain the lock on the resource.
140 
141  \see infinite_timeout()
142  */
143  explicit __stdcall in_process(atomic_t &lo, const timeout_type period=lock_traits::infinite_timeout()) noexcept(true) FORCE_INLINE;
144  /// Release the lock on the resource.
145  __stdcall ~in_process() noexcept(true) FORCE_INLINE;
146 
147  void decay() noexcept(true) FORCE_INLINE;
148 
149  protected:
150  lock_result_type __fastcall try_lock() noexcept(true) override FORCE_INLINE;
151  lock_result_type __fastcall lock() noexcept(false) override FORCE_INLINE;
152  /**
153  \param period The period that should be waited for attempting to gain the lock on the resource.
154 
155  \see infinite_timeout()
156  */
157  lock_result_type __fastcall lock(const timeout_type period) noexcept(false) override FORCE_INLINE;
158 
159  lock_result_type __fastcall unlock() noexcept(true) override FORCE_INLINE;
160  };
161 
162  /// A class to control access to resource, possibly using the stack, using the RAII idiom.
163  /**
164  * Use this lock for multiple threads in the same process space.
165  * Class to manipulate an in-process lock on the stack, with locking on
166  * construction, release on destruction.
167  * (The "LockObject &" will be valid.)
168  */
169  template<class LockObject>
170  class in_process_unlockable : public lockable<typename LockObject::lock_traits> {
171  public:
172  typedef lockable<typename LockObject::lock_traits> base_t;
173  typedef LockObject atomic_t;
174  typedef typename atomic_t::lock_traits lock_traits;
178 
179  /**
180  To assist in allowing compile-time computation of the algorithmic order of the threading model.
181  */
182  static constexpr generic_traits::memory_access_modes memory_access_mode=atomic_t::memory_access_mode;
183 
184  explicit __stdcall in_process_unlockable(atomic_t &lo) noexcept(true) FORCE_INLINE;
185  /// Get control of the lock for the resource.
186  /**
187  * Note that this may lock the resource.
188  *
189  * \param lo The lock for the resource.
190  * \param period The period that should be waited for attempting to gain the lock on the resource.
191  *
192  * \see infinite_timeout()
193  */
194  __stdcall in_process_unlockable(atomic_t &lo,const timeout_type period) noexcept(true) FORCE_INLINE;
196  /// Release the lock on the resource.
197  virtual __stdcall ~in_process_unlockable() noexcept(true) FORCE_INLINE;
198 
199  lock_result_type __fastcall try_lock() noexcept(true) override FORCE_INLINE;
200  lock_result_type __fastcall unlock() noexcept(true) override FORCE_INLINE;
201  void decay() noexcept(true) FORCE_INLINE;
202 
203  private:
204  atomic_t &locker;
205  bool locked;
206 
207  lock_result_type __fastcall lock() noexcept(true) override FORCE_INLINE {return lock_traits::atom_set;}
208  lock_result_type __fastcall lock(const timeout_type) noexcept(true) override FORCE_INLINE {return lock_traits::atom_set;}
209  };
210 
211  template<
212  class St, ///< The various states that can be signalled.
213  St UnSig, ///< The initial, unsignalled state.
214  St Pri, ///< A single state that takes priority over all other states, so that if set, it will always be returned.
215  typename M,
216  typename WL=typename M::write_lock_type,
217  typename Mtx=M
218  >
220  public:
221  using lock_traits=typename M::lock_traits;
222  typedef typename lock_traits::atomic_state_type atomic_state_type;
223  typedef typename lock_traits::anon_semaphore_type anon_semaphore_type;
225  typedef Mtx locker_type; ///< The underlying lock object to use that will be locked in some (EREW or CREW or other) manner.
226  typedef typename locker_type::write_lock_type write_lock_type; ///< The type of write-lock to use. This allows the possibility of using a read-write lock.
227  typedef typename locker_type::read_lock_type read_lock_type; ///< The type of read lock to use, by default the write lock. This allows the possibility of using a read-write lock.
229  typedef St states;
230  typedef typename std::pair<states, atomic_state_type> lock_result_type;
231  static constexpr states unsignalled=UnSig;
232  static constexpr states priority=Pri;
233 
234  /**
235  To assist in allowing compile-time computation of the algorithmic order of the threading model.
236  */
242  );
243 
244  private:
245  atomic_t semaphore;
246 
247  public:
248 
249  BOOST_MPL_ASSERT((std::is_same<typename write_lock_type::atomic_t, locker_type>));
250 
252  : semaphore(lock_traits::atom_unset), state_(unsignalled) {
253  }
255 
256  /**
257  This locker() function is used in the thread_safe_adaptors collections to save on excess locking within those containers.
258 
259  \see safe_colln
260  \see queue
261  \see funky_queue
262  */
263  locker_type &__fastcall locker() noexcept(true) FORCE_INLINE {
264  return lock_;
265  }
266 
267  atomic_state_type __fastcall set_nolk(states const s) noexcept(noexcept(semaphore.set())) FORCE_INLINE {
268  if (state_!=priority) {
269  state_=s;
270  }
271  return semaphore.set();
272  }
273  atomic_state_type __fastcall set(states const s) noexcept(false) FORCE_INLINE {
274  const write_lock_type lk(lock_, locker_type::lock_traits::infinite_timeout());
275  return set_nolk(s);
276  }
277  atomic_state_type __fastcall reset() noexcept(true) FORCE_INLINE {
278  const write_lock_type lk(lock_, locker_type::lock_traits::infinite_timeout());
279  const atomic_state_type ret=semaphore.reset();
280  if (ret==lock_traits::atom_unset) {
281  state_=unsignalled;
282  }
283  return ret;
284  }
285  void clear() noexcept(noexcept(semaphore.try_lock())) FORCE_INLINE {
286  while (semaphore.try_lock()==lock_traits::atom_set);
287  state_=unsignalled;
288  }
289 
290  lock_result_type __fastcall lock() noexcept(noexcept(semaphore.lock())) FORCE_INLINE {
291  const atomic_state_type l=semaphore.lock();
292  return lock_result_type(state_, l);
293  }
294  lock_result_type __fastcall lock(const typename lock_traits::timeout_type t) noexcept(noexcept(semaphore.lock(t))) FORCE_INLINE {
295  const atomic_state_type l=semaphore.lock(t);
296  return lock_result_type(state_, l);
297  }
298  lock_result_type __fastcall try_lock() noexcept(noexcept(semaphore.try_lock())) FORCE_INLINE {
299  const atomic_state_type l=semaphore.try_lock();
300  return lock_result_type(state_, l);
301  }
302  lock_result_type __fastcall unlock() noexcept(noexcept(semaphore.unlock())) {
303  const atomic_state_type l=semaphore.unlock();
304  return lock_result_type(state_, l);
305  }
306  count_type __fastcall count() const noexcept(noexcept(semaphore.count())) FORCE_INLINE {
307  return semaphore.count();
308  }
309 
310  private:
311  states state_;
312  locker_type lock_;
313  };
314 
315 /// This namespace is for containing lock types that lock two lockable objects jointly, atomically.
316 namespace any_order {
317 
318  /// Lock all of the lockable objects, and wait until they are both locked.
319  /**
320  \todo JMG: implement a specialisation for Win32 API using "WaitForMultipleObjects()".
321 
322  \see boost::lock()
323  */
324  template<
325  generic_traits::api_type API_, ///< Allow specialisation for the various APIs that might support alternative implementations.
326  typename Mdl_,
327  class Lk1,
328  class Lk2
329  >
330  class all : public std::binary_function<Lk1, Lk2, void> {
331  public:
332  typedef std::binary_function<Lk1, Lk2, void> base_t;
337 
338  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==atomic2_t::lock_traits::api_type);
339  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, typename atomic2_t::lock_traits::model_type>));
340  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==API_);
341  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, Mdl_>));
342 
343  /**
344  To assist in allowing compile-time computation of the algorithmic order of the threading model.
345  */
351  );
352 
354  all(all const &)=delete;
355  __stdcall ~all() noexcept(true) FORCE_INLINE;
356 
357  private:
360  };
361  template<
362  generic_traits::api_type API_,
363  class Lk1,
364  class Lk2
365  >
366  class all<API_, sequential_mode, Lk1, Lk2> : public std::binary_function<Lk1, Lk2, void> {
367  public:
368  typedef std::binary_function<Lk1, Lk2, void> base_t;
373 
374  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==atomic2_t::lock_traits::api_type);
375  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, typename atomic2_t::lock_traits::model_type>));
376  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==API_);
377  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, sequential_mode>));
378 
379  /**
380  To assist in allowing compile-time computation of the algorithmic order of the threading model.
381  */
383 
384  constexpr all(first_argument_type &, second_argument_type &) noexcept(true) FORCE_INLINE {}
385  all(all const &)=delete;
386  };
387 
388  /// Try to lock at least one of the lockable objects.
389  /**
390  \todo JMG: implement a specialisation for Win32 API using "WaitForMultipleObjects()".
391 
392  \see boost::try_lock()
393  */
394  template<
395  generic_traits::api_type API_, ///< Allow specialisation for the various APIs that might support alternative implementations.
396  typename Mdl_,
397  class Lk1,
398  class Lk2
399  >
400  class try_one : public std::binary_function<Lk1,Lk2,int> {
401  public:
402  typedef std::binary_function<Lk1,Lk2,int> base_t;
407  typedef typename base_t::result_type result_type;
408 
409  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==atomic2_t::lock_traits::api_type);
410  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, typename atomic2_t::lock_traits::model_type>));
411  BOOST_STATIC_ASSERT(atomic1_t::lock_traits::api_type==API_);
412  BOOST_MPL_ASSERT((std::is_same<typename atomic1_t::lock_traits::model_type, Mdl_>));
413 
414  /**
415  To assist in allowing compile-time computation of the algorithmic order of the threading model.
416  */
422  );
423 
424  public:
425  __stdcall try_one(Lk1 &l1,Lk2 &l2) noexcept(true) FORCE_INLINE;
426  try_one(try_one const &)=delete;
427 
428  result_type __fastcall try_lock() noexcept(noexcept(boost::try_lock(std::declval<Lk1>(), std::declval<Lk2>()))) FORCE_INLINE;
429 
430  private:
431  lock::boost_lk_compat<Lk1> lk1;
432  lock::boost_lk_compat<Lk2> lk2;
433  };
434 
435 }
436 
437 } } } }
438 
439 #include "anon_spin_event.hpp"
440 #include "locking_impl.hpp"
441 
442 #endif