]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/thread/doc/external_locking.qbk
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / boost / libs / thread / doc / external_locking.qbk
1 [/
2 / Copyright (c) 2008,2012,2014 Vicente J. Botet Escriba
3 /
4 / Distributed under the Boost Software License, Version 1.0. (See accompanying
5 / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 /]
7
8 [section External Locking -- `strict_lock` and `externally_locked` classes]
9
10
11 [note This tutorial is an adaptation of the paper by Andrei Alexandrescu "Multithreading and the C++ Type System"
12 to the Boost library.]
13
14 [/
15 [section Internal locking]
16
17 Consider, for example, modeling a bank account class that supports simultaneous deposits and withdrawals from multiple locations (arguably the "Hello, World" of multi-threaded programming). In the code below, guard's constructor locks the passed-in object this, and guard's destructor unlocks this.
18
19 class BankAccount {
20 boost::mutex mtx_; // explicit mutex declaration
21 int balance_;
22 public:
23 void Deposit(int amount) {
24 boost::lock_guard<boost::mutex> guard(mtx_);
25 balance_ += amount;
26 }
27 void Withdraw(int amount) {
28 boost::lock_guard<boost::mutex> guard(mtx_);
29 balance_ -= amount;
30 }
31 int GetBalance() {
32 boost::lock_guard<boost::mutex> guard(mtx_);
33 return balance_;
34 }
35 };
36
37 The object-level locking idiom doesn't cover the entire richness of a threading model. For example, the model above is quite deadlock-prone when you try to coordinate multi-object transactions. Nonetheless, object-level locking is useful in many cases, and in combination with other mechanisms can provide a satisfactory solution to many threaded access problems in object-oriented programs.
38
39 [endsect]
40
41 [section Internal and external locking]
42
43 The BankAccount class above uses internal locking. Basically, a class that uses internal locking guarantees that any concurrent calls to its public member functions don't corrupt an instance of that class. This is typically ensured by having each public member function acquire a lock on the object upon entry. This way, for any given object of that class, there can be only one member function call active at any moment, so the operations are nicely serialized.
44
45 This approach is reasonably easy to implement and has an attractive simplicity. Unfortunately, "simple" might sometimes morph into "simplistic."
46
47 Internal locking is insufficient for many real-world synchronization tasks. Imagine that you want to implement an ATM withdrawal transaction with the BankAccount class. The requirements are simple. The ATM transaction consists of two withdrawals-one for the actual money and one for the $2 commission. The two withdrawals must appear in strict sequence; that is, no other transaction can exist between them.
48
49 The obvious implementation is erratic:
50
51 void ATMWithdrawal(BankAccount& acct, int sum) {
52 acct.Withdraw(sum);
53 // preemption possible
54 acct.Withdraw(2);
55 }
56
57 The problem is that between the two calls above, another thread can perform another operation on the account, thus breaking the second design requirement.
58
59 In an attempt to solve this problem, let's lock the account from the outside during the two operations:
60
61 void ATMWithdrawal(BankAccount& acct, int sum) {
62 boost::lock_guard<boost::mutex> guard(acct.mtx_); // mtx_ field is private
63 acct.Withdraw(sum);
64 acct.Withdraw(2);
65 }
66
67
68 Notice that the code above doesn't compiles, the `mtx_` field is private.
69 We have two possibilities:
70
71 * make `mtx_` public which seams odd
72 * make the `BankAccount` lockable by adding the lock/unlock functions
73
74 We can add these functions explicitly
75
76 class BankAccount {
77 boost::mutex mtx_;
78 int balance_;
79 public:
80 void Deposit(int amount) {
81 boost::lock_guard<boost::mutex> guard(mtx_);
82 balance_ += amount;
83 }
84 void Withdraw(int amount) {
85 boost::lock_guard<boost::mutex> guard(mtx_);
86 balance_ -= amount;
87 }
88 void lock() {
89 mtx_.lock();
90 }
91 void unlock() {
92 mtx_.unlock();
93 }
94 };
95
96 or inheriting from a class which add these lockable functions.
97
98 The `basic_lockable_adapter` class helps to define the `BankAccount` class as
99
100 class BankAccount
101 : public basic_lockable_adapter<boost::mutex>
102 {
103 int balance_;
104 public:
105 void Deposit(int amount) {
106 boost::lock_guard<BankAccount> guard(*this);
107 // boost::lock_guard<boost::mutex> guard(*this->mutex());
108 balance_ += amount;
109 }
110 void Withdraw(int amount) {
111 boost::lock_guard<BankAccount> guard(*this);
112 // boost::lock_guard<boost::mutex> guard(*this->mutex());
113 balance_ -= amount;
114 }
115 int GetBalance() {
116 boost::lock_guard<BankAccount> guard(*this);
117 // boost::lock_guard<boost::mutex> guard(*this->mutex());
118 return balance_;
119 }
120 };
121
122
123
124 and the code that does not compiles becomes
125
126 void ATMWithdrawal(BankAccount& acct, int sum) {
127 // boost::lock_guard<boost::mutex> guard(*acct.mutex());
128 boost::lock_guard<BankAccount> guard(acct);
129 acct.Withdraw(sum);
130 acct.Withdraw(2);
131 }
132
133 Notice that now acct is being locked by Withdraw after it has already been locked by guard. When running such code, one of two things happens.
134
135 * Your mutex implementation might support the so-called recursive mutex semantics. This means that the same thread can lock the same mutex several times successfully. In this case, the implementation works but has a performance overhead due to unnecessary locking. (The locking/unlocking sequence in the two Withdraw calls is not needed but performed anyway-and that costs time.)
136 * Your mutex implementation might not support recursive locking, which means that as soon as you try to acquire it the second time, it blocks-so the ATMWithdrawal function enters the dreaded deadlock.
137
138 As `boost::mutex` is not recursive, we need to use its recursive version `boost::recursive_mutex`.
139
140 class BankAccount
141 : public basic_lockable_adapter<boost::recursive_mutex>
142 {
143
144 // ...
145 };
146
147 The caller-ensured locking approach is more flexible and the most efficient, but very dangerous. In an implementation using caller-ensured locking, BankAccount still holds a mutex, but its member functions don't manipulate it at all. Deposit and Withdraw are not thread-safe anymore. Instead, the client code is responsible for locking BankAccount properly.
148
149 class BankAccount
150 : public basic_lockable_adapter<boost::mutex> {
151 int balance_;
152 public:
153 void Deposit(int amount) {
154 balance_ += amount;
155 }
156 void Withdraw(int amount) {
157 balance_ -= amount;
158 }
159 };
160
161 Obviously, the caller-ensured locking approach has a safety problem. BankAccount's implementation code is finite, and easy to reach and maintain, but there's an unbounded amount of client code that manipulates BankAccount objects. In designing applications, it's important to differentiate between requirements imposed on bounded code and unbounded code. If your class makes undue requirements on unbounded code, that's usually a sign that encapsulation is out the window.
162
163 To conclude, if in designing a multi-threaded class you settle on internal locking, you expose yourself to inefficiency or deadlocks. On the other hand, if you rely on caller-provided locking, you make your class error-prone and difficult to use. Finally, external locking completely avoids the issue by leaving it all to the client code.
164 [endsect]
165 ]
166 [section Locks as permits]
167
168 So what to do? Ideally, the BankAccount class should do the following:
169
170 * Support both locking models (internal and external).
171 * Be efficient; that is, use no unnecessary locking.
172 * Be safe; that is, BankAccount objects cannot be manipulated without appropriate locking.
173
174 Let's make a worthwhile observation: Whenever you lock a BankAccount, you do so by using a `lock_guard<BankAccount>` object. Turning this statement around, wherever there's a `lock_guard<BankAccount>`, there's also a locked `BankAccount` somewhere. Thus, you can think of-and use-a `lock_guard<BankAccount>` object as a permit. Owning a `lock_guard<BankAccount>` gives you rights to do certain things. The `lock_guard<BankAccount>` object should not be copied or aliased (it's not a transmissible permit).
175
176 # As long as a permit is still alive, the `BankAccount` object stays locked.
177 # When the `lock_guard<BankAccount>` is destroyed, the `BankAccount`'s mutex is released.
178
179 The net effect is that at any point in your code, having access to a `lock_guard<BankAccount>` object guarantees that a `BankAccount` is locked. (You don't know exactly which `BankAccount` is locked, however-an issue that we'll address soon.)
180
181 For now, let's make a couple of enhancements to the `lock_guard` class template defined in Boost.Thread.
182 We'll call the enhanced version `strict_lock`. Essentially, a `strict_lock`'s role is only to live on the stack as an automatic variable.
183 `strict_lock` must adhere to a non-copy and non-alias policy.
184 `strict_lock` disables copying by making the copy constructor and the assignment operator private.
185 [/
186 While we're at it, let's disable operator new and operator delete.
187
188 `strict_lock` are not intended to be allocated on the heap.
189 `strict_lock` avoids aliasing by using a slightly less orthodox and less well-known technique: disable address taking.
190 ]
191
192 template <typename Lockable>
193 class strict_lock {
194 public:
195 typedef Lockable lockable_type;
196
197
198 explicit strict_lock(lockable_type& obj) : obj_(obj) {
199 obj.lock(); // locks on construction
200 }
201 strict_lock() = delete;
202 strict_lock(strict_lock const&) = delete;
203 strict_lock& operator=(strict_lock const&) = delete;
204
205 ~strict_lock() { obj_.unlock(); } // unlocks on destruction
206
207 bool owns_lock(mutex_type const* l) const noexcept // strict lockers specific function
208 {
209 return l == &obj_;
210 }
211 private:
212 lockable_type& obj_;
213 };
214
215 Silence can be sometimes louder than words-what's forbidden to do with a `strict_lock` is as important as what you can do. Let's see what you can and what you cannot do with a `strict_lock` instantiation:
216
217 * You can create a `strict_lock<T>` only starting from a valid T object. Notice that there is no other way you can create a `strict_lock<T>`.
218
219 BankAccount myAccount("John Doe", "123-45-6789");
220 strict_lock<BankAccount> myLock(myAccount); // ok
221
222 * You cannot copy `strict_lock`s to one another. In particular, you cannot pass `strict_lock`s by value to functions or have them returned by functions:
223
224 extern strict_lock<BankAccount> Foo(); // compile-time error
225 extern void Bar(strict_lock<BankAccount>); // compile-time error
226
227 * However, you still can pass `strict_lock`s by reference to and from functions:
228
229 // ok, Foo returns a reference to strict_lock<BankAccount>
230 extern strict_lock<BankAccount>& Foo();
231 // ok, Bar takes a reference to strict_lock<BankAccount>
232 extern void Bar(strict_lock<BankAccount>&);
233
234 [/
235 * You cannot allocate a `strict_lock` on the heap. However, you still can put `strict_lock`s on the heap if they're members of a class.
236
237 strict_lock<BankAccount>* pL =
238 new strict_lock<BankAccount>(myAcount); //error!
239 // operator new is not accessible
240 class Wrapper {
241 strict_lock memberLock_;
242 ...
243 };
244 Wrapper* pW = new Wrapper; // ok
245
246 (Making `strict_lock` a member variable of a class is not recommended. Fortunately, disabling copying and default construction makes `strict_lock` quite an unfriendly member variable.)
247
248 * You cannot take the address of a `strict_lock` object. This interesting feature, implemented by disabling unary operator&, makes it very unlikely to alias a `strict_lock` object. Aliasing is still possible by taking references to a `strict_lock`:
249
250 strict_lock<BankAccount> myLock(myAccount); // ok
251 strict_lock<BankAccount>* pAlias = &myLock; // error!
252 // strict_lock<BankAccount>::operator& is not accessible
253 strict_lock<BankAccount>& rAlias = myLock; // ok
254
255 Fortunately, references don't engender as bad aliasing as pointers because they're much less versatile (references cannot be copied or reseated).
256 ]
257 [/* You can even make `strict_lock` final; that is, impossible to derive from. This task is left in the form of an exercise to the reader.
258 ]
259
260 All these rules were put in place with one purpose-enforcing that owning a `strict_lock<T>` is a reasonably strong guarantee that
261
262 # you locked a T object, and
263 # that object will be unlocked at a later point.
264
265 Now that we have such a strict `strict_lock`, how do we harness its power in defining a safe, flexible interface for BankAccount? The idea is as follows:
266
267 * Each of BankAccount's interface functions (in our case, Deposit and Withdraw) comes in two overloaded variants.
268 * One version keeps the same signature as before, and the other takes an additional argument of type `strict_lock<BankAccount>`. The first version is internally locked; the second one requires external locking. External locking is enforced at compile time by requiring client code to create a `strict_lock<BankAccount>` object.
269 * BankAccount avoids code bloating by having the internal locked functions forward to the external locked functions, which do the actual job.
270
271 A little code is worth 1,000 words, a (hacked into) saying goes, so here's the new BankAccount class:
272
273 class BankAccount
274 : public basic_lockable_adapter<boost::mutex>
275 {
276 int balance_;
277 public:
278 void Deposit(int amount, strict_lock<BankAccount>&) {
279 // Externally locked
280 balance_ += amount;
281 }
282 void Deposit(int amount) {
283 strict_lock<BankAccount> guard(*this); // Internally locked
284 Deposit(amount, guard);
285 }
286 void Withdraw(int amount, strict_lock<BankAccount>&) {
287 // Externally locked
288 balance_ -= amount;
289 }
290 void Withdraw(int amount) {
291 strict_lock<BankAccount> guard(*this); // Internally locked
292 Withdraw(amount, guard);
293 }
294 };
295
296 Now, if you want the benefit of internal locking, you simply call `Deposit(int)` and `Withdraw(int)`. If you want to use external locking, you lock the object by constructing a `strict_lock<BankAccount>` and then you call `Deposit(int, strict_lock<BankAccount>&)` and `Withdraw(int, strict_lock<BankAccount>&)`. For example, here's the `ATMWithdrawal` function implemented correctly:
297
298 void ATMWithdrawal(BankAccount& acct, int sum) {
299 strict_lock<BankAccount> guard(acct);
300 acct.Withdraw(sum, guard);
301 acct.Withdraw(2, guard);
302 }
303
304 This function has the best of both worlds-it's reasonably safe and efficient at the same time.
305
306 It's worth noting that `strict_lock` being a template gives extra safety compared to a straight polymorphic approach. In such a design, BankAccount would derive from a Lockable interface. `strict_lock` would manipulate Lockable references so there's no need for templates. This approach is sound; however, it provides fewer compile-time guarantees. Having a `strict_lock` object would only tell that some object derived from Lockable is currently locked. In the templated approach, having a `strict_lock<BankAccount>` gives a stronger guarantee-it's a `BankAccount` that stays locked.
307
308 There's a weasel word in there-I mentioned that ATMWithdrawal is reasonably safe. It's not really safe because there's no enforcement that the `strict_lock<BankAccount>` object locks the appropriate BankAccount object. The type system only ensures that some BankAccount object is locked. For example, consider the following phony implementation of ATMWithdrawal:
309
310 void ATMWithdrawal(BankAccount& acct, int sum) {
311 BankAccount fakeAcct("John Doe", "123-45-6789");
312 strict_lock<BankAccount> guard(fakeAcct);
313 acct.Withdraw(sum, guard);
314 acct.Withdraw(2, guard);
315 }
316
317 This code compiles warning-free but obviously doesn't do the right thing-it locks one account and uses another.
318
319 It's important to understand what can be enforced within the realm of the C++ type system and what needs to be enforced at runtime. The mechanism we've put in place so far ensures that some BankAccount object is locked during the call to `BankAccount::Withdraw(int, strict_lock<BankAccount>&)`. We must enforce at runtime exactly what object is locked.
320
321 If our scheme still needs runtime checks, how is it useful? An unwary or malicious programmer can easily lock the wrong object and manipulate any BankAccount without actually locking it.
322
323 First, let's get the malice issue out of the way. C is a language that requires a lot of attention and discipline from the programmer. C++ made some progress by asking a little less of those, while still fundamentally trusting the programmer. These languages are not concerned with malice (as Java is, for example). After all, you can break any C/C++ design simply by using casts "appropriately" (if appropriately is an, er, appropriate word in this context).
324
325 The scheme is useful because the likelihood of a programmer forgetting about any locking whatsoever is much greater than the likelihood of a programmer who does remember about locking, but locks the wrong object.
326
327 Using `strict_lock` permits compile-time checking of the most common source of errors, and runtime checking of the less frequent problem.
328
329 Let's see how to enforce that the appropriate BankAccount object is locked. First, we need to add a member function to the `strict_lock` class template.
330 The `bool strict_lock<T>::owns_lock(Lockable*)` function returns a reference to the locked object.
331
332 template <class Lockable> class strict_lock {
333 ... as before ...
334 public:
335 bool owns_lock(Lockable* mtx) const { return mtx==&obj_; }
336 };
337
338 Second, BankAccount needs to use this function compare the locked object against this:
339
340 class BankAccount {
341 : public basic_lockable_adapter<boost::mutex>
342 int balance_;
343 public:
344 void Deposit(int amount, strict_lock<BankAccount>& guard) {
345 // Externally locked
346 if (!guard.owns_lock(*this))
347 throw "Locking Error: Wrong Object Locked";
348 balance_ += amount;
349 }
350 // ...
351 };
352
353 The overhead incurred by the test above is much lower than locking a recursive mutex for the second time.
354
355 [endsect]
356
357 [section Improving External Locking]
358
359 Now let's assume that BankAccount doesn't use its own locking at all, and has only a thread-neutral implementation:
360
361 class BankAccount {
362 int balance_;
363 public:
364 void Deposit(int amount) {
365 balance_ += amount;
366 }
367 void Withdraw(int amount) {
368 balance_ -= amount;
369 }
370 };
371
372 Now you can use BankAccount in single-threaded and multi-threaded applications alike, but you need to provide your own synchronization in the latter case.
373
374 Say we have an AccountManager class that holds and manipulates a BankAccount object:
375
376 class AccountManager
377 : public basic_lockable_adapter<boost::mutex>
378 {
379 BankAccount checkingAcct_;
380 BankAccount savingsAcct_;
381 ...
382 };
383
384 Let's also assume that, by design, AccountManager must stay locked while accessing its BankAccount members. The question is, how can we express this design constraint using the C++ type system? How can we state "You have access to this BankAccount object only after locking its parent AccountManager object"?
385
386 The solution is to use a little bridge template `externally_locked` that controls access to a BankAccount.
387
388 template <typename T, typename Lockable>
389 class externally_locked {
390 BOOST_CONCEPT_ASSERT((LockableConcept<Lockable>));
391
392 public:
393 externally_locked(T& obj, Lockable& lockable)
394 : obj_(obj)
395 , lockable_(lockable)
396 {}
397
398 externally_locked(Lockable& lockable)
399 : obj_()
400 , lockable_(lockable)
401 {}
402
403 T& get(strict_lock<Lockable>& lock) {
404
405 #ifdef BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED
406 if (!lock.owns_lock(&lockable_)) throw lock_error(); //run time check throw if not locks the same
407 #endif
408 return obj_;
409 }
410 void set(const T& obj, Lockable& lockable) {
411 obj_ = obj;
412 lockable_=lockable;
413 }
414 private:
415 T obj_;
416 Lockable& lockable_;
417 };
418
419 `externally_locked` cloaks an object of type T, and actually provides full access to that object through the get and set member functions, provided you pass a reference to a `strict_lock<Owner>` object.
420
421 Instead of making `checkingAcct_` and `savingsAcct_` of type `BankAccount`, `AccountManager` holds objects of type `externally_locked<BankAccount, AccountManager>`:
422
423 class AccountManager
424 : public basic_lockable_adapter<boost::mutex>
425 {
426 public:
427 typedef basic_lockable_adapter<boost::mutex> lockable_base_type;
428 AccountManager()
429 : checkingAcct_(*this)
430 , savingsAcct_(*this)
431 {}
432 inline void Checking2Savings(int amount);
433 inline void AMoreComplicatedChecking2Savings(int amount);
434 private:
435
436 externally_locked<BankAccount, AccountManager> checkingAcct_;
437 externally_locked<BankAccount, AccountManager> savingsAcct_;
438 };
439
440 The pattern is the same as before - to access the BankAccount object cloaked by `checkingAcct_`, you need to call `get`. To call `get`, you need to pass it a `strict_lock<AccountManager>`. The one thing you have to take care of is to not hold pointers or references you obtained by calling `get`. If you do that, make sure that you don't use them after the strict_lock has been destroyed. That is, if you alias the cloaked objects, you're back from "the compiler takes care of that" mode to "you must pay attention" mode.
441
442 Typically, you use `externally_locked` as shown below. Suppose you want to execute an atomic transfer from your checking account to your savings account:
443
444 void AccountManager::Checking2Savings(int amount) {
445 strict_lock<AccountManager> guard(*this);
446 checkingAcct_.get(guard).Withdraw(amount);
447 savingsAcct_.get(guard).Deposit(amount);
448 }
449
450 We achieved two important goals. First, the declaration of `checkingAcct_` and `savingsAcct_` makes it clear to the code reader that that variable is protected by a lock on an AccountManager. Second, the design makes it impossible to manipulate the two accounts without actually locking a BankAccount. `externally_locked` is what could be called active documentation.
451
452 [endsect]
453
454 [section Allowing other strict locks]
455
456 Now imagine that the AccountManager function needs to take a `unique_lock` in order to reduce the critical regions. And at some time it needs to access to the `checkingAcct_`. As `unique_lock` is not a strict lock the following code doesn't compile:
457
458 void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
459 unique_lock<AccountManager> guard(*this, defer_lock);
460 if (some_condition()) {
461 guard.lock();
462 }
463 checkingAcct_.get(guard).Withdraw(amount); // COMPILE ERROR
464 savingsAcct_.get(guard).Deposit(amount); // COMPILE ERROR
465 do_something_else();
466 }
467
468 We need a way to transfer the ownership from the `unique_lock` to a `strict_lock` during the time we are working with `savingsAcct_` and then restore the ownership on `unique_lock`.
469
470 void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
471 unique_lock<AccountManager> guard1(*this, defer_lock);
472 if (some_condition()) {
473 guard1.lock();
474 }
475 {
476 strict_lock<AccountManager> guard(guard1);
477 checkingAcct_.get(guard).Withdraw(amount);
478 savingsAcct_.get(guard).Deposit(amount);
479 }
480 guard1.unlock();
481 }
482
483 In order to make this code compilable we need to store either a Lockable or a `unique_lock<Lockable>` reference depending on the constructor. We also need to store which kind of reference we have stored, and in the destructor call either to the Lockable `unlock` or restore the ownership.
484
485 This seems too complicated to me. Another possibility is to define a nested strict lock class. The drawback is that instead of having only one strict lock we have two and we need either to duplicate every function taking a `strict_lock` or make these function templates. The problem with template functions is that we don't profit anymore of the C++ type system. We must add some static metafunction that checks that the Locker parameter is a strict lock. The problem is that we can not really check this or can we?. The `is_strict_lock` metafunction must be specialized by the strict lock developer. We need to believe it "sur parole". The advantage is that now we can manage with more than two strict locks without changing our code. This is really nice.
486
487 Now we need to state that both classes are `strict_lock`s.
488
489 template <typename Locker>
490 struct is_strict_lock : mpl::false_ {};
491
492 template <typename Lockable>
493 struct is_strict_lock<strict_lock<Lockable> > : mpl::true_ {}
494
495 template <typename Locker>
496 struct is_strict_lock<nested_strict_lock<Locker> > : mpl::true_ {}
497
498
499 Well let me show what this `nested_strict_lock` class looks like and the impacts on the `externally_locked` class and the `AccountManager::AMoreComplicatedFunction` function.
500
501 First `nested_strict_lock` class will store on a temporary lock the `Locker`, and transfer the lock ownership on the constructor. On destruction it will restore the ownership. Note the use of `lock_traits` and that the `Locker` needs to have a reference to the mutex otherwise an exception is thrown.
502
503 template <typename Locker >
504 class nested_strict_lock
505 {
506 BOOST_CONCEPT_ASSERT((MovableLockerConcept<Locker>));
507 public:
508 typedef typename lockable_type<Locker>::type lockable_type;
509 typedef typename syntactic_lock_traits<lockable_type>::lock_error lock_error;
510
511 nested_strict_lock(Locker& lock)
512 : lock_(lock) // Store reference to locker
513 , tmp_lock_(lock.move()) // Move ownership to temporary locker
514 {
515 #ifdef BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED
516 if (tmp_lock_.mutex()==0) {
517 lock_=tmp_lock_.move(); // Rollback for coherency purposes
518 throw lock_error();
519 }
520 #endif
521 if (!tmp_lock_) tmp_lock_.lock(); // ensures it is locked
522 }
523 ~nested_strict_lock() {
524 lock_=tmp_lock_.move(); // Move ownership to nesting locker
525 }
526 bool owns_lock() const { return true; }
527 lockable_type* mutex() const { return tmp_lock_.mutex(); }
528 bool owns_lock(lockable_type* l) const { return l==mutex(); }
529
530
531 private:
532 Locker& lock_;
533 Locker tmp_lock_;
534 };
535
536 [/
537 typedef bool (nested_strict_lock::*bool_type)() const;
538 operator bool_type() const { return &nested_strict_lock::owns_lock; }
539 bool operator!() const { return false; }
540
541 BOOST_ADRESS_OF_DELETE(nested_strict_lock)
542 BOOST_HEAP_ALLOCATEION_DELETE(nested_strict_lock)
543 BOOST_DEFAULT_CONSTRUCTOR_DELETE(nested_strict_lock)
544 BOOST_COPY_CONSTRUCTOR_DELETE(nested_strict_lock)
545 BOOST_COPY_ASSIGNEMENT_DELETE(nested_strict_lock)
546
547 ]
548
549 The `externally_locked` get function is now a template function taking a Locker as parameters instead of a `strict_lock`.
550 We can add test in debug mode that ensure that the Lockable object is locked.
551
552 template <typename T, typename Lockable>
553 class externally_locked {
554 public:
555 // ...
556 template <class Locker>
557 T& get(Locker& lock) {
558 BOOST_CONCEPT_ASSERT((StrictLockerConcept<Locker>));
559
560 BOOST_STATIC_ASSERT((is_strict_lock<Locker>::value)); // locker is a strict locker "sur parole"
561 BOOST_STATIC_ASSERT((is_same<Lockable,
562 typename lockable_type<Locker>::type>::value)); // that locks the same type
563 #ifndef BOOST_THREAD_EXTERNALLY_LOCKED_DONT_CHECK_OWNERSHIP // define BOOST_THREAD_EXTERNALLY_LOCKED_NO_CHECK_OWNERSHIP if you don't want to check locker ownership
564 if (! lock ) throw lock_error(); // run time check throw if no locked
565 #endif
566 #ifdef BOOST_THREAD_THROW_IF_PRECONDITION_NOT_SATISFIED
567 if (!lock.owns_lock(&lockable_)) throw lock_error();
568 #endif
569 return obj_;
570 }
571 };
572
573 The `AccountManager::AMoreComplicatedFunction` function needs only to replace the `strict_lock` by a `nested_strict_lock`.
574
575 void AccountManager::AMoreComplicatedChecking2Savings(int amount) {
576 unique_lock<AccountManager> guard1(*this);
577 if (some_condition()) {
578 guard1.lock();
579 }
580 {
581 nested_strict_lock<unique_lock<AccountManager> > guard(guard1);
582 checkingAcct_.get(guard).Withdraw(amount);
583 savingsAcct_.get(guard).Deposit(amount);
584 }
585 guard1.unlock();
586 }
587
588 [endsect]
589
590 [endsect]
591
592