当前位置:  编程语言>c/c++

c++的boost库多线程(Thread)编程(线程操作,互斥体mutex,条件变量)详解

 
    发布时间:2014-4-7  


    本文导语:  c++的boost库多线程(Thread)编程(线程操作,互斥体mutex,条件变量)详解 Boost是由C++标准委员会类库工作组成员发起,致力于为C++开发新的类库的组织。许多C++专家都投身于Boost线程库的开发中。所有接口的设计都...

    c++boost多线程(thread)编程(线程操作,互斥mutex,条件变量)详解

    Boost是由C++标准委员会类库工作组成员发起,致力于为C++开发新的类库的组织。许多C++专家都投身于Boost线程库的开发中。所有接口设计都是从0开始的,并不是C线程API的简单封装。许多C++特性(比如构造函数析构函数函数对象function object)和模板)都被使用在其中以使接口更加灵活。现

在的版本可以在POSIX,Win32Macintosh Carbon平台下工作。

1 boost库创建线程

    就像std::fstream关于boost数字转字符串的问题 iis7站长之家就代表一个文件一样,boost::thread类就代表一个可执行的线程。缺省构造函数创建一个代表当前执行线程的实 例。一个重载的构

造函数以一个不需任何参数的函数对象作为参数,并且没有返回值。这个构造函数创建一个新的可执行线程,它调用了那个函数对象。

   起先,大家认为传统C创建线程的方法似乎比这样的设计更有用,因为C创建线程的时候会传入一个void*指针,通过这种方法就可以传入数据。然而, 由

于Boost线程库是使用函数对象来代替函数指针,那么函数对象本身就可以携带线程所需的数据。这种方法更具灵活性,也是类型安全type- safe)的。当和

Boost.Bind这样的功能库一起使用时,这样的方法就可以让你传递任意数量的数据给新建的线程。目前,由Boost线程库创建的线程对象功能还不是很强大。事

实上它只能做两项操作。线程对象可以方便使用==和!=进行比较来确定它们是否是代表 同一个线程;你还可以调用boost::thread::join来等待线程执行完毕

其他一些线程库可以让你对线程做一些其他操作(比如设置优先级,甚 至是取消线程)。然而,由于要在普遍适用(portable)的接口中加入这些操作不是

简单的事,目前仍在讨论如何将这些操组加入到Boost线程库中。


boost库多线程(Thread)用法举例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;
}


2 boost库多线程中的互斥体

   任何写过多线程程序的人都知道避免不同线程同时访问共享区域的重要性。如果一个 线程要改变共享区域中某个数据,而与此同时另一线程正在读这个数

据,那么结果将是未定义的。为了避免这种情况的发生就要使用一些特殊的原始类型和操作。其中最基本的就是互斥体(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++保证了析构函数一定会被调用,所以即使是 有异常抛出,互

斥体也总是会被正确的解锁。这种方法保证正确的使用互斥体。然而,有一点必须注意:尽管Scope Lock模式可以保证互斥体被解锁,但是它并没有保证在异常抛出之后贡献资源仍是可用的。所以就像执行单线程程序一样,必须保证异常不会导致程序状态异 常。另外,这个已经上锁的对象不能传递给另一个线程,因为它们维护的状态并没有禁止这样做。

    例2给出了一个使用boost::mutex的最简单的例子。例子中共创建了两个新的线程,每个线程都有10次循环,在std::cout打印出线程id和当前循环的次数,而main函数等待这两个线程执行完才结束。std::cout就是共享资源,所以每一个线程都使用一个全局互斥体来保 证同时只有一个线程能向它写入。

   许多读者可能已经注意到例2中传递数据给线程还必须的手工写一个函数。尽管这个例子很简单,如果每一次都要写这样的代码实在是让人厌烦的事。 别急

,有一种简单的解决办法。函数库允许你通过将另一个函数绑定,并传入调用时需要的数据来创建一个新的函数。 例3向你展示了如何使用Boost.Bind库来

简化List2中的代码,这样就不必手工写这些函数对象了。


    boost库多线程(Thread)互斥体(mutex)用法举例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;
}



boost库多线程(Thread)互斥体(mutex)用法举例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;
}



3 boost库多线程之条件变量

   有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没 有数据就必须等待数据被压栈。这种情况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。

   条件变量的使用总是和互斥体及共享资源联系在一起的。线程首先锁住互斥体,然后检验共享资源的状态是否处于可使用的状态。如果不是,那么线程就要等 待条件变量。要指向这样的操作就必须在等待的时候将互斥体解锁,以便其他线程可以访问共享资源并改变其状态。它还得保证从等到得线程返回时互斥体是被上锁 得。当另一个线程改变了共享资源的状态时,它就要通知正在等待条件变量得线程,并将之返回等待的线程。

  例4是一个使用了boost::condition的简单例子。有一个实现了有界缓存区的类和一个固定大小的先进先出的容器。由于使用了互斥 体boost::mutex,这个

