标签:des style blog class c code
Launching threads
A new thread is launched by passing an object of a callable type that can be invoked with no parameter to the constructor. The object is then copied into internal storage, and invoked on the newly-created thread of execution. If the object must not be copied, then boost::ref can be used to pass in a reference to the function object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
struct
callable { void
operator()(); }; boost:: thread
copies_are_safe() { callable x; return
boost:: thread (x); } // x is destroyed, but the newly-created thread has a copy, so this is OK boost:: thread
oops() { callable x; return
boost:: thread (boost::ref(x)); } // x is destroyed, but the newly-created thread still has a reference // this leads to undefined behavior |
If you want to construct an instance of boost::thread with a function or callable object that requires arguments to be suppied, this can be done by passing additional arguments to the boost::thread constructor.
1
2
3 |
void
find_the_question( int
the_answer); boost:: thread
deep_thought_2(find_the_question, 42); |
The arguments are copied into the internal thread structure. If a reference is required, use boost::ref.
There is an unspecified limit on the number of additional arguments that can be passed.
Our thread has launched another thread. There are a couple of things that we can do with it now: wait for its termination or let it go.
We can wait newly-created thread for some times using timed_join().
Or we can wait indefinitely for it, by calling join().
A third alternative is detaching the thread from the boost::thread object. In this way, we are saying that we don‘t have anything to do with the newly-created thread any more. It would do its job till the end, and we are not concerned with its life and death.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 |
#include <boost/thread.hpp> #include <iostream> class
Callable { private : int
value_; public : Callable( int
value) : value_(value) { } void
operator()() { std::cout << "cout down " ; while (value_ > 0) { std::cout << value_ << " " ; boost::this_thread::sleep(boost::posix_time::seconds(1)); --value_; } std::cout << "done"
<< std::endl; } }; int
main() { std::cout << "Launching a thread"
<< std::endl; boost:: thread
t1(Callable(6)); t1.timed_join(boost::posix_time::seconds(2)); std::cout << std::endl << "Expired waiting for timed_join()"
<< std::endl; t1.join(); std::cout << "Secondary thread joined"
<< std::endl; Callable c2(3); boost:: thread
t2(c2); t2.detach(); std::cout << "Secondary thread detached"
<< std::endl; boost::this_thread::sleep(boost::posix_time::seconds(5)); std::cout << "Done thread testing"
<< std::endl; } |
This chapter gonna be interesting.
Consider designing a BankAccount class which have two member functions Deposit(int), Withdraw(int) and one member variable balance_.
With RAII idiom we can write code like this: (for boost::lock_guard, refer to http://stackoverflow.com/questions/2276805/boostlock-guard-vs-boostmutexscoped-lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
class
BankAccount { private : boost::mutex mtx_; int
balance_; public : void
Deposit( int
amount) { boost::lock_guard<boost::mutex> guard(mtx_); balance_ += amount; } void
Withdraw( int
amount) { boost::lock_guard<boost::mutex> guard(mtx_); balance_ -= amount; } int
GetBalance() { boost::lock_guard<boost::mutex> guard(mtx_); return
balance_; } }; |
The object-level locking idiom doesn‘t cover the entire richness of a threading model.
The BankAccount class above uses internal locking. Basiclly, 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 tyically ensured by having each public member function acquire a lock on the object upon entry. This way, for any object of that class, there can be only one member function all active at any moment, so the operation are nicely serialized.
Unfortunately, "simple" might sometimes morph into "simplistic".
Internal locking is insufficient for many real-world synchronization tasks. Imagine that you want to implement an ATM transaction with BankAccount class. The requirements are simple. The ATM transaction consists of two withdrawals- one for the actural money and one for $2 commission. The two withdrawls must appear in strict sequence; that is, atomic.
The obvious implementation is erratic:
1
2
3
4 |
void
ATMWithdrawal(BandAccount& acct, int
sum) { acct.Withdraw(sum); acct.Withdraw(2); } |
The problem is that between the two calls above, another thread can perform another operation on the account, thus breaking atomic.
In an attempt to solve this problem, let‘s lock the account from outside during the two operations:
1
2
3
4
5 |
void
ATMWithdrawal(BandAccount& acct, int
sum) { boost::lock_guard<boost::mutex> guard(acc.mtx_); acct.Withdraw(sum); acct.Withdraw(2); } |
Notice that the code above doesn‘t compile, the mtx_ field is private. We have two possibilities:
1) make mtx_ public
2) make the BankAccount lockable by adding the lock/unlock functions
We can add functions explicitly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
class
BankAccout { private : boost::mutex mtx_; int
balance_; public : void
Deposit() { // same as before } void
Withdraw() { // same as before } void
lock() { mtx_.lock(); } void
unlock() { mtx_.unlock(); } }; |
Or inherit from a class which add these lockable functions.
The basic_lockable_adaptor class helps to define the BankAccount class as
1
2
3
4
5
6
7
8
9
10 |
class
BankAccount : public
basic_lockable_adaptor<mutex> { private : void
Deposit( int
amount) { //same as before } void
Withdraw( int
amount) { // same as before } int
GetBalance( int
amount) { // same as before } }; |
and the code that doesn‘t compile becomes
1
2
3
4
5 |
void
ATMWithdrawal(BandAccount& acct, int
sum) { boost::lock_guard<BandAccount> guard(acct); acct.Withdraw(sum); acc.Withdraw(2); } |
// Notice that lock_guard need a member which have lock() and unlock() function
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.
1) 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 the unnecessary locking.
2) 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 ATMWithdraw function enters the deaded deadlock.
As boost::mutex is not recursive, we need to use recursive version boost::recursive_mutex.
1
2
3
4 |
class
BankAccout : public
basic_lockable_adaptor<recursive_mutex> { //... }; |
The caller-ensured locking approach is more flexible and the most efficient, but very dangerous. In an implementation using caller-ensured locking, BandAccount still hold mutex(class hold mutex or make mutex globally visiable), 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 BandAccount properly.
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 issues by leaving it to the client code.
This tutorial is an adaptation of the paper of Andrei Alexandrescu "Multithreading and the C++ Type System" to the Boost library.
Ideally, the BankAccount class should do the following:
1) Support both locking models(internal and external)
2) Be efficient
3) Be safe
For achieving that we need one more class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
template
< typename
Lockable> class
strict_lock { public : typedef
Lockable lockable_type; explicit
strict_lock(lockable_type& obj) : obj_(obj) { obj.lock(); } ~strict_lock() { obj_.unlock(); } bool
owns_lock(mutex_type const
*l) const
noexcept { return
l == &obj_; } private : strict_lock(); strict_lock(strict_lock const &); strict_lock& operator=(strict_lock const &); private : lockable_type &obj_; }; |
strict_lock must adhere to a non-copy and non-alias policy. strict_lock disables copying by making the copy constructor and the assignment operator private.
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>
1
2
3
4
5
6
7
8 |
BandAccount myAccount( "John snow" , "123-33" ); strict_lock<BandAccount> myLock(myAccount); strict_lock<BandAccount> Foo(); // compile error void
Bar(strict_lock<BandAccount>); // compile error strict_lock<BandAccount>& Foo(); // OK void
Bar(strict_lock<BandAccount>&); //OK |
Here is the new BankAccount class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
class
BankAccount : public
basic_lockable_adaptor<boost::recursive_mutex> { public : void
Deposit( int
amount, strict_lock<BandAccount>&) { balance_ += amount; } void
Withdraw( int
amount, strict_lock<BandAccount>&) { balance_ -= amount; } void
Deposit( int
amount) { strict_lock<boost::mutex> guard(* this ); Deposit(amount, guard); } void
Withdraw( int
amount) { strict_lock<boost::mutex> guard(* this ); Withdraw(amount, guard); } }; |
Now, if you want the benefit of internal locking, you simply call Desposit(int) or Withdraw(int). If you want to use external locking:
1
2
3
4
5 |
void
ATMWithdrawal(BandAccount& acct, int
amount) { strict_lock<BandAccount> guard(acct); acct.Withdraw(sum, guard); acct.Withdraw(2, guard); } |
There is only one problem left, that is:
1
2
3
4
5
6 |
void
ATMWithdrawal(BandAccount& acct, int
amount) { BandAccount fakeAcct( "John snow" , "122" ); strict_lock<BandAccount> guard(fakeAcct); acct.Withdraw(sum, guard); acct.Withdraw(2, guard); } |
And that‘s why we have member function owns_lock() in the first place:
BankAccount needs to use this function compare the locked object against this:
1
2
3
4
5
6
7
8
9
10 |
class
BankAccount : public
basic_lockable_adaptor<boost::recursive_mutex> { public : void
Deposit( int
amount, strict_lock<BandAccount>& guard) { if (!guard.owns_lock(* this )) throw
"Locking error\n" ; balance_ += amount; } //... }; |
The overhead incurred by the test above is much lower than locking a recursive mutex for the second time.
Improving External Locking // http://www.boost.org/doc/libs/1_55_0/doc/html/thread/synchronization.html#thread.synchronization.lock_concepts
The general usage pattern is that one thread locks a mutex and then
calls wait
on an instance
of condition_variable
or condition_variable_any
. When the thread
is woken from the wait, then it checks to see if the appropriate condition is
now true, and continues if so. If the condition is not true, then the thread
then calls wait
again to resume waiting. In the
simplest case, this condition is just a boolean variable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
boost::condition_variable cond; boost::mutex mut; bool
data_ready; void
process_data(); void
wait_for_data_to_process() { boost::unique_lock<boost::mutex> lock(mut); while (!data_ready) { cond.wait(lock); } process_data(); } void
prepare_data_for_process() { boost::lock_guard<boost::mutex> lock(mut); data_ready = true ; cond.notify_one(); } |
Notice that the lock is passed to wait: wait atomically add the thread to the set of threads waiting on the condition variable, and unlock the mutex. When the thread is woken, then mutex will lock again before the call to wait returns. This allows other threads to acquire the mutex in order to update the shared data, and ensure that the data associated with the condition is correcyly synchronized.
I was learning the usage of boost thread when I found this tutorial. I found this tutorial interesting and might be useful and it cost me almost an entire day to understand, record it.
Boost Thread and Synchronization Tutorial,布布扣,bubuko.com
Boost Thread and Synchronization Tutorial
标签:des style blog class c code
原文地址:http://www.cnblogs.com/zhouzhuo/p/3734264.html