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 0;
}

第二种方式:复杂类型对象作为参数来创建线程:

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

第三种方式:在类内部创建线程;

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()方法不能是静态方法则采用下面的方法创建线程:

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

第四种方法:用类内部函数在类外部创建线程;

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 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结合起来使用。

2 互斥体

  任何写过多线程程序的人都知道避免不同线程同时访问共享区域的重要性。如果一个线程要改变共享区域中某个数据,而与此同时另一线程正在读这个数据,那么结果将是未定义的。为了避免这种情况的发生就要使用一些特殊的原始类型和操作。其中最基本的就是互斥体(mutex,mutual exclusion的缩写)。一个互斥体一次只允许一个线程访问共享区。当一个线程想要访问共享区时,首先要做的就是锁住(lock)互斥体。如果其他的线程已经锁住了互斥体,那么就必须先等那个线程将互斥体解锁,这样就保证了同一时刻只有一个线程能访问共享区域。

  互斥体的概念有不少变种。Boost线程库支持两大类互斥体,包括简单互斥体(simple mutex)和递归互斥体(recursive mutex)。如果同一个线程对互斥体上了两次锁,就会发生死锁(deadlock),也就是说所有的等待解锁的线程将一直等下去。有了递归互斥体,单个线程就可以对互斥体多次上锁,当然也必须解锁同样次数来保证其他线程可以对这个互斥体上锁。

在这两大类互斥体中,对于线程如何上锁还有多个变种。一个线程可以有三种方法来对一个互斥体加锁:

  1. 一直等到没有其他线程对互斥体加锁。
  2. 如果有其他互斥体已经对互斥体加锁就立即返回。
  3. 一直等到没有其他线程互斥体加锁,直到超时。

似乎最佳的互斥体类型是递归互斥体,它可以使用所有三种上锁形式。然而每一个变种都是有代价的。所以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。

时间: 2024-10-21 22:15:55

BOOST 线程完全攻略的相关文章

程序员技术练级攻略

以下全文来自http://coolshell.cn/articles/4990.html 前言 你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把以下这些课外练习走一遍(朋友的抱怨:学校课程总是从理论出发,作业项目都看不出有什么实际作用,不如从工作中的需求出发) 建议: 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累而且还会在未来至少10年通用. 回顾一下历史,看看历史上时间线上技术的发展,你才能明白明天会是什么样. 一定要动手,例子不管多么简

转载 程序员技术练级攻略

转载 程序员技术练级攻略 博客分类: 转载 本文转载自陈皓(http://coolshell.cn/articles/author/haoel) 博客: http://coolshell.cn/articles/4990.html 月光博客6月12日发表了<写给新手程序员的一封信>,翻译自<An open letter to those who want to start programming>,我的朋友(他在本站的id是Mailper)告诉我,他希望在酷壳上看到一篇更具操作性的

程序猿技术练级攻略

伯乐人才网6月9日发表了<写给即将入行的程序猿的一封信>,翻译自<An open letter to those who want to start programming>.我的朋友(他在本站的id是Mailper)告诉我,他希望在酷壳上看到一篇更具操作性的文章. 由于他也是喜欢编程和技术的家伙.于是,我让他把他的一些学习Python和Web编程的一些点滴总结一下.于是他给我发来了一些他的心得和经历,我在把他的心得做了不多的增改,并依据我的经历添加了"进阶"一

程序员技术练级攻略(经典)

前言 你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把以下这些课外练习走一遍(朋友的抱怨:学校课程总是从理论出发,作业项目都看不出有什么实际作用,不如从工作中的需求出发) 建议: 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累而且还会在未来至少10年通用. 回顾一下历史,看看历史上时间线上技术的发展,你才能明白明天会是什么样. 一定要动手,例子不管多么简单,建议至少自己手敲一遍看看是否理解了里头的细枝末节. 一定要学会思考,思考为什么要这样,

redis大型攻略!

redis大型攻略 一:redis的简介.下载和安装 二:redis主从 三:redis在线升级 四:redis多实例 五:redis常见的操作 六:reids配置详解 实验环境:CentOS-6.5-x86_64   redis-2.8.9.tar.gz 一:redis的简介.下载和安装 Redis是一个key-valu存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集

Windows Socket五种I/O模型——代码全攻略(转)

Winsock 的I/O操作: 1. 两种I/O模式 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序.套接字 默认为阻塞模式.可以通过多线程技术进行处理. 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权.这种模式使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误.但功能强大.为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种: Windows Socket五种I/O模型——代码全攻

DAVINCI DM3730开发攻略——应用程序例程分析

过完2015年春节回来了,利用上班前的几天时间,先把这篇文章写完,本来是先写<DAVINCI DM3730开发攻略--linux-2.6.32移植>,但是那篇文章涉及内核的东西太多,不太好写,而本人已经很长时间没写新文章了,先发布这篇文章.后来想了想,从应用程序使用的角度分析,再一步一步深入内核里边去,也许更好. 前面几篇DM3730开发攻略讲到:一个DAVINCI  DM3730板子程序由xload,uboot, linux-2.6.32或者(linux-2.6.37),文件系统rootfs

LINUX SHELL脚本攻略笔记[速查]

Linux Shell脚本攻略笔记[速查] 资源 shell script run shell script echo printf 环境变量和变量 pgrep shell数学运算 命令状态 文件描述符和重定向 cat 数组和关联数组 alias date 调试脚本 函数和参数 管道 读取命令输出 read 字段分隔符和迭代器 循环 比较和测试 find xargs tr md5sum sha1sum 对目录进行校验 sort uniq tempfile split bash变量匹配切分 exp

php连接微软MSSQL(sql server)完全攻略

http://www.jb51.net/article/98364.htm php连接微软MSSQL(sql server)完全攻略 作者:吵吵 字体:[增加 减小] 类型:转载 时间:2016-11-27我要评论 在研究ezSQL的时候就看到了mssql_connect()等一些php提供的连接MSSQL的函数,本以为php这个开源的风靡世界的编程语言对连接微软的数据应该是不在话下的,但是到真正执行的时候,才发现困难多多 在研究ezSQL的时候就看到了mssql_connect()等一些php