Boost Thread and Synchronization Tutorial

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.

Join and detach a thread


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

// source http://thisthread.blogspot.com/2010/09/joining-and-detaching-boostthread.html

#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;

}

Synchronization


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.

Internal and external locking

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.

External locking -- strict_lock and externally_locked classes

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

Condition Variabls


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.

时间: 2024-10-05 12:57:29

Boost Thread and Synchronization Tutorial的相关文章

boost::thread类

前言 标准C++线程即将到来.预言它将衍生自Boost线程库,现在让我们探索一下Boost线程库. 几年前,用多线程执行程序还是一件非比寻常的事.然而今天互联网应用服务程序普遍使用多线程来提高与多客户链接时的效率:为了达到最大的吞吐量,事务服务器在单独的线程上运行服务程序:GUI应用程序将那些费时,复杂的处理以线程的形式单独运行,以此来保证用户界面能够及时响应用户的操作.这样使用多线程的例子还有很多. 但是C++标准并没有涉及到多线程,这让程序员们开始怀疑是否可能写出多线程的C++程序.尽管不可

Boost::thread库的使用

阅读对象 本文假设读者有几下Skills [1]在C++中至少使用过一种多线程开发库,有Mutex和Lock的概念. [2]熟悉C++开发,在开发工具中,能够编译.设置boost::thread库. 环境 [1]Visual Studio 2005/2008 with SP1 [2]boost1.39/1.40 概要 通过实例介绍boost thread的使用方式,本文主要由线程启动.Interruption机制.线程同步.等待线程退出.Thread Group几个部份组成. 正文 线程启动 线

Boost thread 教程

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <boost/thread.hpp> #include <iostream> #include <stdio.h> class SpecificWork { private:     int p_; public:     SpecificWork(int value) : p_(value) { }     void opera

C++ 系列:Boost Thread 编程指南

转载自:http://www.cppblog.com/shaker/archive/2011/11/30/33583.html 作者: dozbC++ Boost Thread 编程指南0 前言1 创建线程2 互斥体3 条件变量4 线程局部存储5 仅运行一次的例程6 Boost线程库的未来7 参考资料:0 前言 标准C++线程即将到来.CUJ预言它将衍生自Boost线程库,现在就由Bill带领我们探索一下Boost线程库.就在几年前,用多线程执行程序还是一件非比寻常的事.然而今天互联网应用服务程

boost::thread编程-线程中断(转)

原文转自 http://blog.csdn.net/anda0109/article/details/41943691 thread的成员函数interrupt()允许正在执行的线程被中断,被中断的线程会抛出一个thread_interrupted异常,它是一个空类,不是std::exception或boost::exception的子类 #include "stdafx.h" #include <windows.h> #include <iostream> #

boost thread使用

boost库是个准C++标准库,thread是其中重要的组成部分.它封装了不同操作系统的多线程编程,使得它具备了跨平台的能力. 首先是boost安装,从www.boost.org网站下下载最新的库,解压到本地目录下,重命名为boost 这里给出了安装脚本,该脚本采用静态链接多线程编译. 新建一个build_boost.sh的文件,将下述代码拷贝如文件内 #!/bin/bash machine=`uname -m | grep '64'` if [ a"$machine" == &quo

boost::thread之while(true)型线程终结方法

我们的程序中经常会用到线程来执行某些异步操作,而有些时候我们的线程执行的函数是这个样子的: [cpp] view plaincopyprint? void ThreadBody() { while( true ) { std::cout << "Processing..." << std::endl; Sleep(1000); } } 那么,从理论上讲,这个线程将永远的执行下去,直到这个线程所属的进程运行完毕为止.注意,即使这个线程函数是某个类的成员函数,即使我

boost::thread boost库线程

一.boost::thread的创建 1.线程创建方法一: boost::shared_ptr<boost::thread> writeThread_; boost::function0<void> f = boost::bind(&DBTaskMgr::execute, this); writeThread_ = boost::shared_ptr<boost::thread>(new boost::thread(f)); 2.线程创建方法二: boost::

boost thread 传递参数

#include <boost/thread/thread.hpp> #include <boost/bind.hpp> #include <iostream> void threadFunc(const char* pszContext) { std::cout << pszContext << std::endl; } int main(int argc, char* argv[]) { char* pszContext = "[e