BOOST 线程完全攻略
1 创建线程
首先看看boost::thread的构造函数吧,boost::thread有两个构造函数:
(1)thread():构造一个表示当前执行线程的线程对象;
(2)explicit thread(const boost::function0& threadfunc):
boost::function0可以简单看为:一个无返回(返回void),无参数的函数。这里的函数也可以是类重载operator()构成的函数;该构造函数传入的是函数对象而并非是函数指针,这样一个具有一般函数特性的类也能作为参数传入,在下面有例子。
第一种方式:最简单方法
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 ;
}
第二种方式:复杂类型对象作为参数来创建线程:
boost::mutex io_mutex;
struct count
{
count(int id) : id(id) { } void operator()()
{
for (int i = ; i < ; ++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());
boost::thread thrd2(count());
thrd1.join();
thrd2.join();
return ;
}
第三种方式:在类内部创建线程;
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 ;
}
在这里start()和hello()方法都必须是static方法。
(2)如果要求start()和hello()方法不能是静态方法则采用下面的方法创建线程:
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 ;
}
第四种方法:用类内部函数在类外部创建线程;
class HelloWorld
{
public:
void hello(const std::string& str)
{
std::cout < }
}; 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 ;
}
如果线程需要绑定的函数有参数则需要使用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结合起来使用。
2 互斥体
任何写过多线程程序的人都知道避免不同线程同时访问共享区域的重要性。如果一个线程要改变共享区域中某个数据,而与此同时另一线程正在读这个数据,那么结果将是未定义的。为了避免这种情况的发生就要使用一些特殊的原始类型和操作。其中最基本的就是互斥体(mutex,mutual exclusion的缩写)。一个互斥体一次只允许一个线程访问共享区。当一个线程想要访问共享区时,首先要做的就是锁住(lock)互斥体。如果其他的线程已经锁住了互斥体,那么就必须先等那个线程将互斥体解锁,这样就保证了同一时刻只有一个线程能访问共享区域。
互斥体的概念有不少变种。Boost线程库支持两大类互斥体,包括简单互斥体(simple mutex)和递归互斥体(recursive mutex)。如果同一个线程对互斥体上了两次锁,就会发生死锁(deadlock),也就是说所有的等待解锁的线程将一直等下去。有了递归互斥体,单个线程就可以对互斥体多次上锁,当然也必须解锁同样次数来保证其他线程可以对这个互斥体上锁。
在这两大类互斥体中,对于线程如何上锁还有多个变种。一个线程可以有三种方法来对一个互斥体加锁:
- 一直等到没有其他线程对互斥体加锁。
- 如果有其他互斥体已经对互斥体加锁就立即返回。
- 一直等到没有其他线程互斥体加锁,直到超时。
似乎最佳的互斥体类型是递归互斥体,它可以使用所有三种上锁形式。然而每一个变种都是有代价的。所以Boost线程库允许你根据不同的需要使用最有效率的互斥体类型。Boost线程库提供了6中互斥体类型,下面是按照效率进行排序:
boost::mutex,
boost::try_mutex,
boost::timed_mutex,
boost::recursive_mutex,
boost::recursive_try_mutex,
boost::recursive_timed_mutex
如果互斥体上锁之后没有解锁就会发生死锁。这是一个很普遍的错误,Boost线程库就是要将其变成不可能(至少时很困难)。直接对互斥体上锁和解锁对于Boost线程库的用户来说是不可能的。mutex类通过teypdef定义在RAII中实现的类型来实现互斥体的上锁和解锁。这也就是大家知道的Scope Lock模式。为了构造这些类型,要传入一个互斥体的引用。构造函数对互斥体加锁,析构函数对互斥体解锁。C++保证了析构函数一定会被调用,所以即使是有异常抛出,互斥体也总是会被正确的解锁。
3 、条件变量
有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。
条件变量的使用总是和互斥体及共享资源联系在一起的。线程首先锁住互斥体,然后检验共享资源的状态是否处于可使用的状态。如果不是,那么线程就要等待条件变量。要指向这样的操作就必须在等待的时候将互斥体解锁,以便其他线程可以访问共享资源并改变其状态。它还得保证从等到得线程返回时互斥体是被上锁得。当另一个线程改变了共享资源的状态时,它就要通知正在等待条件变量得线程,并将之返回等待的线程。
List4是一个使用了boost::condition的简单例子。有一个实现了有界缓存区的类和一个固定大小的先进先出的容器。由于使用了互斥体boost::mutex,这个缓存区是线程安全的。put和get使用条件变量来保证线程等待完成操作所必须的状态。有两个线程被创建,一个在buffer中放入100个整数,另一个将它们从buffer中取出。这个有界的缓存一次只能存放10个整数,所以这两个线程必须周期性的等待另一个线程。为了验证这一点,put和get在std::cout中输出诊断语句。最后,当两个线程结束后,main函数也就执行完毕了。
4、线程局部存储
大多数函数都不是可重入的。这也就是说在某一个线程已经调用了一个函数时,如果你再调用同一个函数,那么这样是不安全的。一个不可重入的函数通过连续的调用来保存静态变量或者是返回一个指向静态数据的指针。 举例来说,std::strtok就是不可重入的,因为它使用静态变量来保存要被分割成符号的字符串。
有两种方法可以让不可重用的函数变成可重用的函数。第一种方法就是改变接口,用指针或引用代替原先使用静态数据的地方。比方说,POSIX定义了strok_r,std::strtok中的一个可重入的变量,它用一个额外的char**参数来代替静态数据。这种方法很简单,而且提供了可能的最佳效果。但是这样必须改变公共接口,也就意味着必须改代码。另一种方法不用改变公有接口,而是用本地存储线程(thread local storage)来代替静态数据(有时也被成为特殊线程存储,thread-specific storage)。
Boost线程库提供了智能指针boost::thread_specific_ptr来访问本地存储线程。每一个线程第一次使用这个智能指针的实例时,它的初值是NULL,所以必须要先检查这个它的只是否为空,并且为它赋值。Boost线程库保证本地存储线程中保存的数据会在线程结束后被清除。
List5是一个使用boost::thread_specific_ptr的简单例子。其中创建了两个线程来初始化本地存储线程,并有10次循环,每一次都会增加智能指针指向的值,并将其输出到std::cout上(由于std::cout是一个共享资源,所以通过互斥体进行同步)。main线程等待这两个线程结束后就退出。从这个例子输出可以明白的看出每个线程都处理属于自己的数据实例,尽管它们都是使用同一个boost::thread_specific_ptr。
BOOST 线程完全攻略的更多相关文章
- BOOST 线程完全攻略 - 基础篇
http://blog.csdn.net/iamnieo/article/details/2908621 2008-09-10 12:48 9202人阅读 评论(3) 收藏 举报 thread多线程l ...
- BOOST 线程完全攻略 - 扩展 - 可被关闭的线程类
本文假设读者已经基本了解boost线程库的使用方法. boost是个开源工程,线程这一块也在不断完善之中,到现在这个阶段,boost::thread仅仅实现了一个完美的技术框架,但是读者在实际使用中会 ...
- 【C/C++】BOOST 线程完全攻略 - 基础篇
C++多线程开发是一个复杂的事情,mfc下提供了CWinThread类,和AfxBeginThread等等函数,但是在使用中会遇到很多麻烦事情,例如线程之间参数传递的问题,我们一般都是把参数new一个 ...
- BOOST 线程完全攻略 - 扩展 - 线程消息通讯
// controlled_module_ex.hpp : controlled_module类的扩展 // 增强线程之间消息通讯 // 增加线程安全启动和安全关闭功能 // 增加定时器功能 #p ...
- BOOST 线程完全攻略 - 结束语
modulethread扩展多线程破解通讯 全文介绍了3个boost::thread的扩展类,希望能给大家书写多线程代码带来便捷. thread -> controlled_module_ex ...
- BOOST 线程完全攻略 - 扩展 - 事务线程
扩展threadtimermoduleexceptionsocket 什么叫事务线程 举个例子: 我们写一个IM客户端的登录子线程,则该子线程会有这么几个事务要处理 No.1 TCP Socket物理 ...
- linux内核升级图文攻略(转)
一.Linux内核概览Linux是一个一体化内核(monolithic kernel)系统.设备驱动程序可以完全访问硬件.Linux内的设备驱动程序可以方便地以模块化(modularize)的形式设置 ...
- JAVA EE企业级开发四步走完全攻略 [转]
http://bbs.51cto.com/thread-550558-1.html 本文是J2EE企业级开发四步走完全攻略索引,因内容比较广泛,涉及整个JAVA EE开发相关知识,这是一个长期的计划, ...
- IOS面试攻略
IOS面试攻略(1.0) 2013-10-13 20:58:09| 分类: IOS面试 | 标签:ios知识点总汇 ios面试 |举报|字号 订阅 来自:伊甸网 @ 看到这个关键字,我 ...
随机推荐
- POJ 3237 Tree 【树链剖分】+【线段树】
<题目链接> 题目大意: 给定一棵树,该树带有边权,现在对该树进行三种操作: 一:改变指定编号边的边权: 二:对树上指定路径的边权全部取反: 三:查询树上指定路径的最大边权值. 解题分析: ...
- Xamarin Essentials应用教程文件系统FileSystem
Xamarin Essentials应用教程文件系统FileSystem 文件系统用于管理设备内的各类文件.通过文件系统,应用程序可以创建永久文件和临时文件,也可以获取预先打包的文件,如预设数据库文件 ...
- 835.Hamming距离
描述 两个整数的Hamming距离是对应比特位不同的个数. 给定两个整数x和y,计算两者的Hamming距离. 0 ≤ x, y < 2^31. 您在真实的面试中是否遇到过这个题? 样例 输入: ...
- 在eclipse中配置Python环境
1.下载pydev https://sourceforge.net/projects/pydev/files/pydev/PyDev%202.2.2/ 2.解压pydev插件后 有features.p ...
- BZOJ.2655.calc(DP/容斥 拉格朗日插值)
BZOJ 洛谷 待补.刚刚政治会考完来把它补上了2333.考数学去了. DP: 首先把无序化成有序,选严格递增的数,最后乘个\(n!\). 然后容易想到令\(f_{i,j}\)表示到第\(i\)个数, ...
- Sql的行列(纵横表)转换
1.行转列: 表结构和数据: DROP TABLE IF EXISTS `kj`; CREATE TABLE `kj` ( `姓名` ) DEFAULT NULL, `课程` ) DEFAULT NU ...
- VirtWire 向客服发ticket
1 首先需要登录自己的账户 2 点击网页的Open Ticket 3 选择要发送何种类型的ticket 4 写自己的问题,包括一个合适的主题,选择你发ticket是针对哪个vps(一个账户下可以ord ...
- 潭州课堂25班:Ph201805201 django 项目 第二十九课 docker实例,文件下载前后台实现 (课堂笔记)
docker 实例 :wq!保存退出 放入一个 html 文件 权限不够,加 sudo 查看本地仓库的 image 运行 docker -- name,后跟个运行名, -p 物理机端口映射到容器端口, ...
- JS将日期转为距现在的时间长度
最近在弄一个回忆网站,其中有个一板块类似于情侣空间的纪念日. 照着弄了个类似的,效果如下: 在处理过程中需要把时间戳转为Date()对象,然后与本地时间相减获得时间差,通过运算转换成对应的年月日时长, ...
- PowerShell一次执行多条命令
PowerShell一次执行多条命令语句 使用CMD之后换到PS之后想一次执行多条命令会很不习惯,因为原来的&&语句连接符已经不能用了. 在各种搜索后没有发现网上有说明这个的.无奈只能 ...