Boost多线程编程
喷泉的水堵不死,恋情的火扑不灭。 线程是在同一法度同一时候内容许履行不合函数的离散处理惩罚队列,这使得在一个长时候进行某种特别运算的函数在履行时不阻碍其他的函数时变得十分首要。线程实际上容许同时履行两种函数,而这两者不必彼此守候。
人生最可爱的,是为人竭诚。 一旦一个应用法度启动,它仅包含一个默认线程。此线程履行main()函数。在main()中被调用的函数则按这个线程的高低文次序地履行,如许的法度称为单线程法度。
反之,那些创建新的线程的法度就是多线程法度。他们不仅可以在同一时候履行多个函数,并且这在如今多核流行的时代显得尤为首要。既然多核容许同时履行多个函数,这就使得对开辟人员响应地应用这种处理惩罚才能提出了请求。然而线程一向被用来当并发地履行多个函数,开辟人员如今不得不细心地构建应用来支撑这种并发。多线程编程常识也是以在多核体系时代变得越来越首要。
本章介绍的是C++ Boost库Boost.Thread,它可以开辟自力于平台的多线程应用法度。
二、线程经管
这个库中最首要的一个类就是boost::thread,它在boost/thread.hpp里定义,用来创建一个新线程。下面的示例来申明如何应用它。
新建线程里履行的那个函数的名称被传递到 boost::thread 的机关函数。一旦上述示例中的变量t被创建,该thread函数就在其地点线程中被立即履行,同时在main()里也并发地履行该thread。
示例中,为了防止法度终止,就须要对新建线程调用join办法。join办法是一个梗阻调用:它可以暂伏贴火线程,直到调用join的线程运行停止。这就使得main函数一向会守候到thread运行停止。
正如上方例子中看到的,一个特定的线程可以经由过程诸如t的变量接见,经由过程这个变量守候着它的应用join办法终止。 然则,即使t越界或者析构了,该线程也将持续履行。一个线程老是在一开端就绑定到一个类型为 boost::thread 的变量,然则一旦创建,就不在取决于它。 甚至还存在着一个叫detach的办法,容许类型为 boost::thread 的变量从它对应的线程里分别。当然,像 join的办法之后也就不克不及被调用,因为这个变量不再是一个有效的线程。
任何一个函数内可以做的工作也可以在一个线程内完成。所以,一个线程只不过是一个函数,除了它是同时履行的。在上述例子中,应用一个轮回把5个数字写入标准输出流。为了减缓输出,每一个轮回中调用wait函数让履行延迟了一秒。wait可以调用一个名为sleep的函数,这个函数也来自于 Boost.Thread,位于 boost::this_thread 定名空间内。
sleep()可以在估计的一段时候或一个特定的时候点后才让线程持续履行。经由过程传递一个类型为 boost::posix_time::seconds 的对象,在这个例子里我们指定了一段时候。 boost::posix_time::seconds 来自于 Boost.DateTime 库,它被 Boost.Thread 用来经管和处理惩罚时候的数据。
固然前面的例子说了然如何守候一个不合的线程,但下面的例子演示了如何经由过程所谓的中断点让一个线程中断。
在一个线程对象上调用 interrupt() 会中断响应的线程。 在这方面,中断意味着一个类型为 boost::thread_interrupted 的异常,它会在这个线程中抛出。 然后这只有在线程达到中断点时才会产生。
若是给定的线程不包含任何中断点,简单调用interrupt就不会起感化。 每当一个线程中断点,它就会搜检interrupt是否被调用过。只有被调用过了, boost::thread_interrupted 异常才会响应地抛出。
Boost.Thread定义了一系列的中断点,例如sleep() 函数,因为sleep() 在这个例子里被调用了五次,该线程就搜检了五次它是否应当被中断。然而sleep()之间的调用,却不克不及使线程中断。
一旦该法度被履行,它只会打印三个数字到标准输出流。这是因为在main里3秒后调用 interrupt()办法。 是以,响应的线程被中断,并抛出一个 boost::thread_interrupted 异常。这个异常在线程内也被正确地捕获,catch 处理惩罚是空的。因为thread()函数在处理惩罚法度后返回,线程也被终止。这反过来也将终止全部法度,因为 main() 守候该线程应用join终止该线程。
Boost.Thread定义包含上述 sleep()函数等十个中断。 有了这些中断点,线程可以很轻易及时中断。然而,他们并不老是最佳的选择,因为中断点必须事前读入以搜检 boost::thread_interrupted 异常。
为了供给一个对 Boost.Thread 里供给的多种函数的整体概述,下面的例子将会再介绍两个。
应用 boost::this_thread定名空间,能供给自力的函数应用于当火线程,比如前面呈现的sleep() 。另一个是 get_id():它会返回一个当火线程的ID号。它也是由 boost::thread 供给的。
boost::thread 类供给了一个静态办法 hardware_concurrency() ,它可以或许返回基于CPU数量或者CPU内核数量标刻在同时在物理机械上运行的线程数。在常用的双核机械上调用这个办法,返回值为2。 如许的话就可以断定在一个多核法度可以同时运行的理论最大线程数。
三、同步
固然多线程的应用可以进步应用法度的机能,但也增长了错杂性。若是应用线程在同一时候履行几个函数,接见共享资料时必须响应地同步。一旦应用达到了必然范围,这涉及相当一些工作。本段介绍了Boost.Thread供给同步线程的类。
多线程法度应用所谓的互斥对象来同步。Boost.Thread供给多个的互斥类,boost::mutex是最简单的一个,它的应用就像linux下的二进制互斥量。互斥的基起原根蒂根基则是当一个特定的线程拥有资料的时辰防止其他线程争夺其所有权,一旦开释,其他的线程可以取得所有权。这将导致线程守候至另一个线程完成处理惩罚一些操纵,从而响应地开释互斥对象的所有权。
上方的示例应用一个类型为 boost::mutex 的mutex全局互斥对象。thread()函数获取此对象的所有权才在 for 轮回内应用 lock()办法写入到标准输出流的。一旦信息被写入,应用unlock()办法开释所有权。
main() 创建两个线程,同时履行thread ()函数。哄骗 for 轮回,每个线程数到5,用一个迭代器写一条消息到标准输出流。然而,标准输出流是一个全局性的被所有线程共享的对象,该标准不供给任何包管 std::cout 可以安然地从多个线程接见。 是以,接见标准输出流必须同步:在任何时辰,只有一个线程可以接见 std::cout。
因为两个线程试图在写入标准输出流前获得互斥体,实际上只能包管一次只有一个线程接见 std::cout。不管哪个线程成功调用 lock() 办法,其他所有线程必须守候,直到 unlock() 被调用。
获取和开释互斥体是一个典范的模式,是由Boost.Thread经由过程不合的数据类型支撑。 例如,不直接地调用 lock() 和 unlock(),应用 boost::lock_guard 类也是可以的。
boost::lock_guard 在其内部机关和析构函数分别主动调用lock() 和 unlock() 。 接见共享资料是须要同步的,因为它显示地被两个办法调用。 boost::lock_guard 类是另一个呈如今我之前第2个系列智能指针单位的RAII用语。
除了boost::mutex 和 boost::lock_guard 之外,Boost.Thread也供给其他的类支撑各类同步。此中一个首要的就是 boost::unique_lock ,相斗劲 boost::lock_guard 而言,它供给很多有效的办法。
上方的例子用不合的办法来演示 boost::unique_lock 的功能。 当然了,这些功能的用法对给定的情景不必然实用;boost::lock_guard 在上个例子的用法还是挺公道的。 这个例子就是为了演示 boost::unique_lock 供给的功能。
boost::unique_lock 经由过程多个机关函数来供给不合的体式格式获得互斥体。这个期望获得互斥体的函数简单地调用了lock()办法,一向比及获得这个互斥体。所以它的行动跟 boost::lock_guard 的那个是一样的。
若是第二个参数传入一个 boost::try_to_lock 类型的值,对应的机关函数就会调用 try_lock办法。这个办法返回 bool 型的值:若是可以或许获得互斥体则返回true,不然返回 false。比拟lock函数,try_lock会立即返回,并且在获得互斥体之前不会被梗阻。
上方的法度向boost::unique_lock 的机关函数的第二个参数传入boost::try_to_lock。然后经由过程 owns_lock() 可以搜检是否可获得互斥体。若是不克不及, owns_lock() 返回false。这也用到 boost::unique_lock 供给的别的一个函数: timed_lock() 守候必然的时候以获得互斥体。 给定的法度守候长达1秒,应较足够的时候来获取更多的互斥。
其实这个例子显示了三个办法获取一个互斥体:lock() 会一向守候,直到获得一个互斥体。try_lock()则不会守候,但若是它只会在互斥体可用的时辰才干获得,不然返回 false。最后,timed_lock()试图获得在必然的时候内获取互斥体。和try_lock()一样,返回bool 类型的值意味着成功是否。
背景
• 今天互联网应用服务程序普遍使用多线程来提高与多客户链接时的效率;为了达到最大的吞吐量,事务服务器在单独的线程上运行服务程序;
GUI应用程序将那些费时,复杂的处理以线程的形式单独运行,以此来保证用户界面能够及时响应用户的操作。这样使用多线程的例子还有很多。
• 跨平台
创建线程
• 头文件 <boost/thread/thread.hpp>
namespace boost {
class thread;
class thread_group;
}
• thread():构造一个表示当前执行线程的线程对象
• explicit thread(const boost::function0<void>& threadfunc)
注:boost::function0<void>可以简单看为:一个无返回(返回void),无参数的函数。这里的函数也可以是类重载operator()构成的函数。
第一种方式:最简单方法
• #include <boost/thread/thread.hpp>
• #include <iostream>
•
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
•
• int main(int argc, char* argv[])
• {
• boost::thread thrd(&hello);
• thrd.join();
• return 0;
• }
第二种方式:复杂类型对象作为参数来创建线程
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <iostream>
•
• boost::mutex io_mutex;
•
• struct count
• {
• count(int id) : id(id) { }
•
• void operator()()
• {
• for (int i = 0; i < 10; ++i)
• {
• boost::mutex::scoped_lock
• lock(io_mutex);
• std::cout << id << ": "
• << i << std::endl;
• }
• }
•
• int id;
• };
•
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(count(1));
• boost::thread thrd2(count(2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
第三种方式:在类内部创建线程
• (1)类内部静态方法启动线程
• #include <boost/thread/thread.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• static void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• static void start()
• {
•
• boost::thread thrd( hello );
• thrd.join();
• }
•
• };
• int main(int argc, char* argv[])
• {
• HelloWorld::start();
•
• return 0;
• }
• 在这里start()和hello()方法都必须是static方法。
• (2)如果要求start()和hello()方法不能是静态方法则采用下面的方法创建线程:
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• void start()
• {
• boost::function0< void> f = boost::bind(&HelloWorld::hello,this);
• boost::thread thrd( f );
• thrd.join();
• }
•
• };
• int main(int argc, char* argv[])
• {
• HelloWorld hello;
• hello.start();
• return 0;
• }
• (3)在Singleton模式内部创建线程:
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• static void start()
• {
• boost::thread thrd( boost::bind
• (&HelloWorld::hello,&HelloWorld::getInstance() ) ) ;
• thrd.join();
• }
• static HelloWorld& getInstance()
• {
• if ( !instance )
• instance = new HelloWorld;
• return *instance;
• }
• private:
• HelloWorld(){}
• static HelloWorld* instance;
•
• };
• HelloWorld* HelloWorld::instance = 0;
• int main(int argc, char* argv[])
• {
• HelloWorld::start();
• return 0;
• }
第四种方法:用类内部函数在类外部创建线程
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <string>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello(const std::string& str)
• {
• std::cout <<str<< std::endl;
• }
• };
•
• int main(int argc, char* argv[])
• {
• HelloWorld obj;
• boost::thread thrd( boost::bind(&HelloWorld::hello,&obj,"Hello
• world, I''m a thread!" ) ) ;
• thrd.join();
• return 0;
• }
如果线程需要绑定的函数有参数则需要使用boost::bind。比如想使用 boost::thread创建一个线程来执行函数:void f(int i),
如果这样写:boost::thread thrd(f)是不对的,因为thread构造函数声明接受的是一个没有参数且返回类型为void的型别,而且
不提供参数i的值f也无法运行,这时就可以写:boost::thread thrd(boost::bind(f,1))。涉及到有参函数的绑定问题基本上都
是boost::thread、boost::function、boost::bind结合起来使用。
互斥体
• 一个互斥体一次只允许一个线程访问共享区。当一个线程想要访问共享区时,首先要做的就是锁住(lock)互斥体。
• Boost线程库支持两大类互斥体,包括简单互斥体(simple mutex)和递归互斥体(recursive mutex)。
有了递归互斥体,单个线程就可以对互斥体多次上锁,当然也必须解锁同样次数来保证其他线程可以对这个互斥体上锁。
• Boost线程库提供的互斥体类型:
boost::mutex,
boost::try_mutex,
boost::timed_mutex,
boost::recursive_mutex,
boost::recursive_try_mutex,
boost::recursive_timed_mutex,
boost::shared_mutex
• mutex类采用Scope Lock模式实现互斥体的上锁和解锁。即构造函数对互斥体加锁,析构函数对互斥体解锁。
• 对应现有的几个mutex导入了scoped_lock,scoped_try_lock,scoped_timed_lock.
• scoped系列的特色就是析构时解锁,默认构造时加锁,这就很好的确定在某个作用域下某线程独占某段代码。
mutex+scoped_lock
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• boost::mutex io_mutex;
• void count(int id)
• {
• for (int i = 0; i < 10; ++i)
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << id << ": " << i << std::endl;
• }
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(boost::bind(&count, 1));
• boost::thread thrd2(boost::bind(&count, 2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
try_mutex+scoped_try_lock
• void loop(void)
• {
• bool running = true;
• while (running)
• {
• static boost::try_mutex iomutex;
• {
• boost::try_mutex::scoped_try_lock lock(iomutex);//锁定mutex
• if (lock.owns_lock())
• {
• std::cout << "Get lock." << std::endl;
• }
• else
• {
• // To do
• std::cout << "Not get lock." << std::endl;
• boost::thread::yield(); //释放控制权
• continue;
• }
• } //lock析构,iomutex解锁
• }
• }
timed_mutex+scoped_timed_mutex
• void loop(void)
• {
• bool running = true;
• while (running)
• {
• typedef boost::timed_mutex MUTEX;
• typedef MUTEX::scoped_timed_lock LOCK;
• static MUTEX iomutex;
• {
• boost::xtime xt;
• boost::xtime_get(&xt,boost::TIME_UTC);
• xt.sec += 1; //超时时间秒
• LOCK lock(iomutex, xt); //锁定mutex
• if (lock.owns_lock())
• {
• std::cout << "Get lock." << std::endl;
• }
• else
• {
• std::cout << "Not get lock." << std::endl;
• boost::thread::yield(); //释放控制权
• }
• //::sleep(10000); //长时间
• } //lock析构,iomutex解锁
• //::sleep(250);
• }
• }
shared_mutex
• 应用boost::thread的shared_mutex实现singled_write/multi_read的简单例子
• #include <iostream>
• #include <boost/thread/thread.hpp>
• #include <boost/thread/shared_mutex.hpp>
• using namespace std;
• using namespace boost;
• boost::shared_mutex shr_mutex;
• /// 这个是辅助类,能够保证log_info被完整的输出
• class safe_log {
• public:
• static void log(const std::string& log_info) {
• boost::mutex::scoped_lock lock(log_mutex);
• cout << log_info << endl;
• }
• private:
• static boost::mutex log_mutex;
• };
• boost::mutex safe_log::log_mutex;
• void write_process() {
• shr_mutex.lock();
• safe_log::log("begin of write_process");
• safe_log::log("end of write_process");
• shr_mutex.unlock();
• }
• void read_process() {
• shr_mutex.lock_shared();
• safe_log::log("begin of read_process");
• safe_log::log("end of read_process");
• shr_mutex.unlock_shared();
• }
• int main() {
• thread_group threads;
• for (int i = 0; i < 10; ++ i) {
• threads.create_thread(&write_process);
• threads.create_thread(&read_process);
• }
• threads.join_all();
• ::system("PAUSE");
• return 0;
• }
条件变量
• 有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。
比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情
况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。
• boost::condition
typedef condition_variable_any condition;
void wait(unique_lock<mutex>& m);
• boost::condition_variable
template<typename lock_type>
void wait(lock_type& m);
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/thread/condition.hpp>
• #include <iostream>
• const int BUF_SIZE = 10;
• const int ITERS = 100;
• boost::mutex io_mutex;
• class buffer
• {
• public:
• typedef boost::mutex::scoped_lock scoped_lock;
• buffer()
• : p(0), c(0), full(0)
• {
• }
• void put(int m)
• {
• scoped_lock lock(mutex);
• if (full == BUF_SIZE)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "Buffer is full. Waiting..." << std::endl;
• }
• while (full == BUF_SIZE)
• cond.wait(lock);
• }
• buf[p] = m;
• p = (p+1) % BUF_SIZE;
• ++full;
• cond.notify_one();
• }
• int get()
• {
• scoped_lock lk(mutex);
• if (full == 0)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "Buffer is empty. Waiting..." << std::endl;
• }
• while (full == 0)
• cond.wait(lk);
• }
• int i = buf[c];
• c = (c+1) % BUF_SIZE;
• --full;
• cond.notify_one();
• return i;
• }
• private:
• boost::mutex mutex;
• boost::condition cond;
• unsigned int p, c, full;
• int buf[BUF_SIZE];
• };
• buffer buf;
• void writer()
• {
• for (int n = 0; n < ITERS; ++n)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "sending: " << n << std::endl;
• }
• buf.put(n);
• }
• }
• void reader()
• {
• for (int x = 0; x < ITERS; ++x)
• {
• int n = buf.get();
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "received: " << n << std::endl;
• }
• }
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(&reader);
• boost::thread thrd2(&writer);
• thrd1.join();
• thrd2.join();
• return 0;
• }
线程局部存储
• 函数的不可重入。
• Boost线程库提供了智能指针boost::thread_specific_ptr来访问本地存储线程(thread local storage)。
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/thread/tss.hpp>
• #include <iostream>
• boost::mutex io_mutex;
• boost::thread_specific_ptr<int> ptr;
• struct count
• {
• count(int id) : id(id) { }
• void operator()()
• {
• if (ptr.get() == 0)
• ptr.reset(new int(0));
• for (int i = 0; i < 10; ++i)
• {
• (*ptr)++; // 往自己的线程上加
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << id << ": " << *ptr << std::endl;
• }
• }
• int id;
• };
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(count(1));
• boost::thread thrd2(count(2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
仅运行一次的例程
• 如何使得初始化工作(比如说构造函数)也是线程安全的。
• “一次实现”(once routine)。“一次实现”在一个应用程序只能执行一次。
• Boost线程库提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏 BOOST_ONCE_INIT。
• #include <boost/thread/thread.hpp>
• #include <boost/thread/once.hpp>
• #include <iostream>
• int i = 0;
• boost::once_flag flag = BOOST_ONCE_INIT;
• void init()
• {
• ++i;
• }
• void thread()
• {
• boost::call_once(&init, flag);
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(&thread);
• boost::thread thrd2(&thread);
• thrd1.join();
• thrd2.join();
• std::cout << i << std::endl;
• return 0;
• }
Boost线程库的未来
• Boost线程库正在计划加入一些新特性。其中包括boost::read_write_mutex,它可以让多个线程同时从共享区中读取数据,
但是一次只可能有一个线程向共享区写入数据;boost::thread_barrier,它使得一组线程处于等待状态,知道所有得线程
都都进入了屏障区;boost::thread_pool,他允许执行一些小的routine而不必每一都要创建或是销毁一个线程。
• Boost线程库已经作为标准中的类库技术报告中的附件提交给C++标准委员会,它的出现也为下一版C++标准吹响了第一声号角。
委员会成员对 Boost线程库的初稿给予了很高的评价,当然他们还会考虑其他的多线程库。他们对在C++标准中加入对多线程的
支持非常感兴趣。从这一点上也可以看出,多线程在C++中的前途一片光明。
创建线程
boost::thread 类描述线程的执行。缺省构造器创建一个当前线程的执行的实例。重载构造器调用一个无参也无返回值的函数对象,这个构造器启动一个新线程,其调用函数对象执行线程。
刚开始好像这样设计不如典型的C实现方式,创建一个线程,一个参数是void指针类型,可传递一个被新线程调用的函数,另一个参数可传递特定的数据到线程。但是,Boost.Threads 用函数对象代替函数指针,这使得函数对象携带线程需要的数据。这种方法更灵活,并且是线程安全的。对于和函数对象库的组合,比如 Boost.Bind ,这种设计允许你容易地传递任何数量的数据到新创建的线程中。
boost::thread 的 == 和 != 方法用来比较两个线程对象是否在一个线程中;可以调用join()方法等待线程结束。例1是一个使用boost::thread 类的简单例子,创建一个新线程,在新线程中打印"Hello world, I'm a thread!",在main线程中等待这个线程的结束。
例1:
#include <boost/thread/thread.hpp>
#include <iostream>
void hello()
{
std::cout <<
"Hello world, I'm a thread!"
<< std::endl;
}
int main(int argc, char* argv[])
{
boost::thread thrd(&hello);
thrd.join();
return 0;
}
互斥体
我们要知道在多线程程序中多个线程同时访问共享资源时如何同步。假如一个线程试图修改共享数据的值,同时另一个线程试图读这个值,结果是不确定的。为防止这种情况发生,要用专门的原子类型和原子操作。有一种叫做互斥(mutex)。互斥在同一时刻仅允许一个线程访问共享资源。当一个线程要访问共享资源的时候,它首先要“锁定(lock)”互斥,假如另一个线程先一步锁定了互斥,这个线程要等待直到另一个线程“解除锁定(unlock)”互斥,这就保证了同时仅有一个线程访问共享资源。
互斥有几个变种。 Boost.Threads 支持两类互斥,简单互斥和递归互斥。简单互斥仅能被锁定一次,假如同一线程试图锁定互斥两次,将造成死锁,导致这个线程永远等待下去。对于递归互斥,单个线程可以锁定互斥多次,也必须解除锁定相同的次数,这样才能使其他线程锁定互斥。
对于这两种互斥中,一个线程可以通过如下三种途径锁定互斥:
1.尝试且锁定互斥,等待直到没有其他线程锁定互斥。
2.尝试且锁定互斥,假如其他线程已经锁定互斥就立即返回。
3.尝试且锁定互斥,等待直到没有其他线程锁定互斥或直到指定的超时时间到。
Boost.Threads 提供以下六种互斥类型:boost::mutex, boost::try_mutex, boost::timed_mutex, boost::recursive_mutex, boost::recursive_try_mutex, 和 boost::recursive_timed_mutex。
假如一个互斥被锁定,但是没有被解锁,就会导致死锁。这个错误非常普遍,因此Boost.Threads的设计可以避免这种情况的发生。不直接使用锁定、解锁操作是可行的,使用锁对象,锁对象的构造中锁定,在析构的时候解锁。 C++语言的规则确保析构总是被调用,因此当异常发生时,互斥将被解锁。
虽然这种机制对使用互斥有帮助,但是当异常发生时只能保证互斥被解锁,却可能使共享数据处于无效状态。因此当异常发生时,使数据处于不一致状态使程序员的责任。另外,共享的资源要在几个线程之间共享,确保这些线程都能访问到它。
例2是使用boost::mutex类的简单例子。创建两个线程,循环10次,写ID到std::cout。main线程等待这两个线程结束。std::cout对象是共享资源,因此每个线程使用全局互斥来保证每次只有一个线程写它。
例2:
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
boost::mutex io_mutex;
struct count
{
count(int id) : id(id) { }
void operator()()
{
for (int i = 0; i < 10; ++i)
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << id << ": "
<< i << std::endl;
}
}
int id;
};
int main(int argc, char* argv[])
{
boost::thread thrd1(count(1));
boost::thread thrd2(count(2));
thrd1.join();
thrd2.join();
return 0;
}
例3:
// 这个例子和例2一样,除了使用Boost.Bind来简化创建线程携带数据,避免使用函数对象
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>
boost::mutex io_mutex;
void count(int id)
{
for (int i = 0; i < 10; ++i)
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << id << ": " <<
i << std::endl;
}
}
int main(int argc, char* argv[])
{
boost::thread thrd1(
boost::bind(&count, 1));
boost::thread thrd2(
boost::bind(&count, 2));
thrd1.join();
thrd2.join();
return 0;
}
条件变量
有时候锁定共享资源且使用它满足不了应用。有时共享资源在能够使用前需要满足一些特定状态。例如,某线程试图把数据从栈中取出,要是数据没有到达它需要等待。互斥不能满足这种同步。另一种同步类型,叫做条件变量,能够满足这种情况。
条件变量总是和互斥、共享资源一起使用。一个线程首先锁定互斥,然后坚持共享资源是否处于特定状态,假如不在所需要的状态,线程在条件变量上等待。在等待期间这个操作引发互斥解锁,因此另一线程可以改变共享资源的状态。当等待操作结束会确保互斥又被锁定。当另一线程改变了共享资源的状态,其需要通知等待条件变量的线程,确使等待线程从等待操作返回。
例4是使用boost::condition类的简单例子。
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <iostream>
const int BUF_SIZE = 10;
const int ITERS = 100;
boost::mutex io_mutex;
class buffer
{
public:
typedef boost::mutex::scoped_lock
scoped_lock;
buffer()
: p(0), c(0), full(0)
{
}
void put(int m)
{
scoped_lock lock(mutex);
if (full == BUF_SIZE)
{
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout <<
"Buffer is full. Waiting..."
<< std::endl;
}
while (full == BUF_SIZE)
cond.wait(lock);
}
buf[p] = m;
p = (p+1) % BUF_SIZE;
++full;
cond.notify_one();
}
int get()
{
scoped_lock lk(mutex);
if (full == 0)
{
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout <<
"Buffer is empty. Waiting..."
<< std::endl;
}
while (full == 0)
cond.wait(lk);
}
int i = buf[c];
c = (c+1) % BUF_SIZE;
--full;
cond.notify_one();
return i;
}
private:
boost::mutex mutex;
boost::condition cond;
unsigned int p, c, full;
int buf[BUF_SIZE];
};
buffer buf;
void writer()
{
for (int n = 0; n < ITERS; ++n)
{
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << "sending: "
<< n << std::endl;
}
buf.put(n);
}
}
void reader()
{
for (int x = 0; x < ITERS; ++x)
{
int n = buf.get();
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << "received: "
<< n << std::endl;
}
}
}
int main(int argc, char* argv[])
{
boost::thread thrd1(&reader);
boost::thread thrd2(&writer);
thrd1.join();
thrd2.join();
return 0;
}
线程局部存储
许多函数被实现成不可重入。这意味着它是非线程安全的。就是说不能被多个线程同时调用。一个不可重入的函数通常保存静态数据,或返回值指向静态数据。例如,std::strtok是不可重入的,以为它用静态数据保存字符串来分解成标记。
有两种途径可使一个不可重入函数变成可重入函数。一种途径使修改函数接口,譬如可以传递一个指向数据的指针或引用来代替静态数据。这种方案简单且性能最佳,但是需要改变公共接口,导致应用代码的大量修改。另一种途径是不改变公共接口,用线程局部存储(有时也叫线程特定存储)代替静态数据。
线程局部存储时数据和特定线程相关联。多线程库给出存取线程局部存储的接口,可存取当前线程的数据实例。每个线程提供其自己的数据实例,因此对同时存取就不会有任何问题。但是,线程局部存储比静态数据或局部数据要慢。因此这也不总是最好的方案。但是它是不改变公共接口的唯一方案。
Boost.Threads 通过智能(smart)指针 boost::thread_specific_ptr 来存取线程局部存储。首先每个线程试图获取这个智能指针的一个实例,其有一个 NULL 值,因此第一次使用的时候检查指针要是为 NULL,就需要初始化。Boost.Threads确保线程局部存储的数据在线程退出的时候被清除。
例5是使用 boost::thread_specific_ptr 的简单例子。
例5:
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/tss.hpp>
#include <iostream>
boost::mutex io_mutex;
boost::thread_specific_ptr<int> ptr;
struct count
{
count(int id) : id(id) { }
void operator()()
{
if (ptr.get() == 0)
ptr.reset(new int(0));
for (int i = 0; i < 10; ++i)
{
(*ptr)++;
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << id << ": "
<< *ptr << std::endl;
}
}
int id;
};
int main(int argc, char* argv[])
{
boost::thread thrd1(count(1));
boost::thread thrd2(count(2));
thrd1.join();
thrd2.join();
return 0;
}
仅运行一次的例程
一般初始化例程仅执行一次,不能被多次执行,这就需要保证初始化例程是线程安全的,也就是说仅有一个线程能调用执行。这种情况叫做“仅运行一次的例程”。一个“仅运行一次的例程”在应用程序中只能被调用一次。假如多个线程试图在同时执行这个例程,仅有一个实际能执行,其他都将等待这个线程执行完毕后才返回。 Boost.Threads 通过boost::call_once 函数和boost::once_flag 标志类型和一个特定的宏定义BOOST_ONCE_INIT来支持“仅运行一次的例程”。
例6 展示了 boost::call_once 的简单使用。
例6:
#include <boost/thread/thread.hpp>
#include <boost/thread/once.hpp>
#include <iostream>
int i = 0;
boost::once_flag flag =
BOOST_ONCE_INIT;
void init()
{
++i;
}
void thread()
{
boost::call_once(&init, flag);
}
int main(int argc, char* argv[])
{
boost::thread thrd1(&thread);
boost::thread thrd2(&thread);
thrd1.join();
thrd2.join();
std::cout << i << std::endl;
return 0;
}
背景
• 今天互联网应用服务程序普遍使用多线程来提高与多客户链接时的效率;为了达到最大的吞吐量,事务服务器在单独的线程上运行服务程序;
GUI应用程序将那些费时,复杂的处理以线程的形式单独运行,以此来保证用户界面能够及时响应用户的操作。这样使用多线程的例子还有很多。
• 跨平台
创建线程
• 头文件 <boost/thread/thread.hpp>
namespace boost {
class thread;
class thread_group;
}
• thread():构造一个表示当前执行线程的线程对象
• explicit thread(const boost::function0<void>& threadfunc)
注:boost::function0<void>可以简单看为:一个无返回(返回void),无参数的函数。这里的函数也可以是类重载operator()构成的函数。
第一种方式:最简单方法
• #include <boost/thread/thread.hpp>
• #include <iostream>
•
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
•
• int main(int argc, char* argv[])
• {
• boost::thread thrd(&hello);
• thrd.join();
• return 0;
• }
第二种方式:复杂类型对象作为参数来创建线程
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <iostream>
•
• boost::mutex io_mutex;
•
• struct count
• {
• count(int id) : id(id) { }
•
• void operator()()
• {
• for (int i = 0; i < 10; ++i)
• {
• boost::mutex::scoped_lock
• lock(io_mutex);
• std::cout << id << ": "
• << i << std::endl;
• }
• }
•
• int id;
• };
•
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(count(1));
• boost::thread thrd2(count(2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
第三种方式:在类内部创建线程
• (1)类内部静态方法启动线程
• #include <boost/thread/thread.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• static void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• static void start()
• {
•
• boost::thread thrd( hello );
• thrd.join();
• }
•
• };
• int main(int argc, char* argv[])
• {
• HelloWorld::start();
•
• return 0;
• }
• 在这里start()和hello()方法都必须是static方法。
• (2)如果要求start()和hello()方法不能是静态方法则采用下面的方法创建线程:
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• void start()
• {
• boost::function0< void> f = boost::bind(&HelloWorld::hello,this);
• boost::thread thrd( f );
• thrd.join();
• }
•
• };
• int main(int argc, char* argv[])
• {
• HelloWorld hello;
• hello.start();
• return 0;
• }
• (3)在Singleton模式内部创建线程:
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello()
• {
• std::cout <<
• "Hello world, I''m a thread!"
• << std::endl;
• }
• static void start()
• {
• boost::thread thrd( boost::bind
• (&HelloWorld::hello,&HelloWorld::getInstance() ) ) ;
• thrd.join();
• }
• static HelloWorld& getInstance()
• {
• if ( !instance )
• instance = new HelloWorld;
• return *instance;
• }
• private:
• HelloWorld(){}
• static HelloWorld* instance;
•
• };
• HelloWorld* HelloWorld::instance = 0;
• int main(int argc, char* argv[])
• {
• HelloWorld::start();
• return 0;
• }
第四种方法:用类内部函数在类外部创建线程
• #include <boost/thread/thread.hpp>
• #include <boost/bind.hpp>
• #include <string>
• #include <iostream>
• class HelloWorld
• {
• public:
• void hello(const std::string& str)
• {
• std::cout <<str<< std::endl;
• }
• };
•
• int main(int argc, char* argv[])
• {
• HelloWorld obj;
• boost::thread thrd( boost::bind(&HelloWorld::hello,&obj,"Hello
• world, I''m a thread!" ) ) ;
• thrd.join();
• return 0;
• }
如果线程需要绑定的函数有参数则需要使用boost::bind。比如想使用 boost::thread创建一个线程来执行函数:void f(int i),
如果这样写:boost::thread thrd(f)是不对的,因为thread构造函数声明接受的是一个没有参数且返回类型为void的型别,而且
不提供参数i的值f也无法运行,这时就可以写:boost::thread thrd(boost::bind(f,1))。涉及到有参函数的绑定问题基本上都
是boost::thread、boost::function、boost::bind结合起来使用。
互斥体
• 一个互斥体一次只允许一个线程访问共享区。当一个线程想要访问共享区时,首先要做的就是锁住(lock)互斥体。
• Boost线程库支持两大类互斥体,包括简单互斥体(simple mutex)和递归互斥体(recursive mutex)。
有了递归互斥体,单个线程就可以对互斥体多次上锁,当然也必须解锁同样次数来保证其他线程可以对这个互斥体上锁。
• Boost线程库提供的互斥体类型:
boost::mutex,
boost::try_mutex,
boost::timed_mutex,
boost::recursive_mutex,
boost::recursive_try_mutex,
boost::recursive_timed_mutex,
boost::shared_mutex
• mutex类采用Scope Lock模式实现互斥体的上锁和解锁。即构造函数对互斥体加锁,析构函数对互斥体解锁。
• 对应现有的几个mutex导入了scoped_lock,scoped_try_lock,scoped_timed_lock.
• scoped系列的特色就是析构时解锁,默认构造时加锁,这就很好的确定在某个作用域下某线程独占某段代码。
mutex+scoped_lock
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/bind.hpp>
• #include <iostream>
• boost::mutex io_mutex;
• void count(int id)
• {
• for (int i = 0; i < 10; ++i)
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << id << ": " << i << std::endl;
• }
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(boost::bind(&count, 1));
• boost::thread thrd2(boost::bind(&count, 2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
try_mutex+scoped_try_lock
• void loop(void)
• {
• bool running = true;
• while (running)
• {
• static boost::try_mutex iomutex;
• {
• boost::try_mutex::scoped_try_lock lock(iomutex);//锁定mutex
• if (lock.owns_lock())
• {
• std::cout << "Get lock." << std::endl;
• }
• else
• {
• // To do
• std::cout << "Not get lock." << std::endl;
• boost::thread::yield(); //释放控制权
• continue;
• }
• } //lock析构,iomutex解锁
• }
• }
timed_mutex+scoped_timed_mutex
• void loop(void)
• {
• bool running = true;
• while (running)
• {
• typedef boost::timed_mutex MUTEX;
• typedef MUTEX::scoped_timed_lock LOCK;
• static MUTEX iomutex;
• {
• boost::xtime xt;
• boost::xtime_get(&xt,boost::TIME_UTC);
• xt.sec += 1; //超时时间秒
• LOCK lock(iomutex, xt); //锁定mutex
• if (lock.owns_lock())
• {
• std::cout << "Get lock." << std::endl;
• }
• else
• {
• std::cout << "Not get lock." << std::endl;
• boost::thread::yield(); //释放控制权
• }
• //::sleep(10000); //长时间
• } //lock析构,iomutex解锁
• //::sleep(250);
• }
• }
shared_mutex
• 应用boost::thread的shared_mutex实现singled_write/multi_read的简单例子
• #include <iostream>
• #include <boost/thread/thread.hpp>
• #include <boost/thread/shared_mutex.hpp>
• using namespace std;
• using namespace boost;
• boost::shared_mutex shr_mutex;
• /// 这个是辅助类,能够保证log_info被完整的输出
• class safe_log {
• public:
• static void log(const std::string& log_info) {
• boost::mutex::scoped_lock lock(log_mutex);
• cout << log_info << endl;
• }
• private:
• static boost::mutex log_mutex;
• };
• boost::mutex safe_log::log_mutex;
• void write_process() {
• shr_mutex.lock();
• safe_log::log("begin of write_process");
• safe_log::log("end of write_process");
• shr_mutex.unlock();
• }
• void read_process() {
• shr_mutex.lock_shared();
• safe_log::log("begin of read_process");
• safe_log::log("end of read_process");
• shr_mutex.unlock_shared();
• }
• int main() {
• thread_group threads;
• for (int i = 0; i < 10; ++ i) {
• threads.create_thread(&write_process);
• threads.create_thread(&read_process);
• }
• threads.join_all();
• ::system("PAUSE");
• return 0;
• }
条件变量
• 有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。
比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情
况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。
• boost::condition
typedef condition_variable_any condition;
void wait(unique_lock<mutex>& m);
• boost::condition_variable
template<typename lock_type>
void wait(lock_type& m);
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/thread/condition.hpp>
• #include <iostream>
• const int BUF_SIZE = 10;
• const int ITERS = 100;
• boost::mutex io_mutex;
• class buffer
• {
• public:
• typedef boost::mutex::scoped_lock scoped_lock;
• buffer()
• : p(0), c(0), full(0)
• {
• }
• void put(int m)
• {
• scoped_lock lock(mutex);
• if (full == BUF_SIZE)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "Buffer is full. Waiting..." << std::endl;
• }
• while (full == BUF_SIZE)
• cond.wait(lock);
• }
• buf[p] = m;
• p = (p+1) % BUF_SIZE;
• ++full;
• cond.notify_one();
• }
• int get()
• {
• scoped_lock lk(mutex);
• if (full == 0)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "Buffer is empty. Waiting..." << std::endl;
• }
• while (full == 0)
• cond.wait(lk);
• }
• int i = buf[c];
• c = (c+1) % BUF_SIZE;
• --full;
• cond.notify_one();
• return i;
• }
• private:
• boost::mutex mutex;
• boost::condition cond;
• unsigned int p, c, full;
• int buf[BUF_SIZE];
• };
• buffer buf;
• void writer()
• {
• for (int n = 0; n < ITERS; ++n)
• {
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "sending: " << n << std::endl;
• }
• buf.put(n);
• }
• }
• void reader()
• {
• for (int x = 0; x < ITERS; ++x)
• {
• int n = buf.get();
• {
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << "received: " << n << std::endl;
• }
• }
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(&reader);
• boost::thread thrd2(&writer);
• thrd1.join();
• thrd2.join();
• return 0;
• }
线程局部存储
• 函数的不可重入。
• Boost线程库提供了智能指针boost::thread_specific_ptr来访问本地存储线程(thread local storage)。
• #include <boost/thread/thread.hpp>
• #include <boost/thread/mutex.hpp>
• #include <boost/thread/tss.hpp>
• #include <iostream>
• boost::mutex io_mutex;
• boost::thread_specific_ptr<int> ptr;
• struct count
• {
• count(int id) : id(id) { }
• void operator()()
• {
• if (ptr.get() == 0)
• ptr.reset(new int(0));
• for (int i = 0; i < 10; ++i)
• {
• (*ptr)++; // 往自己的线程上加
• boost::mutex::scoped_lock lock(io_mutex);
• std::cout << id << ": " << *ptr << std::endl;
• }
• }
• int id;
• };
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(count(1));
• boost::thread thrd2(count(2));
• thrd1.join();
• thrd2.join();
• return 0;
• }
仅运行一次的例程
• 如何使得初始化工作(比如说构造函数)也是线程安全的。
• “一次实现”(once routine)。“一次实现”在一个应用程序只能执行一次。
• Boost线程库提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏 BOOST_ONCE_INIT。
• #include <boost/thread/thread.hpp>
• #include <boost/thread/once.hpp>
• #include <iostream>
• int i = 0;
• boost::once_flag flag = BOOST_ONCE_INIT;
• void init()
• {
• ++i;
• }
• void thread()
• {
• boost::call_once(&init, flag);
• }
• int main(int argc, char* argv[])
• {
• boost::thread thrd1(&thread);
• boost::thread thrd2(&thread);
• thrd1.join();
• thrd2.join();
• std::cout << i << std::endl;
• return 0;
• }
Boost线程库的未来
• Boost线程库正在计划加入一些新特性。其中包括boost::read_write_mutex,它可以让多个线程同时从共享区中读取数据,
但是一次只可能有一个线程向共享区写入数据;boost::thread_barrier,它使得一组线程处于等待状态,知道所有得线程
都都进入了屏障区;boost::thread_pool,他允许执行一些小的routine而不必每一都要创建或是销毁一个线程。
• Boost线程库已经作为标准中的类库技术报告中的附件提交给C++标准委员会,它的出现也为下一版C++标准吹响了第一声号角。
委员会成员对 Boost线程库的初稿给予了很高的评价,当然他们还会考虑其他的多线程库。他们对在C++标准中加入对多线程的
支持非常感兴趣。从这一点上也可以看出,多线程在C++中的前途一片光明。