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

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

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

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),也就是说全部的等待解锁的线程将一直等下去。有了递归互斥体,单个线程就能够对互斥体屡次上锁,固然也必须解锁一样次数来保证其余线程能够对这个互斥体上锁。code

 

 

 

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

 

  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函数也就执行完毕了。

 

 四、线程局部存储

  大多数函数都不是可重入的。这也就是说在某一个线程已经调用了一个函数时,若是你再调用同一个函数,那么这样是不安全的。一个不可重入的函数经过连续的调用来保存静态变量或者是返回一个指向静态数据的指针。 举例来讲,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。

相关文章
相关标签/搜索