缓存区是线程安全的。putget使用条件变量来保证线程等待完成操作所必须的状态。有两个线程被创建,一个在 buffer中放入100个整数,另一个将它们从

buffer中取出。这个有界的缓存一次只能存放10个整数,所以这两个线程必须周期性的等待另一个线 程。为了验证这一点,put和get在std::cout中输出诊断

语句。最后,当两个线程结束后,main函数也就执行完毕了。


  boost库多线程(Thread)添加变量用法例4


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



4 boost库多线程(Thread)之线程局部存储

    大多数函数都不是可重入的。这也就是说在某一个线程已经调用了一个函数时,如果你再调用同一个函数,那么这样是不安全的。一个不可重入的函数通过连续的调 用来保存静态变量或者是返回一个指向静态数据的指针。 举例来说,std::strtok就是不可重入的,因为它使用静态变量来保存要被分割符号字符串。

    有两种方法可以让不可重用的函数变成可重用的函数。第一种方法就是改变接口,用指针或引用代替原先使用静态数据的地方。比方说,POSIX定义了 strok_r,std::strtok中的一个可重入的变量,它用一个额外的char**参数来代替静态数据。这种方法很简单,而且提供了可能的最佳效 果。但是这样必须改变公共接口,也就意味着必须改代码。另一种方法不用改变公有接口,而是用本地存储线程(thread local storage)来代替静态数据(有时也被成为特殊线程存储,thread-specific storage)。

    Boost线程库提供了智能指针boost::thread_specific_ptr访问本地存储线程。每一个线程第一次使用这个智能指针的实 例时,它的初值是NULL,所以必须要先检查这个它的只是否为空,并且为它赋值。Boost线程库保证本地存储线程中保存的数据会在线程结束后被清除。

     例5是一个使用boost::thread_specific_ptr的简单例子。其中创建了两个线程来初始化本地存储线程,并有10次循 环,每一次都会增加智能指针指向的值,并将其输出到std::cout上(由于std::cout是一个共享资源,所以通过互斥体进行同步)。main线 程等待这两个线程结束后就退出。从这个例子输出可以明白的看出每个线程都处理属于自己的数据实例,尽管它们都是使用同一个 boost::thread_specific_ptr。


   boost库多线程线程局部存储例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;
}



5 boost库多线程(Thread)编程知识点总结

头文件:#include<boost/thread.hpp>

1) 线程创建,管理

thread类

(1)线程构造函数:

   boost::thread(Callable func);

(2)带参数函数对应的线程构造函数:

   boost::thread(F f,A1 a1,A2 a2,...); //目前最多支持带九个额外参数(a1 到 a9 )的f。

(3)成员函数join()

   阻塞调用当前线程的线程,直到当前线程执行完毕。

(4)成员函数get_id()

   返回当前线程的ID。

(5)成员函数hardware_concurrency()

   返回当前线程数。

独立于thread类的this_thread命名空间

(1)boost::this_thread::get_id

 同上get_id

(2)boost::this_thread::sleep

 将调用线程挂起指定时间。例:boost::this_thread::sleep(boost::posix_time::seconds(seconds));

2) 同步

  使用线程同时执行几个函数时,访问共享资源(全局变量等)时要考虑同步,同步所需工作如下:

(1)互斥量

   互斥的原则是当线程拥有共享资源时防止其他线程夺取其所有权,一旦释放,其他线程可以取得所有权,这将导致线程等待另一个线程处理完一些操作,释放互斥量的所有权。

独占式互斥量:boost::mutex ,并发调用成员函数lock(),unlock()

multiple-reader / single-writer互斥量:boost::shared_mutex,并发调用成员函数lock(),unlock()

(2)更多功能的锁

boost::unique_lock 独占锁

boost::shared_lock multiple-reader / single-writer锁

3) 死锁

  死锁主要发生在有多个依赖锁存在时,会在一个线程试图与另一个线程以相反的顺序锁住互斥量。如何避免死锁:

(1)对共享资源操作前,一定要获得锁;

(2)完成操作后,一定要释放锁;

(3)尽量短时占用锁;

(4)如果有多锁,如获得顺序是ABC,则释放顺序也是ABC。

(5)如果线程错误返回,应该释放他所获得的锁。


    您可能感兴趣的文章:

相关文章推荐:


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3