boost多线程库使用指南
来源:互联网 发布:淘宝详情页和主图视频 编辑:程序博客网 时间:2024/05/21 01:56
一、基本使用
头文件
class thread;
class thread_group;
}
1、thread
thread的构造形式为explicit thread(const boost::function0&);
如果你对boost::function不熟,那么我听我简短的介绍一下:
boost::function0可以简单看为:一个无返回(返回void),无参数的函数。
这里的函数也可以是类重载operator()构成的函数。
举例来说如下形都可以转化为function0。
2 {
3 }
4
5 struct Run
6 {
7 void operator ()( void ) {}
8 } ;
9
只要带参数构造一个thread实例那么就是构建了一个线程,相当的方便快捷。
于是有了我们第一个例子:
例一:
2 #include < iostream >
3
4 struct Run
5 {
6 void operator ()( void )
7 {
8
9 std::cout << __FUNCTION__ << std::endl;
10 }
11 } ;
12 void run( void )
13 {
14 std::cout << __FUNCTION__ << std::endl;
15 }
16
17 int main( int argc, char * argv[])
18 {
19 Run r;
20 boost::thread thrd(r);
21 boost::thread thrdb(run);
22 return 0 ;
23 }
24
25
运行后发生了什么?线程起动了,但一闪而过,结果都没能输出全就运行结束了。
那该怎么办呢?
答:使用thread::join,当join后的thread在该线程未执行结束会一直处于阻塞状态。
改下例子中主程序main为
2 Run r;
3 boost::thread thrd(r);
4 boost::thread thrdb(run);
5 thrd.join();
6 thrdb.join();
7 return 0 ;
8 }
看到结果了,但似乎线程有点不同步,呃。。暂时放在一旁吧。
什么?你觉得void(void)的函数连变量入口都没,传值不方便?其实你错了,当你用会了boost::bind,会发现函数有多少参数都不是问题,都可以轻松bind为void(void)形式。我几乎可以说boost::thread最基本的的使用就是boost::thread+boost::function+boost::bind的结合。
2、thread_group
大家一定注意到除了thread还有thread_group,顾名思义,thread_group就是thread的group,看看main主程序有点烦琐,引入thread_group看看效果
先认识下thread_group的成员函数:
2 void add_thread(thread * ); // 加入一个已存在的线程
3 void remove_thread(thread * ); // 移除一个线程
4 void join_all(); // 全部等待结束
很清晰,改造开始
2 Run r;
3 boost::thread_group grp;
4 grp.create_thread(r); // 使用create_thread
5 grp.add_thread( new boost::thread(run)); // 使用add_thread
6 grp.join_all();
7 return 0 ;
8 }
运行,结果完全一样。
注意:当thread_group析构时会自动delete已加入的thread
例
2 boost::thread_group grp;
3 boost::thread * thrd = grp.create_thread(r);
4 grp.join_all();
5 delete thrd;
6 } // 错误, grp在析构时对已删除的thrd再进行删除
若要手动管理,可改为:
2 Run r;
3 boost::thread_group grp;
4 boost::thread * thrd = grp.create_thread(r);
5 grp.join_all();
6 grp.remove_thread(thrd); // 把thrd移出grp
7 delete thrd;
8 return 0 ;
9 }
好了,
usidc52010-09-22 00:21
一、创建一个线程
创建线程
boost::thread myThread(threadFun);
需要注意的是:参数可以是函数对象或者函数指针。并且这个函数无参数,并返回void类型。
当一个thread执行完成时,这个子线程就会消失。注意这个线程对象不会消失,它仍然是一个还处在它的生存期的C++对象。同理,当对一个堆上的线程对象的指针调用delete时候,线程对象被销毁,操作系统的线程并不能保证就消失。
放弃时间片
boost::thread::yield();
当前线程放弃余下的时间片。
等待一个线程
myThread.join();
调用这个方法的线程进入wait状态,直到myThread代表的线程完成为止。如果它不结束的话,join方法就不会返回。join是一个等待子线程结束的最好的方法。如果主程序不调用join方法而直接结束,它的子线程有可能没有执行完成,但是所有的子线程也随之退出。不调用join方法,主线程就不会等待它的子线程。
01#include <iostream>02#include <boost/thread/thread.hpp>03#include <boost/thread/xtime.hpp>04 05struct MyThreadFunc {06 void operator( )( ) {07 // Do something long-running...08 }09} threadFun;10 11int main( ) {12 13 boost::thread myThread(threadFun); // Create a thread that starts14 // running threadFun15 16 boost::thread::yield( ); // Give up the main thread's timeslice17 // so the child thread can get some work18 // done.19 20 // Go do some other work...21 22 myThread.join( ); // The current (i.e., main) thread will wait23 // for myThread to finish before it returns24 25}线程组
如果你需要创建几个线程,考虑使用一个线程组对象thread_group来组织它们。一个thread_group对象可以使用多种方法管理线程。首先,可以使用一个指向动态创建的线程对象的指针作为参数来调用add_thread方法,将这个线程加入线程组。也可以直接使用线程组类的create_thread方法,可不先创建线程而直接把线程加入到线程组中。
当线程组对象的析构函数被调用时,它将删除(delete)所有这些通过add_thread方法加入的线程指针。所以,只能将堆上的线程对象指针通过add_thread方法加入线程组。remove_thread方法从线程组删除某个线程的指针,但是我们仍需负责把线程本身内存释放掉。
线程组对象的成员方法join_all方法等待线程组中所有线程结束,才返回。
01boost::thread_group grp;02boost::thread *p = new boost::thread(threadFun);03grp.add_thread(p);04//do something...05grp.remove_thread(p);06 07grp.create_thread(threadFun);08grp.create_thread(threadFun); //Now there are two threads in grp09 10grp.join_all(); //Wait for all threads to finish
二、使资源是线程安全的
保证同一时刻多个线程不会同时修改同一个共享资源,那么这个程序是线程安全的,或者是串行化访问资源的。可以使用mutex类来控制线程的并发问题。
01#include <iostream>02#include <boost/thread/thread.hpp>03#include <string>04 05// A simple queue class; don't do this, use std::queue06template<typename T>07class Queue {08public:09 Queue( ) {}10 ~Queue( ) {}11 12 void enqueue(const T& x) {13 // Lock the mutex for this queue14 boost::mutex::scoped_lock lock(mutex_);15 list_.push_back(x);16 // A scoped_lock is automatically destroyed (and thus unlocked)17 // when it goes out of scope18 }19 20 T dequeue( ) {21 boost::mutex::scoped_lock lock(mutex_);22 23 if (list_.empty( ))24 throw "empty!"; // This leaves the current scope, so the25 T tmp = list_.front( ); // lock is released26 list_.pop_front( );27 return(tmp);28 } // Again: when scope ends, mutex_ is unlocked29 30private:31 std::list<T> list_;32 boost::mutex mutex_;33};34 35Queue<std::string> queueOfStrings;36 37void sendSomething( ) {38 std::string s;39 for (int i = 0; i < 10; ++i) {40 queueOfStrings.enqueue("Cyrus");41 }42}43 44void recvSomething( ) {45 std::string s;46 47 for (int i = 0; i < 10; ++i) {48 try {s = queueOfStrings.dequeue( );}49 catch(...) {}50 }51}52 53int main( ) {54 boost::thread thr1(sendSomething);55 boost::thread thr2(recvSomething);56 57 thr1.join( );58 thr2.join( );59} mutex对象本身并不知道它代表什么,它仅仅是被多个消费者线程使用的资源访问的锁定解锁标志。在某个时刻,只有一个线程可以锁定这个mutex对象,这就阻止了同一时刻有多个线程并发访问共享资源。一个mutex就是一个简单的信号机制。
给mutex加解锁有多种策略,最简单的是使用scoped_lock类,它使用一个mutex参数来构造,并一直锁定这个mutex直到对象被销毁。如果这个正在被构造的mutex已经被别的线程锁定的话,当前线程就会进入wait状态,直到这个锁被解开。
三、读写锁
mutex有一个美中不足,它不区分读和写。线程如果只是进行读操作,mutex强制线程串行化访问资源,效率低。而且这种操作不需要排他性访问。基于这个原因,Boost线程库提供了read_write_mutex。
01#include <iostream>02#include <boost/thread/thread.hpp>03#include <boost/thread/read_write_mutex.hpp>04#include <string>05 06template<typename T>07class Queue {08public:09 Queue( ) : // Use a read/write mutex and give writers priority10 rwMutex_(boost::read_write_scheduling_policy::writer_priority){}11 ~Queue( ) {}12 13 void enqueue(const T& x) {14 // Use a r/w lock since enqueue updates the state15 boost::read_write_mutex::scoped_write_lock writeLock(rwMutex_);16 list_.push_back(x);17 }18 19 T dequeue( ) {20 // Again, use a write lock21 boost::read_write_mutex::scoped_write_lock writeLock(rwMutex_);22 23 if (list_.empty( ))24 throw "empty!";25 T tmp = list_.front( );26 list_.pop_front( );27 return(tmp);28 }29 30 T getFront( ) {31 // This is a read-only operation, so you only need a read lock32 boost::read_write_mutex::scoped_read_lock readLock(rwMutex_);33 if (list_.empty( ))34 throw "empty!";35 return(list_.front( ));36 }37 38private:39 std::list<T> list_;40 boost::read_write_mutex rwMutex_;41};42 43Queue<std::string> queueOfStrings;44 45void sendSomething( ) {46 std::string s;47 48 for (int i = 0; i < 10; ++i) {49 queueOfStrings.enqueue("Cyrus");50 }51}52 53void checkTheFront( ) {54 std::string s;55 56 for (int i = 0; i < 10; ++i) {57 try {s = queueOfStrings.getFront( );}58 catch(...) {}59 }60}61 62int main( ) {63 64 boost::thread thr1(sendSomething);65 boost::thread_group grp;66 67 grp.create_thread(checkTheFront);68 grp.create_thread(checkTheFront);69 grp.create_thread(checkTheFront);70 grp.create_thread(checkTheFront);71 72 thr1.join( );73 grp.join_all( );74} 注意Queue的构造函数中队读写锁rwMutex的初始化。同一时刻,可能有多个读写线程要锁定一个read_write_mutex,而这些锁的调度策略依赖于构造这个mutex时选定的调度策略。Boost库中提供了四种调度策略:
1)reader_priority:等待读锁的线程优先于等待写锁的线程选择使用哪种策略要慎重,因为使用前两种的话可能会导致某些锁始终不能成功,出现饿死的现象。
2)writer_priority:等待写锁的线程优先于等待读锁的线程
3)alternating_single_read:在读锁和写锁之间交替
4)alternating_many_reads:在读锁和写锁之间交替,这个策略将在两个写锁之间使得所有的在这个queue上挂起的读锁都被允许。
usidc52010-09-22 00:22
0 前言
标准C++线程即将到来。CUJ预言它将衍生自Boost线程库,现在就由Bill带领我们探索一下Boost线程库。
就在几年前,用多线程执行程序还是一件非比寻常的事。然而今天互联网应用服务程序普遍使用多线程来提高与多客户链接时的效率;为了达到最大的吞吐 量,事务服务器在单独的线程上运行服务程序;GUI应用程序将那些费时,复杂的处理以线程的形式单独运行,以此来保证用户界面能够及时响应用户的操作。这 样使用多线程的例子还有很多。
但是C++标准并没有涉及到多线程,这让程序员们开始怀疑是否可能写出多线程的C++程序。尽管不可能写出符合标准的多线程程序,但是程序员们还是 会使用支持多线程的操作系统提供的多线程库来写出多线程C++程序。但是这样做至少有两个问题:这些库大部分都是用C语言完成的,如果在C++程序中要使 用这些库就必须十分小心;还有,每一个操作系统都有自己的一套支持多线程的类库。因此,这样写出来得代码是没有标准可循的,也不是到处都适用的(non- portable)。Boost线程库就是为了解决所有这些问题而设计的。
Boost是由C++标准委员会类库工作组成员发起,致力于为C++开发新的类库的组织。现在它已经有近2000名成员。许多库都可以在Boost源码的发布版本中找到。为了使这些类库是线程安全的(thread-safe),Boost线程库被创建了。
许多C++专家都投身于Boost线程库的开发中。所有接口的设计都是从0开始的,并不是C线程API的简单封装。许多C++特性(比如构造函数和 析构函数,函数对象(function object)和模板)都被使用在其中以使接口更加灵活。现在的版本可以在POSIX,Win32和Macintosh Carbon平台下工作。
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">1 创建线程
就像std::fstream类就代表一个文件一样,boost::thread类就代表一个可执行的线程。缺省构造函数创建一个代表当前执行线程的实 例。一个重载的构造函数以一个不需任何参数的函数对象作为参数,并且没有返回值。这个构造函数创建一个新的可执行线程,它调用了那个函数对象。
起先,大家认为传统C创建线程的方法似乎比这样的设计更有用,因为C创建线程的时候会传入一个void*指针,通过这种方法就可以传入数据。然而, 由于Boost线程库是使用函数对象来代替函数指针,那么函数对象本身就可以携带线程所需的数据。这种方法更具灵活性,也是类型安全(type- safe)的。当和Boost.Bind这样的功能库一起使用时,这样的方法就可以让你传递任意数量的数据给新建的线程。
目前,由Boost线程库创建的线程对象功能还不是很强大。事实上它只能做两项操作。线程对象可以方便使用==和!=进行比较来确定它们是否是代表 同一个线程;你还可以调用boost::thread::join来等待线程执行完毕。其他一些线程库可以让你对线程做一些其他操作(比如设置优先级,甚 至是取消线程)。然而,由于要在普遍适用(portable)的接口中加入这些操作不是简单的事,目前仍在讨论如何将这些操组加入到Boost线程库中。
Listing1展示了boost::thread类的一个最简单的用法。 新建的线程只是简单的在std::out上打印“hello,world”,main函数在它执行完毕之后结束。
例1:
#include
#include
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;
}
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">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
这种方法保证正确的使用互斥体。然而,有一点必须注意:尽管Scope Lock模式可以保证互斥体被解锁,但是它并没有保证在异常抛出之后贡献资源仍是可用的。所以就像执行单线程程序一样,必须保证异常不会导致程序状态异 常。另外,这个已经上锁的对象不能传递给另一个线程,因为它们维护的状态并没有禁止这样做。
List2给出了一个使用boost::mutex的最简单的例子。例子中共创建了两个新的线程,每个线程都有10次循环,在std::cout上 打印出线程id和当前循环的次数,而main函数等待这两个线程执行完才结束。std::cout就是共享资源,所以每一个线程都使用一个全局互斥体来保 证同时只有一个线程能向它写入。
许多读者可能已经注意到List2中传递数据给线程还必须的手工写一个函数。尽管这个例子很简单,如果每一次都要写这样的代码实在是让人厌烦的事。 别急,有一种简单的解决办法。函数库允许你通过将另一个函数绑定,并传入调用时需要的数据来创建一个新的函数。 List3向你展示了如何使用Boost.Bind库来简化List2中的代码,这样就不必手工写这些函数对象了。
例2:
#include
#include
#include
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
#include
#include
#include
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;
}
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">3 条件变量
有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没 有数据就必须等待数据被压栈。这种情况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。
条件变量的使用总是和互斥体及共享资源联系在一起的。线程首先锁住互斥体,然后检验共享资源的状态是否处于可使用的状态。如果不是,那么线程就要等 待条件变量。要指向这样的操作就必须在等待的时候将互斥体解锁,以便其他线程可以访问共享资源并改变其状态。它还得保证从等到得线程返回时互斥体是被上锁 得。当另一个线程改变了共享资源的状态时,它就要通知正在等待条件变量得线程,并将之返回等待的线程。
List4是一个使用了boost::condition的简单例子。有一个实现了有界缓存区的类和一个固定大小的先进先出的容器。由于使用了互斥 体boost::mutex,这个缓存区是线程安全的。put和get使用条件变量来保证线程等待完成操作所必须的状态。有两个线程被创建,一个在 buffer中放入100个整数,另一个将它们从buffer中取出。这个有界的缓存一次只能存放10个整数,所以这两个线程必须周期性的等待另一个线 程。为了验证这一点,put和get在std::cout中输出诊断语句。最后,当两个线程结束后,main函数也就执行完毕了。
#include
#include
#include
#include
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;
}
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">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。
例5:
#include
#include
#include
#include
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;
}
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">5 仅运行一次的例程
还有一个问题没有解决:如何使得初始化工作(比如说构造函数)也是线程安全的。比方说,如果一个引用程序要产生唯一的全局的对象,由于实例化顺序的问题, 某个函数会被调用来返回一个静态的对象,它必须保证第一次被调用时就产生这个静态的对象。这里的问题就是如果多个线程同时调用了这个函数,那么这个静态对 象的构造函数就会被调用多次,这样错误产生了。
解决这个问题的方法就是所谓的“一次实现”(once routine)。“一次实现”在一个应用程序只能执行一次。如果多个线程想同时执行这个操作,那么真正执行的只有一个,而其他线程必须等这个操作结束。 为了保证它只被执行一次,这个routine由另一个函数间接的调用,而这个函数传给它一个指针以及一个标志着这个routine是否已经被调用的特殊标 志。这个标志是以静态的方式初始化的,这也就保证了它在编译期间就被初始化而不是运行时。因此也就没有多个线程同时将它初始化的问题了。Boost线程库 提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏 BOOST_ONCE_INIT。
List6是一个使用了boost::call_once的例子。其中定义了一个静态的全局整数,初始值为0;还有一个由 BOOST_ONCE_INIT初始化的静态boost::once_flag实例。main函数创建了两个线程,它们都想通过传入一个函数调用 boost::call_once来初始化这个全局的整数,这个函数是将它加1。main函数等待着两个线程结束,并将最后的结果输出的到 std::cout。由最后的结果可以看出这个操作确实只被执行了一次,因为它的值是1。
#include
#include
#include
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;
}
'700')this.width='700';if(this.offsetHeight>'700')this.height='700';" http: www.cnblogs.com CuteSoft_Client CuteEditor Load.ashx?type='image&file=anchor.gif"' border="0">); background-repeat: no-repeat no-repeat; ">6 Boost线程库的未来
Boost线程库正在计划加入一些新特性。其中包括boost::read_write_mutex,它可以让多个线程同时从共享区中读取数据,但是一次 只可能有一个线程向共享区写入数据;boost::thread_barrier,它使得一组线程处于等待状态,知道所有得线程都都进入了屏障 区;boost::thread_pool,他允许执行一些小的routine而不必每一都要创建或是销毁一个线程。
Boost线程库已经作为标准中的类库技术报告中的附件提交给C++标准委员会,它的出现也为下一版C++标准吹响了第一声号角。委员会成员对 Boost线程库的初稿给予了很高的评价,当然他们还会考虑其他的多线程库。他们对在C++标准中加入对多线程的支持非常感兴趣。从这一点上也可以看出, 多线程在C++中的前途一片光明。
usidc52010-09-22 00:24
死锁、饿死和竞态条件
1)死锁,是涉及至少2个线程和2个资源的情况。线程A和B,资源X和Y。A锁定了X,而B锁定了Y。此时A和B有彼此想要对方的资源,死锁就出现了。
死锁的预防有两种方法。一种是,通过小心的按照一定的顺序对不同的mutex来加锁。另一种是,使用Boost提供的try_mutex互斥量和scoped_try_lock。或者使用时间锁。scoped_try_lock对try_mutex加锁时,可能成功,也可能失败,但不会阻塞。时间锁则有一个超时时间。
bool dequeue(T& x)
{
boost::try_mutex::scope_try_lock lock(tryMutex_);
if(!lock.locked())
return false;
else{
if (list_.empty())
throw "empty!";
x = list_.front();
list_.pop_front();
return true;
}
}
private:
boost::try_mutex tryMutex_;
2)饿死,如果你正在使用write_priority策略,并且你有很多创建写锁的线程,那么读锁的线程就可能饿死。
3)竞态条件,
if(q.getFront() == "Cyrus"){
str = q.dequeue();
//....
}
这个代码在单线程环境中工作很好,因为q在第一行和第二行代码之间不会被修改。多线程环境中则会出现问题。此为竞态条件。解决的方法是为Queue添加一个成员函数dequeueIfEquals,在函数执行过程中始终锁定互斥量。
四、从一个线程中给另一个线程发送通知
当需要线程等待某个事物时,可以创建一个condition对象,然后通过这个对象来通知那些等待的线程。
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <list>
#include <string>
class Request { /*...*/ };
// A simple job queue class; don't do this, use std::queue
template<typename T>
class JobQueue {
public:
JobQueue( ) {}
~JobQueue( ) {}
void submitJob(const T& x) {
boost::mutex::scoped_lock lock(mutex_);
list_.push_back(x);
workToBeDone_.notify_one( );
}
T getJob( ) {
boost::mutex::scoped_lock lock(mutex_);
workToBeDone_.wait(lock); // Wait until this condition is
// satisfied, then lock the mutex
T tmp = list_.front( );
list_.pop_front( );
return(tmp);
}
private:
std::list<T> list_;
boost::mutex mutex_;
boost::condition workToBeDone_;
};
JobQueue<Request> myJobQueue;
void boss( ) {
for (;;) {
// Get the request from somewhere
Request req;
myJobQueue.submitJob(req);
}
}
void worker( ) {
for (;;) {
Request r(myJobQueue.getJob( ));
// Do something with the job...
}
}
int main( ) {
boost::thread thr1(boss);
boost::thread thr2(worker);
boost::thread thr3(worker);
thr1.join( );
thr2.join( );
thr3.join( );
}
boost::mutex::scoped_lock lock(mutex_);
workToBeDone_.wait(lock);
这两行代码,第一行锁定这个mutex对象。第二行代码解开这个mutex上的锁,然后进行等待或者休眠,直到它的条件得到了满足。这个mutex互斥对象的解锁让其他的线程能够使用这个mutex对象,它们中的某个需要设置这个等待条件,之后通知另外的线程。
notify_all函数,通知那些所有正在等待某个条件变为真的线程,那些线程随后进入运行状态。wait方法做两件事情:它一直等待直到有人在它正等待的condition上调用notify_one或notify_all,然后它就试图锁定相关的mutex。当调用的是notify_all时,尽管多个等待的线程都尽量去获得下一个锁,但谁将获得依赖于这个mutex的类型和使用的优先策略。
一个condition对象能让消费者线程休眠,因此在还没有碰到一个condition时处理器可以去处理别的事情。例如一个web服务器使用一个工作线程池来处理进来的请求。当没有需求进来时,让这些子线程处于等待状态比让它们循环的查询或者睡眠然后偶尔唤醒来检查这个队列,要好很多。
五、只初始化一次共享资源
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/once.hpp>
// Some sort of connection class that should only be initialized once
struct Conn {
static void init( ) {++i_;}
static boost::once_flag init_;
static int i_;
// ...
};
int Conn::i_ = 0;
boost::once_flag Conn::init_ = BOOST_ONCE_INIT;
void worker( ) {
boost::call_once(Conn::init, Conn::init_);
// Do the real work...
}
Conn c; // You probably don't want to use a global, so see the
// next Recipe
int main( ) {
boost::thread_group grp;
for (int i = 0; i < 100; ++i)
grp.create_thread(worker);
grp.join_all( );
std::cout << c.i_ << '\n'; // c.i_ = 1
}
一个共享资源不得不在某个地方被初始化,并且你希望第一次使用这个资源的线程来完成初始化工作。一个once_flag类型和call_once函数能够保证多个线程不会重复的初始化同一个对象。首先,必须使用BOOST_ONCE_INIT宏来初始化这个once_flag对象。boost::once_flag Conn::init_ = BOOST_ONCE_INIT; 之后调用call_once函数,boost::call_once(Conn::init, Conn::init_); 第一个形参是希望被执行一次的初始化函数的地址。
usidc52010-09-22 00:24
六、给线程函数传递一个参数
#include <iostream>
#include <string>
#include <functional>
#include <boost/thread/thread.hpp>
// A typedef to make the declarations below easier to read
typedef void (*WorkerFunPtr)(const std::string&);
template<typename FunT, // The type of the function being called
typename ParamT> // The type of its parameter
struct Adapter {
Adapter(FunT f, ParamT& p) : // Construct this adapter and set the
f_(f), p_(&p) {} // members to the function and its arg
void operator( )( ) { // This just calls the function with its arg
f_(*p_);
}
private:
FunT f_;
ParamT* p_; // Use the parameter's address to avoid extra copying
};
void worker(const std::string& s) {
std::cout << s << '\n';
}
int main( ) {
std::string s1 = "This is the first thread!";
std::string s2 = "This is the second thread!";
boost::thread thr1(Adapter<WorkerFunPtr, std::string>(worker, s1));
boost::thread thr2(Adapter<WorkerFunPtr, std::string>(worker, s2));
thr1.join( );
thr2.join( );
}
使用这个函数适配器类模板,你就可以给线程函数传递参数了。如果你需要传递多个参数,仅需要在这个适配器中增加另一个类型和成员变量。
usidc52010-09-22 00:26最近在做一个消息中间件里面涉及到多线程编程,由于跨平台的原因我采用了boost线程库。在创建线程时遇到了几种线程创建方式现总结如下:
首先看看boost::thread的构造函数吧,boost::thread有两个构造函数:
(1)thread():构造一个表示当前执行线程的线程对象;
(2)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结合起来使用。
usidc52011-07-10 12:43为了取thread调用的函数的返回值,以前要么是弄个函数包起来,用一个外部变量记录返回值,要不就是在类里面加个public的变量做记录,感觉很不地道,而且麻烦
最近看boost的文档,才发现原来1.41和以后的版本的thread库里面已经有相关的东西做这件事了
这个就是 boost::unique_future 和 boost::shared_future.
用起来很方便,下面是示例
#include <boost/thread.hpp>
#include <string>
using namespace std;
using namespace boost;
int calculate_the_answer_to_life_the_universe_and_everything(int par, string aaa)
{
if(aaa == "hello")
return par-1;
else
return par+1;
}
int main(void)
{
int a = 33;
string b = "hello";
boost::packaged_task<int> pt(boost::bind(calculate_the_answer_to_life_the_universe_and_everything, a, b));
boost::unique_future<int> fi=pt.get_future();
boost::thread task(boost::move(pt)); // launch task on a thread
fi.wait(); // wait for it to finish
assert(fi.is_ready());
assert(fi.has_value());
assert(!fi.has_exception());
assert(fi.get_state()==boost::future_state::ready);
// assert(fi.get()==42);
int aaa = fi.get();
return 0;
}
usidc52011-07-17 13:09
Boost.Thread可以使用多线程执行可移植C++代码中的共享数据。它提供了一些类和函数来管理线程本身,还有其它一些为了实现在线程之间同步数据或者提供针对特定单个线程的数据拷贝。
头文件:
#include <boost/thread.hpp>
线程定义
boost::thread 类是负责启动和管理线程。每个boost::thread对象代表一个单独的执行线程,是不可拷贝的。由于它是可以被移动到,所以它们可以被保存到会改变大小的容器中,并且从函数返回。这使得线程创建的详细信息可以被封装到一个函数中。
boost::thread make_thread();
void f()
{
boost::thread some_thread = make_thread();
some_thread.join();
}
启动线程
一个新的线程可以通过传递一个可被调用的类型对象来启动,这个对象可以不需要给构造器参数就被唤醒。对象被拷贝到内存,并且在最新创建的线程上唤醒。如果对象不能被拷贝,boost::ref可以以引用的方式来传递给函数对象。在这种情况下,用户的boost.thread必须确保对象的引用的生命期必须比最新创建的执行线程要长。
struct callable
{
void operator()();
};
boost::thread copies_are_safe()
{
callable x;
return boost::thread(x);
} // x is destroyed, but the newly-created thread has a copy, so this is OK
boost::thread oops()
{
callable x;
return boost::thread(boost::ref(x));
} // x is destroyed, but the newly-created thread still has a reference
// this leads to undefined behaviour
如果你用一个函数或者可调用的对象希望创建一个boost::thread 的实例需要提供一些参数,这些可以通过给它的构造体传递另外的参数来办到。
void find_the_question(int the_answer);
boost::thread deep_thought_2(find_the_question,42);
参数被拷贝到内部线程结构里:如果需要传递一个引用,可以使用boost::Ref,只是对可调用对象的引用。
没有指定限制传递的额外参数的数量。
线程中的异常
如果传入到boost::thread构造体的函数或者可调用的对象抛出了一个异常而且唤醒它的不是boosst::thread_interrupted类型,std::terminate()会被调用来结束这个线程。
等待
当代表一个执行线程的线程对象被破坏时,这个线程变成分离的,一旦它被分离,将会继续执行知道唤醒由构造体提供的函数或者可调用对象执行结束,或者程序已经结束。线程也可以通过调用detach()成员函数来显示的分离。在这种情形下,线程对象将不在表示一个当前分离的线程,而是一个非线程体。
为了等待一个线程执行完毕,必须使用join()和timed_join()成员函数。join()会阻塞调用的线程直到线程结束。如果线程刚刚执行结束,或者它已经不代表一个线程,join()会立即返回。timed_join()也是类似的,但是调用它如果在指定的时间流逝后线程仍然没有结束它也会返回。
中断
一个正在运行的线程可以通过调用相应的boost::thread对象的interrupt()成员函数来中断。当被中断的线程在下次执行一个指定的中断点(或者如果它在同时执行一个的时候被锁)并开启中断时,在被中断的线程中就会抛出一个boost::thread_interrupted异常。如果没有被捕获,这会导致结束被中断线程的执行。与其他异常一样,栈就会被释放,自动存储期对象的析构体将会被执行。
如果一个线程需要避免被中断,可以创建一个boost::this_thread::disable_interruption实例。这个类的对象在构造体创建线程的时候禁止了中断,可以在析构体调用之前的任意地方恢复允许中断。
void f()
{
// interruption enabled here
{
boost::this_thread::disable_interruption di;
// interruption disabled
{
boost::this_thread::disable_interruption di2;
// interruption still disabled
} // di2 destroyed, interruption state restored
// interruption still disabled
} // di destroyed, interruption state restored
// interruption now enabled
}
通过构造一个boost::this_thread::restore_interruption实例可以临时转换一个boost::this_thread::disable_interruption实例造成的影响,只要在有问题的地方传递一个boost::this_thread::disable_interruption对象。这会重新恢复中断状态到当boost::this_thread_diable_interruption对象被构造时,并且在次禁止中断当boost::this_thread::restore_interruption对象被破坏时。
void g()
{
// interruption enabled here
{
boost::this_thread::disable_interruption di;
// interruption disabled
{
boost::this_thread::restore_interruption ri(di);
// interruption now enabled
} // ri destroyed, interruption disable again
} // di destroyed, interruption state restored
// interruption now enabled
}
我们可以通过调用boost::this_thread::interruption_enabled()来查询中断的状态。
预定义的中断点
以下函数当允许中断时可能会抛出boost::thread_interrupted异常。
boost::thread::join()
boost::thread::timed_join()
boost::condition_variable::wait()
boost::condition_variable::timed_wait()
boost::condition_variable_any::wait()
boost::condition_variable_any::timed_wait()
boost::thread::sleep()
boost::this_thread::sleep()
boost::this_thread::interruption_point()
线程ID
boost::thread::id类可以用来标识一个线程。每个运行的执行线程都有一个特有的ID,可以通过对应的boost::thread的get_id()成员函数来获得ID。
usidc52011-09-20 14:21
一、概述
线程是在同一程序同一时间内允许执行不同函数的离散处理队列,这使得在一个长时间进行某种特殊运算的函数在执行时不阻碍其他的函数时变得十分重要。线程实际上允许同时执行两种函数,而这两者不必相互等待。
一旦一个应用程序启动,它仅包含一个默认线程。此线程执行main()函数。在main()中被调用的函数则按这个线程的上下文顺序地执行,这样的程序称为单线程程序。
反之,那些创建新的线程的程序就是多线程程序。他们不仅可以在同一时间执行多个函数,而且这在如今多核盛行的时代显得尤为重要。既然多核允许同时执行多个函数,这就使得对开发人员相应地使用这种处理能力提出了要求。然而线程一直被用来当并发地执行多个函数,开发人员现在不得不仔细地构建应用来支持这种并发。多线程编程知识也因此在多核系统时代变得越来越重要。
本章介绍的是C++ Boost库Boost.Thread,它可以开发独立于平台的多线程应用程序。
二、线程管理
这个库中最重要的一个类就是boost::thread,它在boost/thread.hpp里定义,用来创建一个新线程。下面的示例来说明如何运用它。
view plain
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
void thread()
{
for (int i = 0; i < 5; ++i)
{
wait(1);
std::cout << i << std::endl;
}
}
int main()
{
boost::thread t(thread);
t.join();
}
新建线程里执行的那个函数的名称被传递到 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 用来管理和处理时间的数据。
虽然前面的例子说明了如何等待一个不同的线程,但下面的例子演示了如何通过所谓的中断点让一个线程中断。
view plain
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
void thread()
{
try
{
for (int i = 0; i < 5; ++i)
{
wait(1);
std::cout << i << std::endl;
}
}
catch (boost::thread_interrupted&)
{
}
}
int main()
{
boost::thread t(thread);
wait(3);
t.interrupt();
t.join();
}
在一个线程对象上调用 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 里提供的多种函数的整体概述,下面的例子将会再介绍两个。
view plain
#include
#include
int main()
{
std::cout << boost::this_thread::get_id() << std::endl;
std::cout << boost::thread::hardware_concurrency() << std::endl;
}
使用 boost::this_thread命名空间,能提供独立的函数应用于当前线程,比如前面出现的sleep() 。另一个是 get_id():它会返回一个当前线程的ID号。它也是由 boost::thread 提供的。
boost::thread 类提供了一个静态方法 hardware_concurrency() ,它能够返回基于CPU数目或者CPU内核数目的刻在同时在物理机器上运行的线程数。在常用的双核机器上调用这个方法,返回值为2。 这样的话就可以确定在一个多核程序可以同时运行的理论最大线程数。
三、同步
虽然多线程的使用可以提高应用程序的性能,但也增加了复杂性。如果使用线程在同一时间执行几个函数,访问共享资源时必须相应地同步。一旦应用达到了一定规模,这涉及相当一些工作。本段介绍了Boost.Thread提供同步线程的类。
view plain
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
boost::mutex mutex;
void thread()
{
for (int i = 0; i < 5; ++i)
{
wait(1);
mutex.lock();
std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;
mutex.unlock();
}
}
int main()
{
boost::thread t1(thread);
boost::thread t2(thread);
t1.join();
t2.join();
}
多线程程序使用所谓的互斥对象来同步。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 类也是可以的。
view plain
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
boost::mutex mutex;
void thread()
{
for (int i = 0; i < 5; ++i)
{
wait(1);
boost::lock_guard lock(mutex);
std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;
}
}
int main()
{
boost::thread t1(thread);
boost::thread t2(thread);
t1.join();
t2.join();
}
boost::lock_guard 在其内部构造和析构函数分别自动调用lock() 和 unlock() 。 访问共享资源是需要同步的,因为它显示地被两个方法调用。 boost::lock_guard 类是另一个出现在我之前第2个系列智能指针单元的RAII用语。
除了boost::mutex 和 boost::lock_guard 之外,Boost.Thread也提供其他的类支持各种同步。其中一个重要的就是 boost::unique_lock ,相比较 boost::lock_guard 而言,它提供许多有用的方法。
view plain
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
boost::timed_mutex mutex;
void thread()
{
for (int i = 0; i < 5; ++i)
{
wait(1);
boost::unique_lock lock(mutex, boost::try_to_lock);
if (!lock.owns_lock())
lock.timed_lock(boost::get_system_time() + boost::posix_time::seconds(1));
std::cout << "Thread " << boost::this_thread::get_id() << ": " << i <unlock();
}
}
int main()
{
boost::thread t1(thread);
boost::thread t2(thread);
t1.join();
t2.join();
}
上面的例子用不同的方法来演示 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 类型的值意味着成功是否。
usidc52011-09-20 14:22
虽然boost::mutex 提供了lock和try_lock两个方法,但是 boost::timed_mutex 只支持 timed_lock,这就是上面示例那么使用的原因。如果不用timed_lock的话,也可以像以前的例子那样用 boost::mutex。
就像 boost::lock_guard 一样, boost::unique_lock 的析构函数也会相应地释放互斥量。此外,可以手动地用 unlock() 释放互斥量。也可以像上面的例子那样,通过调用 release() 解除boost::unique_lock 和互斥量之间的关联。然而在这种情况下,必须显式地调用 unlock() 方法来释放互斥量,因为 boost::unique_lock 的析构函数不再做这件事情。
boost::unique_lock 这个所谓的独占锁意味着一个互斥量同时只能被一个线程获取。其他线程必须等待,直到互斥体再次被释放。 除了独占锁,还有非独占锁。 Boost.Thread里有个 boost::shared_lock 的类提供了非独占锁。 正如下面的例子,这个类必须和 boost::shared_mutex 型的互斥量一起使用。
view plain
#include
#include
#include
#include
#include
void wait(int seconds)
{
boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}
boost::shared_mutex mutex;
std::vector random_numbers;
void fill()
{
std::srand(static_cast(std::time(0)));
for (int i = 0; i < 3; ++i)
{
boost::unique_lock lock(mutex);
random_numbers.push_back(std::rand());
lock.unlock();
wait(1);
}
}
void print()
{
for (int i = 0; i < 3; ++i)
{
wait(1);
boost::shared_lock lock(mutex);
std::cout << random_numbers.back() << std::endl;
}
}
int sum = 0;
void count()
{
for (int i = 0; i < 3; ++i)
{
wait(1);
boost::shared_lock lock(mutex);
sum += random_numbers.back();
}
}
int main()
{
boost::thread t1(fill);
boost::thread t2(print);
boost::thread t3(count);
t1.join();
t2.join();
t3.join();
std::cout << "Sum: " << sum << std::endl;
}
boost::shared_lock 类型的非独占锁可以在线程只对某个资源读访问的情况下使用。一个线程修改的资源需要写访问,因此需要一个独占锁。 这样做也很明显:只需要读访问的线程不需要知道同一时间其他线程是否访问。 因此非独占锁可以共享一个互斥体。
在给定的例子里, print() 和 count() 都可以只读访问 random_numbers 。 虽然 print() 函数把 random_numbers 里的最后一个数写到标准输出,count() 函数把它统计到 sum 变量。 由于没有函数修改 random_numbers,所有的都可以在同一时间用 boost::shared_lock 类型的非独占锁访问它。
在 fill() 函数里,需要用一个 boost::unique_lock 类型的非独占锁,因为它插入了一个新的随机数到 random_numbers。在 unlock() 显式地调用 unlock() 来释放互斥量之后, fill() 等待了一秒。 相比于之前的那个样子,在 for 循环的尾部调用 wait() 以保证容器里至少存在一个随机数,可以被print() 或者 count() 访问。 对应地,这两个函数在 for 循环的开始调用了 wait() 。
考虑到在不同的地方每个单独地调用 wait() ,一个潜在的问题变得很明显:函数调用的顺序直接受CPU执行每个独立进程的顺序决定。 利用所谓的条件变量,可以同步哪些独立的线程,使数组的每个元素都被不同的线程立即添加到 random_numbers 。
view plain
#include
#include
#include
#include
#include
boost::mutex mutex;
boost::condition_variable_any cond;
std::vector random_numbers;
void fill()
{
std::srand(static_cast(std::time(0)));
for (int i = 0; i < 3; ++i)
{
boost::unique_lock lock(mutex);
random_numbers.push_back(std::rand());
cond.notify_all();
cond.wait(mutex);
}
}
void print()
{
std::size_t next_size = 1;
for (int i = 0; i < 3; ++i)
{
boost::unique_lock lock(mutex);
while (random_numbers.size() != next_size)
cond.wait(mutex);
std::cout << random_numbers.back() << std::endl;
++next_size;
cond.notify_all();
}
}
int main()
{
boost::thread t1(fill);
boost::thread t2(print);
t1.join();
t2.join();
}
这个例子的程序删除了wait() 和count() 。线程不用在每个循环迭代中等待一秒,而是尽可能快地执行。此外,没有计算总额;数字完全写入标准输出流。
为确保正确地处理随机数,需要一个允许检查多个线程之间特定条件的条件变量来同步不每个独立的线程。
正如上面所说,fill() 函数用在每个迭代产生一个随机数,然后放在 random_numbers 容器中。 为了防止其他线程同时访问这个容器,就要相应得使用一个排它锁。 不是等待一秒,实际上这个例子却用了一个条件变量。调用 notify_all() 会唤醒每个哪些正在分别通过调用wait() 等待此通知的线程。
通过查看 print() 函数里的 for 循环,可以看到相同的条件变量被 wait() 函数调用了。 如果这个线程被 notify_all() 唤醒,它就会试图这个互斥量,但只有在 fill() 函数完全释放之后才能成功。
这里的窍门就是调用 wait() 会释放相应的被参数传入的互斥量。 在调用 notify_all()后, fill() 函数会通过 wait() 相应地释放线程。 然后它会阻止和等待其他的线程调用 notify_all() ,一旦随机数已写入标准输出流,这就会在 print() 里发生。
注意到在 print() 函数里调用 wait() 事实上发生在一个单独 while 循环里。 这样做的目的是为了处理在 print() 函数里第一次调用 wait() 函数之前随机数已经放到容器里。 通过比较 random_numbers 里元素的数目与预期值,发现这成功地处理了把随机数写入到标准输出流。
四、线程本地存储
线程本地存储(TLS)是一个只能由一个线程访问的专门的存储区域。 TLS的变量可以被看作是一个只对某个特定线程而非整个程序可见的全局变量。 下面的例子显示了这些变量的好处。
view plain
#include
#include
#include
#include
void init_number_generator()
{
static bool done = false;
if (!done)
{
done = true;
std::srand(static_cast(std::time(0)));
}
}
boost::mutex mutex;
void random_number_generator()
{
init_number_generator();
int i = std::rand();
boost::lock_guard lock(mutex);
std::cout << i << std::endl;
}
int main()
{
boost::thread t[3];
for (int i = 0; i < 3; ++i)
t = boost::thread(random_number_generator);
for (int i = 0; i < 3; ++i)
t.join();
}
该示例创建三个线程,每个线程写一个随机数到标准输出流。 random_number_generator() 函数将会利用在C++标准里定义的 std::rand() 函数创建一个随机数。 但是用于 std::rand() 的随机数产生器必须先用 std::srand() 正确地初始化。 如果没做,程序始终打印同一个随机数。
随机数产生器,通过 std::time() 返回当前时间, 在 init_number_generator() 函数里完成初始化。 由于这个值每次都不同,可以保证产生器总是用不同的值初始化,从而产生不同的随机数。因为产生器只要初始化一次, init_number_generator() 用了一个静态变量 done 作为条件量。
如果程序运行了多次,写入的三分之二的随机数显然就会相同。事实上这个程序有个缺陷:std::rand所用的产生器必须被各个线程初始化。因此init_number_generator() 的实现实际上是不对的,因为它只调用了一次std::srand()。使用TLS,这一缺陷可以得到纠正。
view plain
#include
#include
#include
#include
void init_number_generator()
{
static boost::thread_specific_ptr tls;
if (!tls.get())
tls.reset(new bool(false));
if (!*tls)
{
*tls = true;
std::srand(static_cast(std::time(0)));
}
}
boost::mutex mutex;
void random_number_generator()
{
init_number_generator();
int i = std::rand();
boost::lock_guard lock(mutex);
std::cout << i << std::endl;
}
int main()
{
boost::thread t[3];
for (int i = 0; i < 3; ++i)
t = boost::thread(random_number_generator);
for (int i = 0; i ()都被被重载以方便使用。这个例子用*tls检查这个条件当前是true还是false。再根据当前的条件,随机数生成器决定是否初始化。
正如所见,boost::thread_specific_ptr允许为当前进程保存一个对象的地址,然后只允许当前进程获得这个地址。然而,当一个线程已经成功保存这个地址,其他的线程就会可能就失败。
如果程序正在执行时,它可能会令人感到奇怪:尽管有了TLS的变量,生成的随机数仍然相等。 这是因为,三个线程在同一时间被创建,从而造成随机数生成器在同一时间初始化。如果该程序执行了几次,随机数就会改变,这就表明生成器初始化正确了。
usidc52011-10-18 18:09
多线程具有良好的并发处理能力,以及多核处理器的良好利用,
单线程程序运行期间由于没有线程切换,所以在单处理环境下有着高性能,相比较多线程服务在共享资源方面的操作可以避免锁操作及相关拷贝,本文使用一个map表说明差异,假设key为int类型,而值为string,那么单线程版本比较简单,代码如下:
C/C++ code
boost::unordered_map<int,string> map;
....
string *Get(int key)
{
MapInt::iterator it = m_map_int.find(key); //查找迭代器
return (it == m_map_int.end()) ? NULL : &it->second; //值判断
}
而多线程版本很多人马上想到的可能是加锁,那么实现想当然有可能就是这样:
C/C++ code
boost::unordered_map<int,string> map;
boost::shared_mutex <m_mutex>; //锁定义
....
string *Get(int key)
{
boost::shared_lock lock( m_mutex ); //这边定义为读锁
MapInt::iterator it = m_map_int.find(key);
return (it == m_map_int.end()) ? NULL : &it->second;
}
但是这样仍然有问题,上面的函数返回了一个string的指针,这个指针在外部调用map表中的值,
这是非线程安全的,换句话说这个锁定义是无效的,多线程中的标准做法如下:
C/C++ code
boost::unordered_map<int,string> map;
boost::shared_mutex <m_mutex>; //锁定义
....
string Get(int key)
{
boost::shared_lock lock( m_mutex ); //这边定义为读锁
MapInt::iterator it = m_map_int.find(key);
return (it == m_map_int.end()) ? NULL : it->second;
}
改进后的多线程map表的读访问直接返回了一个值的拷贝string对象,这样保证了线程的绝对安全,但又面临另一个问题,值拷贝是低效的,特别当string对象非常庞大时,这个过程将变得十分漫长。
如何解决读锁的问题呢,一种比较可行的办法就是在写入时记录值的hash值(保证写入时的线程安全),或者说是一个校验码,这样在读取时就可以无需加锁,只需判断校验码的正确性即可,简称“读时校验”,示意代码:
C/C++ code
struct value
{
string val;
int hash;
};
boost::unordered_map<int,value> map;
....
void Get(int key,string &str)
{
MapInt::iterator it = m_map_int.find(key);
str = it->second.val;
//这里用到"hash"函数得到值的hash值与写入时的hash值做比较,当无法匹配时说明其它线程正在写操作,这时可以适当"等待"后重新读取。
while(hash(str) != it->second.hash)
{
sleep(1);
it = m_map_int.find(key);
str = it->second.val;
}
}
经过上述改进后,性能上有了较大提升,但仍然有可能读取到“非正常”数据,因为hash值是可碰撞的并“欺骗”了校验,虽然这个可能性极低,但在一些对数据安全性要求较高的场合就显得特别谨慎了,所以追求性能的同时也会付出额外的代价。
在一些使用简单锁机制的服务,如Memcached就会有类似的问题,使用时要注意在应用层做好防范工作。
总结,相对单线程,在多线程程序中,需要额外的锁开销(将近500ns)以及很多的拷贝工作,这使得多线程程序在某些情况下要比单线程慢得多(Memcached早期版本这样典型的追求性能的服务中就只使用单线程),所以当我们选择单线程或多线程程序进行开发时,非常有必要考虑这样类似的问题。
usidc52011-10-28 20:43C++多线程开发是一个复杂的事情,mfc下提供了CWinThread类,和AfxBeginThread等等函数,但是在使用中会遇到很多麻烦事情,例如线程之间参数传递的问题,我们一般都是把参数new一个结构体,传递给子线程,然后释放的工作交给了子线程,这样不可避免会有内存泄漏的危险,例如线程关闭的问题,我们一般用WaitForSingleObject来完成线程关闭工作,但是这个函数并不一定保证线程能收到要关闭的信号,这样父亲已经退出工作了,子线程还在工作,程序也会有潜在的危险。
所以我已经慢慢不再用这套线程机制了,boost标准stl库的出现,让我眼前一亮,boost所推行的简洁代码概念和模板概念,让我有了清风扑面的感觉,本文将介绍如何使用boost::thread来取代不太安全的MFC线程编程。
本文所牵涉到的源码下载地址:
[url]http://download.csdn.net/source/618903[/url]
基础篇主要是汇集和转载一些已有网文,让初学者入门boost::thread.
一. 安装 原地址http://www.douban.com/group/topic/2494650/
1.下载boost_1_34_1压缩文件,解压缩到d:/boost_1_34_1/目录下
二.boost::thread入门 原文http://www.stlchina.org/twiki/bin/view.pl/Main/BoostThread
1 创建线程
就像std::fstream类就代表一个文件一样,boost::thread类就代表一个可执行的线程。缺省构造函数创建一个代表当前执行线程的实例。一个重载的构造函数以一个不需任何参数的函数对象作为参数,并且没有返回值。这个构造函数创建一个新的可执行线程,它调用了那个函数对象。
起先,大家认为传统C创建线程的方法似乎比这样的设计更有用,因为C创建线程的时候会传入一个void*指针,通过这种方法就可以传入数据。然而,由于Boost线程库是使用函数对象来代替函数指针,那么函数对象本身就可以携带线程所需的数据。这种方法更具灵活性,也是类型安全(type-safe)的。当和Boost.Bind这样的功能库一起使用时,这样的方法就可以让你传递任意数量的数据给新建的线程。
目前,由Boost线程库创建的线程对象功能还不是很强大。事实上它只能做两项操作。线程对象可以方便使用==和!=进行比较来确定它们是否是代表同一个线程;你还可以调用boost::thread::join来等待线程执行完毕。其他一些线程库可以让你对线程做一些其他操作(比如设置优先级,甚至是取消线程)。然而,由于要在普遍适用(portable)的接口中加入这些操作不是简单的事,目前仍在讨论如何将这些操组加入到Boost线程库中。
Listing1展示了boost::thread类的一个最简单的用法。 新建的线程只是简单的在std::out上打印“hello,world”,main函数在它执行完毕之后结束。
- #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 互斥体
任何写过多线程程序的人都知道避免不同线程同时访问共享区域的重要性。如果一个线程要改变共享区域中某个数据,而与此同时另一线程正在读这个数据,那么结果将是未定义的。为了避免这种情况的发生就要使用一些特殊的原始类型和操作。其中最基本的就是互斥体(mutex,mutual exclusion的缩写)。一个互斥体一次只允许一个线程访问共享区。当一个线程想要访问共享区时,首先要做的就是锁住(lock)互斥体。如果其他的线程已经锁住了互斥体,那么就必须先等那个线程将互斥体解锁,这样就保证了同一时刻只有一个线程能访问共享区域。
互斥体的概念有不少变种。Boost线程库支持两大类互斥体,包括简单互斥体(simple mutex)和递归互斥体(recursive mutex)。如果同一个线程对互斥体上了两次锁,就会发生死锁(deadlock),也就是说所有的等待解锁的线程将一直等下去。有了递归互斥体,单个线程就可以对互斥体多次上锁,当然也必须解锁同样次数来保证其他线程可以对这个互斥体上锁。
在这两大类互斥体中,对于线程如何上锁还有多个变种。一个线程可以有三种方法来对一个互斥体加锁:
- 一直等到没有其他线程对互斥体加锁。
- 如果有其他互斥体已经对互斥体加锁就立即返回。
- 一直等到没有其他线程互斥体加锁,直到超时。
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模式可以保证互斥体被解锁,但是它并没有保证在异常抛出之后贡献资源仍是可用的。所以就像执行单线程程序一样,必须保证异常不会导致程序状态异常。另外,这个已经上锁的对象不能传递给另一个线程,因为它们维护的状态并没有禁止这样做。
List2给出了一个使用boost::mutex的最简单的例子。例子中共创建了两个新的线程,每个线程都有10次循环,在std::cout上打印出线程id和当前循环的次数,而main函数等待这两个线程执行完才结束。std::cout就是共享资源,所以每一个线程都使用一个全局互斥体来保证同时只有一个线程能向它写入。
许多读者可能已经注意到List2中传递数据给线程还必须的手工写一个函数。尽管这个例子很简单,如果每一次都要写这样的代码实在是让人厌烦的事。别急,有一种简单的解决办法。函数库允许你通过将另一个函数绑定,并传入调用时需要的数据来创建一个新的函数。 List3向你展示了如何使用Boost.Bind库来简化List2中的代码,这样就不必手工写这些函数对象了。
- #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.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 条件变量
有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。
条件变量的使用总是和互斥体及共享资源联系在一起的。线程首先锁住互斥体,然后检验共享资源的状态是否处于可使用的状态。如果不是,那么线程就要等待条件变量。要指向这样的操作就必须在等待的时候将互斥体解锁,以便其他线程可以访问共享资源并改变其状态。它还得保证从等到得线程返回时互斥体是被上锁得。当另一个线程改变了共享资源的状态时,它就要通知正在等待条件变量得线程,并将之返回等待的线程。
List4是一个使用了boost::condition的简单例子。有一个实现了有界缓存区的类和一个固定大小的先进先出的容器。由于使用了互斥体boost::mutex,这个缓存区是线程安全的。put和get使用条件变量来保证线程等待完成操作所必须的状态。有两个线程被创建,一个在buffer中放入100个整数,另一个将它们从buffer中取出。这个有界的缓存一次只能存放10个整数,所以这两个线程必须周期性的等待另一个线程。为了验证这一点,put和get在std::cout中输出诊断语句。最后,当两个线程结束后,main函数也就执行完毕了。
- #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就是不可重入的,因为它使用静态变量来保存要被分割成符号的字符串。
有两种方法可以让不可重用的函数变成可重用的函数。第一种方法就是改变接口,用指针或引用代替原先使用静态数据的地方。比方说,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。
- 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 仅运行一次的例程
还有一个问题没有解决:如何使得初始化工作(比如说构造函数)也是线程安全的。比方说,如果一个引用程序要产生唯一的全局的对象,由于实例化顺序的问题,某个函数会被调用来返回一个静态的对象,它必须保证第一次被调用时就产生这个静态的对象。这里的问题就是如果多个线程同时调用了这个函数,那么这个静态对象的构造函数就会被调用多次,这样错误产生了。
解决这个问题的方法就是所谓的“一次实现”(once routine)。“一次实现”在一个应用程序只能执行一次。如果多个线程想同时执行这个操作,那么真正执行的只有一个,而其他线程必须等这个操作结束。为了保证它只被执行一次,这个routine由另一个函数间接的调用,而这个函数传给它一个指针以及一个标志着这个routine是否已经被调用的特殊标志。这个标志是以静态的方式初始化的,这也就保证了它在编译期间就被初始化而不是运行时。因此也就没有多个线程同时将它初始化的问题了。Boost线程库提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏BOOST_ONCE_INIT。
List6是一个使用了boost::call_once的例子。其中定义了一个静态的全局整数,初始值为0;还有一个由BOOST_ONCE_INIT初始化的静态boost::once_flag实例。main函数创建了两个线程,它们都想通过传入一个函数调用boost::call_once来初始化这个全局的整数,这个函数是将它加1。main函数等待着两个线程结束,并将最后的结果输出的到std::cout。由最后的结果可以看出这个操作确实只被执行了一次,因为它的值是1。
- #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;
- }
6 Boost线程库的未来
Boost线程库正在计划加入一些新特性。其中包括boost::read_write_mutex,它可以让多个线程同时从共享区中读取数据,但是一次只可能有一个线程向共享区写入数据;boost::thread_barrier,它使得一组线程处于等待状态,知道所有得线程都都进入了屏障区;boost::thread_pool,他允许执行一些小的routine而不必每一都要创建或是销毁一个线程。
Boost线程库已经作为标准中的类库技术报告中的附件提交给C++标准委员会,它的出现也为下一版C++标准吹响了第一声号角。委员会成员对Boost线程库的初稿给予了很高的评价,当然他们还会考虑其他的多线程库。他们对在C++标准中加入对多线程的支持非常感兴趣。从这一点上也可以看出,多线程在C++中的前途一片光明。
7 参考资料:
- The Boost.Threads Library by Bill Kempf
- Visit the Boost website at <http://www.boost.org>.
usidc52011-10-28 20:44本文假设读者已经基本了解boost线程库的使用方法。
- // janitor.hpp : 安全执行类库//
- #pragma once#include <list>
- template <class T>class RefHolder
- { T& ref_;
- public: RefHolder(T& ref) : ref_(ref) {}
- operator T& () const {
- return ref_; }
- private: // Disable assignment - not implemented
- RefHolder& operator=(const RefHolder&);};
- template <class T>
- inline RefHolder<T> ByRef(T& t){
- return RefHolder<T>(t);}
- class ScopeGuardImplBase
- { ScopeGuardImplBase& operator =(const ScopeGuardImplBase&);
- protected: ~ScopeGuardImplBase()
- { }
- ScopeGuardImplBase(const ScopeGuardImplBase& other) throw() : dismissed_(other.dismissed_)
- { other.Dismiss();
- } template <typename J>
- static void SafeExecute(J& j) throw() {
- if (!j.dismissed_) try
- { j.Execute();
- } catch(...)
- { }
- }
- mutable bool dismissed_;public:
- ScopeGuardImplBase() throw() : dismissed_(false) {
- } void Dismiss() const throw()
- { dismissed_ = true;
- }};
- typedef const ScopeGuardImplBase& ScopeGuard;
- template <typename F>
- class ScopeGuardImpl0 : public ScopeGuardImplBase{
- public: static ScopeGuardImpl0<F> MakeGuard(F fun)
- { return ScopeGuardImpl0<F>(fun);
- } ~ScopeGuardImpl0() throw()
- { SafeExecute(*this);
- } void Execute()
- { fun_();
- }protected:
- ScopeGuardImpl0(F fun) : fun_(fun) {
- } F fun_;
- };
- template <typename F> inline ScopeGuardImpl0<F> MakeGuard(F fun)
- { return ScopeGuardImpl0<F>::MakeGuard(fun);
- }
- template <typename F, typename P1>class ScopeGuardImpl1 : public ScopeGuardImplBase
- {public:
- static ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1) {
- return ScopeGuardImpl1<F, P1>(fun, p1); }
- ~ScopeGuardImpl1() throw() {
- SafeExecute(*this); }
- void Execute() {
- fun_(p1_); }
- protected: ScopeGuardImpl1(F fun, P1 p1) : fun_(fun), p1_(p1)
- { }
- F fun_; const P1 p1_;
- };
- template <typename F, typename P1> inline ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1)
- { return ScopeGuardImpl1<F, P1>::MakeGuard(fun, p1);
- }
- template <typename F, typename P1, typename P2>class ScopeGuardImpl2: public ScopeGuardImplBase
- {public:
- static ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2) {
- return ScopeGuardImpl2<F, P1, P2>(fun, p1, p2); }
- ~ScopeGuardImpl2() throw() {
- SafeExecute(*this); }
- void Execute() {
- fun_(p1_, p2_); }
- protected: ScopeGuardImpl2(F fun, P1 p1, P2 p2) : fun_(fun), p1_(p1), p2_(p2)
- { }
- F fun_; const P1 p1_;
- const P2 p2_;};
- template <typename F, typename P1, typename P2>
- inline ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2){
- return ScopeGuardImpl2<F, P1, P2>::MakeGuard(fun, p1, p2);}
- template <typename F, typename P1, typename P2, typename P3>
- class ScopeGuardImpl3 : public ScopeGuardImplBase{
- public: static ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3)
- { return ScopeGuardImpl3<F, P1, P2, P3>(fun, p1, p2, p3);
- } ~ScopeGuardImpl3() throw()
- { SafeExecute(*this);
- } void Execute()
- { fun_(p1_, p2_, p3_);
- }protected:
- ScopeGuardImpl3(F fun, P1 p1, P2 p2, P3 p3) : fun_(fun), p1_(p1), p2_(p2), p3_(p3) {
- } F fun_;
- const P1 p1_; const P2 p2_;
- const P3 p3_;};
- template <typename F, typename P1, typename P2, typename P3>
- inline ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3){
- return ScopeGuardImpl3<F, P1, P2, P3>::MakeGuard(fun, p1, p2, p3);}
- //************************************************************
- template <class Obj, typename MemFun>
- class ObjScopeGuardImpl0 : public ScopeGuardImplBase{
- public: static ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun)
- { return ObjScopeGuardImpl0<Obj, MemFun>(obj, memFun);
- } ~ObjScopeGuardImpl0() throw()
- { SafeExecute(*this);
- } void Execute()
- { (obj_.*memFun_)();
- }protected:
- ObjScopeGuardImpl0(Obj& obj, MemFun memFun) : obj_(obj), memFun_(memFun) {}
- Obj& obj_; MemFun memFun_;
- };
- template <class Obj, typename MemFun>inline ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun)
- { return ObjScopeGuardImpl0<Obj, MemFun>::MakeObjGuard(obj, memFun);
- }
- template <class Obj, typename MemFun, typename P1>class ObjScopeGuardImpl1 : public ScopeGuardImplBase
- {public:
- static ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1) {
- return ObjScopeGuardImpl1<Obj, MemFun, P1>(obj, memFun, p1); }
- ~ObjScopeGuardImpl1() throw() {
- SafeExecute(*this); }
- void Execute() {
- (obj_.*memFun_)(p1_); }
- protected: ObjScopeGuardImpl1(Obj& obj, MemFun memFun, P1 p1)
- : obj_(obj), memFun_(memFun), p1_(p1) {} Obj& obj_;
- MemFun memFun_; const P1 p1_;
- };
- template <class Obj, typename MemFun, typename P1>inline ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1)
- { return ObjScopeGuardImpl1<Obj, MemFun, P1>::MakeObjGuard(obj, memFun, p1);
- }
- template <class Obj, typename MemFun, typename P1, typename P2>class ObjScopeGuardImpl2 : public ScopeGuardImplBase
- {public:
- static ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1, P2 p2) {
- return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>(obj, memFun, p1, p2); }
- ~ObjScopeGuardImpl2() throw() {
- SafeExecute(*this); }
- void Execute() {
- (obj_.*memFun_)(p1_, p2_); }
- protected: ObjScopeGuardImpl2(Obj& obj, MemFun memFun, P1 p1, P2 p2)
- : obj_(obj), memFun_(memFun), p1_(p1), p2_(p2) {} Obj& obj_;
- MemFun memFun_; const P1 p1_;
- const P2 p2_;};
- template <class Obj, typename MemFun, typename P1, typename P2>
- inline ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1, P2 p2){
- return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>::MakeObjGuard(obj, memFun, p1, p2);}
- #define CONCATENATE_DIRECT(s1, s2) s1##s2
- #define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
- #define ON_BLOCK_EXIT ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard
- #define ON_BLOCK_EXIT_OBJ ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard
- //////////////////////////////////////////////////////////////////////////////////////////// janitor
- struct ICmd_{
- virtual void Dismiss() const throw() = 0; virtual ~ICmd_() throw() {}
- };
- template<typename T>class CmdAdaptor : public ICmd_, protected T
- {public:
- template<typename Fun> CmdAdaptor(Fun fun) : T(fun) {}
- template<typename Fun, typename P1> CmdAdaptor(Fun fun, P1 p1) : T(fun, p1) {}
- template<typename Fun, typename P1, typename P2> CmdAdaptor(Fun fun, P1 p1, P2 p2) : T(fun, p1, p2) {}
- template<typename Fun, typename P1, typename P2, typename P3> CmdAdaptor(Fun fun, P1 p1, P2 p2, P3 p3) : T(fun, p1, p2, p3) {}
- void Dismiss() const throw() {
- T::Dismiss(); }
- };
- class Janitor{
- public: Janitor() throw() {}
- template <typename F> Janitor(F pFun) : spCmd_(
- new CmdAdaptor<ScopeGuardImpl0<F> >(pFun)) {} template <typename F, typename P1>
- Janitor(F pFun, P1 p1) : spCmd_( new CmdAdaptor<ScopeGuardImpl1<F, P1> >(pFun, p1)) {}
- template <typename F, typename P1, typename P2> Janitor(F pFun, P1 p1, P2 p2) : spCmd_(
- new CmdAdaptor<ScopeGuardImpl2<F, P1, P2> >(pFun, p1, p2)) {} template <typename F, typename P1, typename P2, typename P3>
- Janitor(F pFun, P1 p1, P2 p2, P3 p3) : spCmd_( new CmdAdaptor<ScopeGuardImpl3<F, P1, P2, P3> >(pFun, p1, p2, p3)) {}
- Janitor(const Janitor& other) throw() : spCmd_(other.spCmd_) {} //VC++, Comeau need it!
- Janitor& operator =(const Janitor& other) throw() {
- if (spCmd_.get()) spCmd_->Dismiss();
- spCmd_ = other.spCmd_; return *this;
- } void Dismiss() const throw()
- { spCmd_->Dismiss();
- }protected:
- mutable std::auto_ptr<ICmd_> spCmd_;};
- template<typename T>
- class ObjCmdAdaptor : public ICmd_, protected T{
- public: template<typename Obj, typename MemFun>
- ObjCmdAdaptor(Obj& obj, MemFun memFun) : T(obj, memFun) {} template<typename Obj, typename MemFun, typename P1>
- ObjCmdAdaptor(Obj& obj, MemFun memFun, P1 p1) : T(obj, memFun, p1) {} template<typename Obj, typename MemFun, typename P1, typename P2>
- ObjCmdAdaptor(Obj& obj, MemFun memFun, P1 p1, P2 p2) : T(obj, memFun, p1, p2) {} void Dismiss() const throw()
- { T::Dismiss();
- }};
- class ObjJanitor : protected Janitor
- {public:
- using Janitor::Dismiss; ObjJanitor() throw() {}
- template <typename Obj, typename MemFun>
- ObjJanitor(Obj& obj, MemFun memFun) {
- std::auto_ptr<ICmd_> spTmp( new ObjCmdAdaptor<ObjScopeGuardImpl0<Obj, MemFun> >(obj, memFun));
- spCmd_ = spTmp; }
- template <typename Obj, typename MemFun, typename P1> ObjJanitor(Obj& obj, MemFun memFun, P1 p1)
- { std::auto_ptr<ICmd_> spTmp(
- new ObjCmdAdaptor<ObjScopeGuardImpl1<Obj, MemFun, P1> >(obj, memFun, p1)); spCmd_ = spTmp;
- } template <typename Obj, typename MemFun, typename P1, typename P2>
- ObjJanitor(Obj& obj, MemFun memFun, P1 p1, P2 p2) {
- std::auto_ptr<ICmd_> spTmp( new ObjCmdAdaptor<ObjScopeGuardImpl2<Obj, MemFun, P1, P2> >(obj, memFun, p1, p2));
- spCmd_ = spTmp; }
- };
使用范例
- #include "stdafx.h"
- #include <iostream>#include <string>
- using namespace std;
- #include "janitor.hpp"class class1
- {public:
- class1() {
- } ~class1()
- { }
- public: void test()
- { ObjJanitor ja(*this,&class1::testJanitor);
- } void testJanitor()
- { cout << "hello world" << endl;
- }};
- int _tmain(int argc, _TCHAR* argv[])
- { class1 c;
- c.test(); return 0;
- }
二.controlled_module 可被关闭的线程类
- // controlled_module.hpp : 可主动关闭的boost线程类//
- #pragma once
- #include <boost/utility.hpp> #include <boost/thread/condition.hpp>
- #include <boost/thread/mutex.hpp>#include <boost/thread/thread.hpp>
- #include <boost/bind.hpp>
- #include "janitor.hpp"
- class controlled_module_implement : boost::noncopyable { public:
- controlled_module_implement() :active_(false),command_exit_(false){} boost::condition module_is_exit;
- boost::mutex monitor; void active(bool ac)
- {boost::mutex::scoped_lock lk(monitor); if(!(active_=ac))module_is_exit.notify_all();else command_exit_=false;} bool command_exit(){boost::mutex::scoped_lock lk(monitor); return command_exit_;}
- bool active_,command_exit_;
- }; class controlled_module : boost::noncopyable {
- public: virtual void run()
- { ObjJanitor janitor(*impl_,&controlled_module_implement::active,false);
- impl_->active(true); {
- ObjJanitor janitor(*this,&controlled_module::release); if(this->initialize())
- { m_live = true;
- SetEvent(m_event_init); while(!impl_->command_exit() && this->islive() && this->work())
- { }
- } else
- { m_live = false;
- SetEvent(m_event_init); }
- } }
- bool exit(unsigned long sec=0) {
- boost::mutex::scoped_lock lk(impl_->monitor); impl_->command_exit_ = true;
- while(impl_->active_) {
- if(sec) {
- boost::xtime xt; boost::xtime_get(&xt, boost::TIME_UTC);
- xt.sec += sec; if(!impl_->module_is_exit.timed_wait(lk,xt))
- return false; }
- else impl_->module_is_exit.wait(lk);
- } return true;
- } protected:
- controlled_module() :impl_(new controlled_module_implement)
- ,m_live(false) ,m_event_init(0)
- ,m_sleeptime(10) {
- } virtual ~controlled_module()
- { if(m_live)
- stop(); delete impl_;
- } private:
- virtual bool initialize(){return true;} virtual void release(){}
- protected: virtual bool work()
- { Sleep(this->m_sleeptime);
- return true; }
- int m_sleeptime; private:
- bool m_live; void * m_event_init;
- controlled_module_implement* impl_; public:
- bool start() {
- m_event_init = CreateEvent(NULL,FALSE,FALSE,""); boost::thread thd(boost::bind(&controlled_module::run,this));
- ::WaitForSingleObject(m_event_init,INFINITE); CloseHandle(m_event_init);
- m_event_init = 0; return m_live;
- } void stop()
- { m_live = false;
- exit(1); }
- bool islive(){return m_live;} void die()
- { m_live = false;
- SetEvent(m_event_init); }
- void setsleeptime(int n) {
- m_sleeptime = n; }
- };
virtual void release(); 释放
virtual bool work(); 工作函数
如果我们要创建一个可被关闭的线程,可以如下步骤:
1)创建一个controlled_module 继承类
2)实现3个虚拟函数
3)用start(),stop()启动和停止线程
范例:
- //controlled_module demo#include "controlled_module.hpp"
- class thd: public controlled_module{
- public: virtual bool initialize()
- { cout << "thd init" << endl;
- return true; }
- virtual void release() {
- cout << "thd release" << endl; }
- virtual bool work() {
- //your work........ return controlled_module::work();
- }};
- int _tmain(int argc, _TCHAR* argv[]){
- thd t; t.start();
- char buf[10]; gets_s(buf,sizeof buf);
- t.stop(); return 0;
- }
thd线程在启动以后,将循环执行work()函数,直到线程退出
usidc52011-10-28 20:44什么叫事务线程
- void ThreadLogin(){
- try {
- if(fail(物理连接)) throw;
- if(fail(登录)) throw;
- if(fail(查询好友)) throw;
- if(fail(更新)) throw;
- } catch(exception)
- { }
- }
串行的逻辑用串行的代码写,不太好看,况且中途如果主线程发出取消指令,还不好处理。
- class threadLogin{
- void onEventConnect(){
- 物理连接}
- void onEventLogin(){
- 登录}
- void onEventQuery(){
- 查询}
- void onEventUpdate(){
- 更新}
- }
- // thread.hpp : controlled_module_ex类的扩展// 增强线程事务处理能力
- #pragma once#include "controlled_module_ex.hpp"
- class thread: public controlled_module_ex {
- protected: static const int NONE = -1;
- static const int WAITING =-2; static const int DONE =-3;
- static const int FAILED =-4; protected:
- struct process {
- int level; int status;
- int sequence; int trycount;
- int tryindex; std::string lasterror;
- double timeout; bool bTimeout;
- }; process m_process;
- controlled_timer m_timer_process; int m_process_begin,m_process_end;
- double m_timeout_default; public:
- void startprocess(int process_begin,int process_end,double timeout_default=1.0,int cycle=1000) {
- m_process_begin = process_begin; m_process_end = process_end;
- m_timeout_default = timeout_default; m_process.level = m_process_begin;
- m_process.tryindex = 0; this->postmessage(BM_RING_PROCESS);
- m_timer_process.starttimer(cycle,this); }
- void tryagain() {
- if(this->m_process.level==thread::NONE) return;
- this->m_process.tryindex++; if(this->m_process.trycount>0 && this->m_process.tryindex>=this->m_process.trycount)
- { this->fail();
- } else
- this->postmessage(BM_RING_PROCESS); }
- void next() {
- if(this->m_process.level==thread::NONE) return;
- if(this->m_process.level>=this->m_process_end) {
- this->m_timer_process.stoptimer(); this->postmessage(BM_RING_PROCESSEND);
- } else
- { this->m_process.tryindex = 0;
- this->m_process.level++; this->m_process.bTimeout = false;
- this->postmessage(BM_RING_PROCESS); }
- } void fail()
- { m_process.level = thread::NONE;
- this->m_timer_process.stoptimer(); this->postmessage(BM_RING_PROCESSFAIL);
- } virtual void on_safestart()
- { m_process.level = thread::NONE;
- m_process.status = thread::NONE; m_process_begin = m_process_end = thread::NONE;
- controlled_module_ex::on_safestart(); }
- virtual void on_safestop() {
- m_timer_process.stoptimer(); controlled_module_ex::on_safestop();
- } virtual void message(const _command & cmd)
- { controlled_module_ex::message(cmd);
- if(cmd.nCmd==BM_RING_PROCESS) {
- this->on_process(); }
- if(cmd.nCmd==BM_RING_PROCESSEND) {
- this->m_process.level = thread::NONE; this->on_process_end();
- } if(cmd.nCmd==BM_RING_PROCESSFAIL)
- { this->m_process.level = thread::NONE;
- this->on_process_fail(); }
- } virtual void on_timer(const controlled_timer * p)
- { if(p==this->m_timer_process)
- { if(this->m_process.level!=thread::NONE)
- { if(this->m_process.level>=this->m_process_begin && this->m_process.level<=this->m_process_end)
- { if(this->m_process.status==thread::NONE)
- { this->m_process.level = this->m_process_begin;
- m_process.tryindex = 0; on_process();
- } else if(this->m_process.status==thread::WAITING)
- { if(this->m_process.timeout>0)
- { time_t cur;
- time(&cur); if(difftime(cur,(time_t)this->m_process.sequence)>this->m_process.timeout)
- { this->m_process.bTimeout = true;
- this->tryagain(); }
- } }
- else if(this->m_process.status==thread::FAILED) {
- this->tryagain(); }
- else if(this->m_process.status==thread::DONE) {
- this->m_process.level++; m_process.tryindex = 0;
- this->on_process(); }
- } }
- } }
- virtual void on_process() {
- time((time_t*)&m_process.sequence); m_process.timeout = m_timeout_default;
- m_process.status = thread::WAITING; m_process.trycount = -1;
- } virtual void on_process_end(){}
- virtual void on_process_fail(){} int get_sequence(){return m_process.sequence;}
- void put_timeout(double v){m_process.timeout = v;} void put_trycount(int v){m_process.trycount = v;}
- int get_level(){return m_process.level;} void put_level(int v){m_process.level=v;}
- std::string get_lasterror(){return m_process.lasterror;} void put_lasterror(std::string v){m_process.lasterror=v;}
- __declspec(property(put=put_trycount)) int trycount; __declspec(property(put=put_timeout)) double timeout;
- __declspec(property(get=get_level,put=put_level)) int level; __declspec(property(get=get_sequence)) int sequence;
- __declspec(property(get=get_lasterror,put=put_lasterror)) std::string lasterror; };
虚拟函数thread::on_process()处理各种事务事件
- #define PROCESS_1 1#define PROCESS_2 2
- #define PROCESS_3 3class thdex: public thread
- {public:
- virtual void on_process() {
- thread::on_process(); if(this->level==PROCESS_1)
- { cout << "work on process 1..." << endl;
- Sleep(100); cout << "process 1 done." << endl;
- this->next(); }
- else if(this->level==PROCESS_2) {
- cout << "work on process 2..." << endl; this->timeout = -1;
- if(IDNO==::MessageBox(0,"are your want continue?","ask",MB_ICONQUESTION|MB_YESNO)) {
- this->lasterror = "canceled by user"; this->fail();
- } else
- { Sleep(100);
- cout << "process 2 done." << endl; this->next();
- } }
- else if(this->level==PROCESS_3) {
- cout << "work on process 3..." << endl; Sleep(100);
- cout << "process 3 done." << endl; this->next();
- } }
- virtual void on_process_fail() {
- cout << this->lasterror << endl; }
- virtual void on_process_end() {
- cout << "all process done." << endl; }
- };int _tmain(int argc, _TCHAR* argv[])
- { thdex t;
- t.safestart(); t.startprocess(PROCESS_1,PROCESS_3);
- char buf[10]; gets_s(buf,sizeof buf);
- t.safestop(); return 0;
- }
thread事务还支持超时设定和重试次数设定,这里就不做介绍,读者可以自己研究代码。
usidc52011-10-28 20:46全文介绍了3个boost::thread的扩展类,希望能给大家书写多线程代码带来便捷。
- void threadCount(){
- int num = 0; for(int i=0;i<1000000;i++)
- { num+=i;
- }cout << num << endl;
- }int _tmain(int argc, _TCHAR* argv[])
- { boost::thread thd(threadCount);
- thd.join();}
controlled_module
- class mymonitor{
- public: virtual bool work()
- { monitor all socket packets
- return true;}
- }int _tmain(int argc, _TCHAR* argv[])
- { mymonitor m;
- m.start(); //........
- m.stop(); return 0;
- }
3.如果子线程不光生命周期长,而且与主线程经常有消息通讯,或数据传递等等,那么就要用到controlled_module_ex,例如TCP监听服务子线程,controlled_module_ex是最常用到的一个类,至于完整的范例,例如如何实现tcpserver,有时间我会把代码也贴出来。
4.如果子线程是一个有串行事务逻辑的,例如第一步登录银行系统,第二部破解系统密码,第三部拿钱 开个玩笑:),那么就要用到thread类了
usidc52012-02-14 20:51线程池可以减少创建和切换线程的额外开销,利用已经存在的线程多次循环执行多个任务从而提高系统的处理能力,有关线程池的概念可google搜索,下面将其使用实例:
#include <iostream>
#include <boost/bind.hpp>
#include "threadpool.hpp"
using namespace std;
using namespace boost::threadpool;
// Some example tasks
void first_task()
{
for (int i = 0; i < 30; ++i)
cout << "first" << endl;
}
void second_task()
{
for (int i = 0; i < 30; ++i)
cout << "second" << endl;
}
void third_task()
{
for (int i = 0; i < 30; ++i)
cout << "third" << endl;
}
void task_with_parameter(int value, string str)
{
printf("task_with_parameter with int=(%d).\n" ,value);
printf("task_with_parameter with string=(%s).\n" ,str.c_str());
}
void execute_with_threadpool()
{
// Create a thread pool.
pool tp(2);
// Add some tasks to the pool.
tp.schedule(&first_task); // 不带参数的执行函数
tp.schedule(&second_task); // 不带参数的执行函数
tp.schedule(&third_task); // 不带参数的执行函数
tp.schedule(boost::bind(task_with_parameter, 8, "hello")); // 带两个参数的执行函数
// Leave this function and wait until all tasks are finished.
}
int main(int argc, const char* argv[])
{
execute_with_threadpool();
}
可见,使用起来非常简单方便
usidc52012-02-14 20:52Boost的thread库中目前并没有提供线程池,我在sorceforge上找了一个用boost编写的线程池。该线程池和boost结合的比较好,并且提供了多种任务执行策略,使用也非常简单。
下载地址:
http://threadpool.sourceforge.net/
使用threadpool:
这个线程池不需要编译,只要在项目中包含其头文件就可以了。
一个简单的例子:
#include <iostream>
#include "threadpool.hpp"
using namespace std;
using namespace boost::threadpool;
// Some example tasks
void first_task()
{
cout << "first task is running\n" ;
}
void second_task()
{
cout << "second task is running\n" ;
}
void task_with_parameter(int value)
{
cout << "task_with_parameter(" << value << ")\n";
}
int main(int argc,char *argv[])
{
// Create fifo thread pool container with two threads.
pool tp(2);
// Add some tasks to the pool.
tp.schedule(&first_task);
tp.schedule(&second_task);
tp.schedule(boost::bind(task_with_parameter, 4));
// Wait until all tasks are finished.
tp.wait();
// Now all tasks are finished!
return(0);
}
任务返回值的获取:
一般异步调用中,返回值的获取有同步获取和异步获取两种形式。
同步获取返回值:
int task_int_23()
{
cout<<"task_int_23()\n";
return 23;
}
future<int> res = schedule(tp, &task_int_23);
res.wait();
cout<<"get res value:"<<res.get()<<endl;
异步获取返回值:
不知道是设计者就不打算使用异步回调获取返回值还是我看的不够仔细,异步获取返回值的方式还真没有找着,只好自己简单的写了一个回调的仿函数来实现异步返回值的获取。
//R为任务函数的返回值类型
template<class R>
class callback_task
{
typedef boost::function<void (R)> callback;
typedef boost::function<R ()> function;
private:
callback c_;
function f_;
public:
//F: 任务执行函数 C:结果回调函数
template<class F,class C>
callback_task(F f,C c)
{
f_ = f;
c_ = c;
}
void operator()()
{
c_(f_());
}
};
通过这个对象可以很容易的实现异步结果的回调。
//task_int_23的结果回调函数
void callback(int k)
{
cout<<"get callback value:"<<k<<endl;
}
//通过回调的形式获取任务的返回值
tp.schedule(callback_task<int>(&task_int_23,&callback));
执行效率:
这个线程池的效率还没有怎么测试过,目前还没有应用到对性能要求比较高的地方,有时间测试一下。
usidc52012-02-14 20:55呵呵,今天要写一个性能测试的程序,由于之前用过boost的thread,所以就采用了boost的thread库
程序大概是根据指定的参数来生成多个线程来进行一个操作…本来满简单的..但是之前时候不知道boost有进程组的支持…所以只能自己动态的建立一 大堆 thread ..放到一个容器中..然后在遍历join下,然后再在结束前delete他们,很麻烦..不过最后还是实现了。不过就在完成之后,同事晓哲给我看了一下 他的程序..用到了boost的thread_group ,这才发现原来boost也有进程组的支持阿…晕….刚才试着写了一个简单的程序…呵呵,根据指定参数生成指定个数的子程序… 很简单阿..再也不用遍历一遍每一个join一下了..join_all就搞定了..
下面是代码,呵呵,很简单吧
#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>using namespace boost;
using namespace std;
void runChild(const int n)
{
cout << "我是第" << n << "个子线程" << endl;
sleep(1);
cout << "进程" << n << "退出" << endl;
}
int main(int argc, char** argv)
{
int num;
thread_group threads;
if (argc < 2)
{
cout << "请提供一个要生成线程数的参数" << endl;
exit(-1);
}
num = atoi(argv[1]);
cout << "我是主程序,我准备产生" << num << "个子线程" << endl;
for(int i = 0; i < num; i++)
{
threads.create_thread(bind(&runChild, i));
}
cout << "我是主程序,我在等子线程运行结束" << endl;
threads.join_all();
return 0;
}
编译&测试(我在我的ubuntu下测试的)
> g++ threadgroup.cc -lboost_thread
> ./a.out 3
如果在freebsd4下编译的话,如果使用pthread作为线程实现的话,需要明确指出pthread使用线程库,而且默认的template深度好像不能满足boost的需求..需要在编译时加上:
-ftemplate-depth-20 -boost
usidc52012-02-14 21:24为什么要使用线程池:
创建多线程应用程序是非常困难的。需要会面临两个大问题。
一个是要对线程的创建和撤消进行管理,另一个是要对线程对资源的访问实施同步 。
线程池(thread pool),允许有多个线程同时存在,并发执行,并且这些线程受到统一管理。
在Windows Vista中,提供了全新的线程池机制,一般这些线程池中的线程的创建的销毁是由操作系统自动完成的。
Windows Vista 中重新设计了线程池,提供了一组新的线程池API。因此,本篇讨论的仅仅在Windows Vista系统,或其以上的Windows版本中有效。
当一个进程创建之后,它并不与线程池关联。一旦新的线程池API函数被呼叫之后,系统就为该进程创建内核资源,并且有些资源直到进程结束才释放。因此,在使用线程池的时候,线程、其他内核对象、内部数据结构被分配给进程,因此要考虑线程池是否确实必要。
线程池机制有4种功能:
1、调用一个异步函数
2、定时地调用一个函数
3、当一个内核对象被通知的时候调用一个函数
4、当一个异步I/O请求完成的时候调用一个函数
而这4个功能都和线程池中的“工作项”息息相关。可以把“工作项”看作是一个特定的工作记录,记录着异步函数,线程池定时器信息,线程池等待对象信息,线程池I/O对象,而这4个对象就是实现上述4个功能的要素。
调用一个异步函数
首先来讨论一下第1种功能:调用一个异步函数。其基本步骤可以有两种:
第一种:
1、定义一个给定格式的异步函数
2、提交这个异步函数给线程池
第二种:
1、定义一个给定格式的异步函数
2、创建一个“工作项”,该工作项与异步函数、异步函数参数关联
3、将这个工作项提交给线程池
4、关闭创建的“工作项”
为了在线程池中调用一个异步函数,该异步函数的定义如下(第1个参数pInstance暂不讨论,可以简单地传递NULL,下同):
VOID NTAPI SimpleCallback( //函数名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext); //函数参数
然后,你可以提交一个请求给线程池,让其中的一个线程执行这个函数:
BOOL TrySubmitThreadpoolCallback(
PTP_SIMPLE_CALLBACK pfnCallback, //按上面格式定义的异步函数的指针
PVOID pvContext, //传递给异步函数的参数
PTP_CALLBACK_ENVIRON pcbe);
TrySubmitThreadpoolCallback 函数在线程池队列中加入一个“工作项”(work item),如果成功返回TRUE,否则返回FLASE。pcbe参数下面会介绍,你可以简单地传递NULL给这个参数(下同)。
你不必调用CreateThread来创建线程,当进程内调用TrySubmitThreadpoolCallback函数的时候,系统会自动地给你的进程创建线程池,然后在该线程池队列中队列中加入一个“工作项”,并让其中的一个线程来执行你定义的异步函数。当异步函数执行完毕后,该线程不会被销毁,而是进入线程池等待另一个“工作项”的到来。线程池中的线程是回收利用的,并不是不断创建和销毁的,这样提高了性能。同时,这个线程池如果觉得自己的线程太多的话,就自动地销毁一些线程,可以让性能达到最佳。
你可以使用CreateThreadpoolWork函数来创建一个“工作项”:
PTP_WORK CreateThreadpoolWork(
PTP_WORK_CALLBACK pfnWorkHandler, //异步函数指针
PVOID pvContext, //异步函数参数
PTP_CALLBACK_ENVIRON pcbe);
该函数接受一个异步函数的指针和这个异步函数的参数,并创建一个用户模式的数据结构来保存对应的3个参数的数据,同时返回一个指向这个数据结构的指针,可以理解为“工作项”指针。
其中,pfnWordHandler 函数是一个异步函数的指针,这个异步函数会被线程池中某个线程调用,该异步函数定义如下:
VOID CALLBACK WorkCallback( //函数名可以任意
PTP_CALLBACK_INSTANCE Instance,
PVOID Context, //异步函数参数,由CreateThreadpoolWork函数指定
PTP_WORK Work); //线程池“工作项”指针
当你想将一个创建了的工作项提交给线程池,可以使用SubmitThreadpoolWork函数:
VOID SubmitThreadpoolWork(PTP_WORK pWork); //参数是工作项指针
如果多次调用该函数向一个线程池提交同一个工作项,那么异步函数会被调用多次,而每次的参数都是同样一个值。
如果有另一个线程想要取消提交的工作项,或者挂起自己等待工作项完成,可以使用这个函数:
VOID WaitForThreadpoolWorkCallbacks(
PTP_WORK pWork, //工作项指针
BOOL bCancelPendingCallbacks); //是否取消该工作项
pWork 函数是一个工作项指针,由函数CreateThreadpoolWork创建并返回。如果该工作项没有被提交,则该函数马上返回,不做任何工作。
如果传递TRUE给参数bCancelPendingCallbacks,WaitForThreadpoolWorkCallbacks函数将试图取消这个先前提交的工作项。如果这个工作项正在被处理,那么这个处理不会被打断,该函数会等待直到工作项结束才返回。如果这个工作项被提交,但是目前不在处理,那么该函数就会立即取消该工作项并理解返回,那么这个工作项的异步函数就不会被调用了。
如果传递FALSE给传递bCancelPendingCallbacks,WaitForThreadpoolWorkCallbacks函数将挂起这个调用它的线程,直到指定的工作项完成,而线程池中执行这个工作项的线程在完成处理工作项之后返回线程池,继续处理下一个工作项。
如果传递给WaitForThreadpoolWorkCallbacks函数的第一个参数的工作项指针被提交给线程池多次,也就是说多个工作项使用同一个工作项指针,如果第2个参数为FALSE,那么WaitForThreadpoolWorkCallbacks将等到这个工作项指针代表的所有工作项处理完成才返回。如果传递TRUE给第2个参数,WaitForThreadpoolWorkCallbacks将等待,只要当前正在执行的工作项结束就返回。
当你不要使用工作项的时候,使用CloseThreadpoolWork函数关闭之。
VOID CloseThreadpoolWork(PTP_WORK pwk);
定时调用一个函数
这是Windows线程池提供的第2个功能。
有的时候,应用程序需要在某一个特定的时间执行特定的任务,你可以选择使用Windows内核对象“等待定时器”来实现这个功能,但是如果这种基于时间的任务特别的多,那么就不得不为每个这样的任务创建一个“等待定时器”对象,无疑会浪费资源。当然,你也许会想到创建单个“等待定时器”,然后不断地设置它的下一次要等待的时间,这样就可以完成多个基于时间的任务了。但是如此一来,代码量就会增大。
Windows提供了线程池来实现这样的功能,其方法是通过“线程池定时器”。
首先,你要定义一个如下格式的回调函数,让线程池中的线程定时调用它:
VOID CALLBACK TimeoutCallback( // 函数名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext, // 函数的参数
PTP_TIMER pTimer); // 一个指向“线程池定时器”的指针
然后告诉线程池什么时候调用你的回调函数:
PTP_TIMER CreateThreadpoolTimer(
PTP_TIMER_CALLBACK pfnTimerCallback, // 类似上面格式的函数的指针
PVOID pvContext, // 回调函数的参数,由这个参数指明
PTP_CALLBACK_ENVIRON pcbe);
不难发现,CreateThreadpoolTimer函数和第1种方法中的CreateThreadpoolWork函数十分类似,而且两者的回调函数也十分类似。当调用CreateThreadpoolTimer函数的时候,第1个参数指向一个回调函数,第2个参数pvContext会传递给这个回调函数的第2个参数,而其返回值——一个“线程池定时器”指针也会传递给这个回调函数的第3个参数。
如果你想把由CreateThreadpoolTimer函数创建的“线程池定时器”注册到线程池中去,可以使用如下函数:
VOID SetThreadpoolTimer(
PTP_TIMER pTimer, // 一个“线程池定时器”指针
PFILETIME pftDueTime, // 回调函数被调用的时间
DWORD msPeriod, // 周期性调用回调函数的间隔时间(毫秒)
DWORD msWindowLength); // 周期时间的波动范围(毫秒)
该函数的第1个参数pTimer是由CreateThreadpoolTimer函数返回的。第2个参数pftDueTimer是指明回调函数什么时候被调用,一个正的数值表示的是绝对时间,即UTC统一时间;一个负数表示相对时间,即调用该函数之后开始计时,以毫秒为单位;如果是-1,表明回调函数马上被调用。第3个参数msPeriod表明周期性地调用回调函数的时间间隔,即周期时间,如果只想回调函数调用一次,传递0给这个参数。第4个参数是和第3个参数联用的,表明周期时间的波动范围,比如,msPeriod=1000,msWindowLength=2,那么回调函数会在每隔998、999、1000、1001、1002这5个可能的毫秒时间被调用。
如果一个“线程池定时器”已经被SetThreadpoolTimer设置了,那么可以再次呼叫SetThreadpoolTimer函数来更改它的相关属性。呼叫SetThreadpoolTimer的时候,可以把NULL传递给第2个参数pftDueTime,这样就说明让线程池停止呼叫对应的回调函数。
你可以查询一个“线程池定时器”是否被设置,呼叫IsThreadpoolTimerSet函数:
BOOL IsThreadpoolTimerSet(PTP_TIMER pti);
你也可以让线程等待一个“线程池定时器”完成工作,呼叫函数WaitForThreadpoolTimerCallbacks,当要关闭一个“线程池定时器”的时候,呼叫函数CloseThreadpoolTimer,这两个函数同前面讨论的WaitForThreadpoolWork和CloseThreadpoolWorkCallbacks函数类似,可以参考本篇前面的内容。
下面总结一下“线程池定时器”的使用方法:
- 定义一个回调函数,如TimeoutCallback那样的格式。
- 使用CreateThreadpoolTimer函数创建一个“线程池定时器”,并将已定义的回调函数与它关联在了一起。
- 使用SetThreadpoolTimer设置“线程池定时器”的属性,并将其提交给线程池。
- 调用CloseThreadpoolTimer关闭“线程池定时器”。
当一个内核对象被通知的时候调用一个函数
有很多线程,初始化的时候等待一个内核对象,一旦这个内核对象转入“已通知”状态,线程就会通知另外一些线程,然后转回继续等待这个内核对象。但是,如果这样的线程很多的话,无疑会增大系统的开销。
此时,你可以考虑使用线程池来实现这个功能,就是当一个内核对象被通知的时候,由线程池中的一个线程调用一个异步的回调函数。
如果你想让一个“工作项”在一个内核对象为“已通知”的状态下被执行,这个基本流程和前面两个功能的流程类似。
首先,定义一个如下格式的异步函数:
VOID CALLBACK WaitCallback( // 函数名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID Context, // 函数的参数
PTP_WAIT Wait, // 线程池等待对象的指针
TP_WAIT_RESULT WaitResult); // 该函数被调用的原因
然后,需要创建一个“线程池等待对象”:
PTP_WAIT CreateThreadpoolWait(
PTP_WAIT_CALLBACK pfnWaitCallback, // 回调函数指针,函数如上定义
PVOID pvContext, // 传递给回调函数参数Context
PTP_CALLBACK_ENVIRON pcbe);
接着就可以将创建的“线程池等待对象”与这个线程池关联起来,此时线程池队列中会有一个“等待项”记录:
VOID SetThreadpoolWait(
PTP_WAIT pWaitItem, // 一个“线程池等待对象”指针
HANDLE hObject, // 一个内核对象句柄,当被通知时,回调函数被调用
PFILETIME pftTimeout); // 等待hObjetct内核对象受到通知的时间
这个函数的第1个参数pWaitItem很显然是从CreateThreadpoolWait成功返回的“线程池等待对象”指针。第2个参数hObject是一个内核对象句柄,当这个内核对象为“已通知”状态,则线程池中的一个线程调用异步回调函数。第3个参数pftTimeout是一个等待内核对象的时间,如果为0表示不等待;传递一个负数表示一个相对时间;传递一个正数表示绝对时间;传递NULL表示无限期地等待。
要注意的是,不要多次使用SetThreadpoolWait来等待同一个hObject。
当内核对象被通知或者等待时间超出,线程池中的线程将呼叫你的回调函数,这个回调函数的最后一个参数WaitResult的值,其实是一个DOWRD类型的,它指明的该回调函数被调用的原因:
1、WAIT_OBJECT_0:SetThreadpoolWait中第二个参数hObject所表明的内核对象受到通知。
2、WAIT_TIMEOUT:内核对象受到通知的时间超过了SetThreadpoolWait的第三个参数所设置的等待时间。
3、WAIT_ABANDONED_0:SetThreadWait函数第二个参数hObject代表一个互斥内核对象,而这个互斥内核对象被丢弃。
一旦一个线程池线程调用了你的回调函数,那么对应的“等待项”就不活跃了,你必须使用相同的参数再次调用SetThreadpoolWait函数来提交一个等待项。
如果想删除一个“等待项”,可以使用与之对应的“线程池等待对象”指针来调用SetThreadpoolWait,并将hObejct参数设置为NULL。
最后,你也可以使用WaitForThreadpoolWaitCallbacks来等待对应的“等待项”结束,也可以使用CloseThreadpoolWait来关闭一个“等待项”。这两个参数和WaitForThreadpoolWorkCallbakcs和CloseThreadpoolWork是类似的。
当异步I/O请求结束的时候调用一个函数
读过上面3中线程池的功能,不难发现有很多共同的特点,连函数名称都很有规律。线程池中的线程由系统统一管理,自动地创建和销毁。其实,这些线程内部都在等待一个I/O完成端口,这个I/O完成端口称为“线程池的I/O完成端口”。
如果你要使用线程池来处理设备异步I/O请求的时候,当你打开一个设备的时候,必须首先将这个设备与“线程池I/O完成端口”关联起来,然后告诉线程池当设备异步I/O请求结束之后哪个函数将被调用。
首先,定义一个如下格式的异步回调函数:
VOID CALLBACK OverlappedCompletionRoutine( // 函数名可以任意
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext, // 该函数的一个参数
PVOID pOverlapped, // OVERLAPPED结构指针
ULONG IoResult, // I/O请求结果,如果成功,则为NO_ERROR
ULONG_PTR NumberOfBytesTransferred, // I/O请求的数据传输字节数
PTP_IO pIo); // 一个“线程池I/O完成项”指针
这个函数的最后一个参数pIo是一个PTP_IO类型,即一个线程池I/O完成项,它与“线程池工作项”和“线程池等待项”是类似的。你必须创建它,使用如下函数:
PTP_IO CreateThreadpoolIo(
HANDLE hDevice, // 与线程池I/O完成端口关联的设备对象句柄
PTP_WIN32_IO_CALLBACK pfnIoCallback, // 如上格式的异步回调函数指针
PVOID pvContext, // 该参数在调用时传递给回调函数的第2个参数
PTP_CALLBACK_ENVIRON pcbe);
该函数将hDevice参数所对应的设备记录到线程池I/O项中,然后,可以使用如下函数将设备与线程池I/O完成端口关联起来:
VOID StartThreadpoolIo(PTP_IO pio);
注意,StartThreadpoolIo函数必须在ReadFile和WriteFile之前调用,如果没有在它们之前调用,你的异步回调函数不会被调用。
当你想停止调用回调函数的时候,可以使用CancelThreadpoolIo,如果在调用ReadFile或WriteFile之后,它们的返回值是FLASE,而GetLastError的返回值不是ERROR_IO_PENDING,那么也应该调用CancelThreadpoolIo:
VOID CancelThreadpoolIo(PTP_IO pio);
当结束了设备I/O,你应该使用CloseHandle关闭设备句柄,然后呼叫CloseThradpoolIo关闭线程池I/O项,即取消设备与线程池I/O请求的关联。
VOID CloseThreadpoolIo(PTP_IO pio);
另外,你可以让一个线程等待I/O请求结束:
VOID WaitForThreadpoolIoCallbacks(
PTP_IO pio,
BOOL bCancelPendingCallbacks); // 是否取消回调函数的调用
如果给这个函数的参数BCancelPendingCallbacks传递TRUE,那么回调函数将不会被调用,该函数的用法和WaitForThreadpoolWork是类似的。
回调函数结束之后的行为
注意上面讨论的各种类型的回调函数第1个参数,是一个PTF_CALLBACK_INSTANCE类型的数据pInstance,从字面上看,是“线程池回调函数实体指针”,也就是说,这个数据是各个回调函数唯一的,是回调函数的标识,这个数据是在调用回调函数之前由系统自动分配的,可以用这个参数调用如下函数:
// 当回调函数返回的时候,自动离开一个指定的关键代码段
VOID LeaveCriticalSectionWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci, // 回调函数实体指针,标识一个回调函数
PCRITICAL_SECTION pcs); // 关键代码段结构指针,标识一个关键代码段
// 当回调函数返回的时候,自动释放一个指定的互斥内核对象
VOID ReleaseMutexWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE mut);
// 当回调函数返回的时候,自动释放一个指定的信号量内核对象
VOID ReleaseSemaphoreWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE sem,
DWORD crel);
// 当回调函数返回的时候,自动将一个事件内核对象设置为已通知状态
VOID SetEventWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HANDLE evt);
// 当回调函数返回的时候,自动卸载一个模块
VOID FreeLibraryWhenCallbackReturns(
PTP_CALLBACK_INSTANCE pci,
HMODULE mod);
这些函数的第1个参数pci标识当线程池前正在处理的工作、定时器、等待、I/O项,调用这些函数,表示对应的回调函数结束之后,所做的一些释放和设置工作。
其中,前4个函数,提供了一种方法来通知其他线程,说明线程池中的某一个工作项完成。最后一个函数,提供了一种方法来卸载DLL的方法,特别是当回调函数是从DLL中导出的时候,这种方法特别适用。要注意的是,只能有一个动作在回调函数返回的时候被执行,你不能多次呼叫上述5个函数,这样的话,最后一次呼叫的函数会覆盖前面所呼叫的函数,因此,不能同时离开关键代码段并释放信号量内核对象。
另外,还有两个函数需要回调函数实体指针:
BOOL CallbackMayRunLong(PTP_CALLBACK_INSTANCE pci);
VOID DisassociateCurrentThreadFromCallback(PTP_CALLBACK_INSTANCE pci);
CallbackMayRunLong并不是设置回调函数结束时的工作的,而是当一个回调函数认为自己执行的时间可能比较长才可能需要呼叫这个函数。此时,线程池不会创建新的线程,以此来提高这个回调函数的性能。当该函数返回FLASE,线程池不允许其他线程能够处理线程池队列中的其他项;如果返回TRUE,表示线程池允许其他线程处理其他工作项。
DisassociateCurrentThreadFromCallback函数表明与回调函数关联的工作项在“逻辑上”完成了(其实不是真正完成),此时允许等待在这个工作项上的函数返回,比如WaitForThreadpoolWorkCallbacks、WaitForThreadpoolTimerCallbacks、WaitForThreadpoolWaitCallbacks、WaitForThreadpoolIoCallbacks这些函数返回。
定制线程池
以上所讨论的线程池,都是系统自动控制的,用户无法改变其内部的流程。
下面,我们讨论一下如何自己定制线程池。
你会注意到,上面的每个“创建”函数:CreateThreadpoolWork、CreateThreadpoolTimer、CreateThreadpoolWait、CreateThreadpoolIo以及TrySubmitThreadpoolCallback这5个函数中的最后一个参数pcbe,一个类型为PTP_CALLBACK_ENVIRON的参数,一个指向“回调函数环境”结构的指针。你可以简单地传递NULL给这个参数,表明你使用默认的系统自动分配和管理的进程线程池。
但是,有的时候程序员喜欢自己来控制线城池,给线城池设置一些规则和属性。比如设置改线城池中线程的数量上下限,或者想操纵线城池中线程的创建和销毁。
要达到这个目的,可以自己创建线程池,然后设置一些属性。
首先,创建一个线程池,使CreateThreadpool函数:
PTP_POOL CreateThreadpool(PVOID reserved); // 参数是保留参数,必须为NULL
该函数返回一个PTP_POOL类型的数据,姑且认为是“线城池指针”的意思,即代表了一个线程池。
然后,就可以通过这个线城池指针来呼叫相应的API函数,设置线程池的一些属性了。
你可以设置线城池的线程数量的上下限:
BOOL SetThreadpoolThreadMinimum(
PTP_POOL pThreadPool,
DWORD cthrdMin);
BOOL SetThreadpoolThreadMaximum(
PTP_POOL pThreadPool,
DWORD cthrdMost);
通过呼叫这两个函数之后,线程池中的线程的数量决不会少于设置的最小值,并且允许这个数量增大到最大值。顺便说一下,默认的线城池的线程数量范围为1~500。
当一个线程池需要关闭的时候,呼叫CloseThreadpool函数:
VOID CloseThreadpool(PTP_POOL pThreadPool);
呼叫这个函数之后,对应的线程池队列中的项都不会被处理,当前正在处理工作项的线程都会结束处理然后线程终止,其他还没有被处理的项都会被取消。
一旦你创建了你自己的线程池并设置了线程数量上下限,你就初始化那个“回调函数环境”结构了,该结构中包含了另外的一些设置。这个结构与一个工作项有关。该结构定义如下:
typedefstruct _TP_CALLBACK_ENVIRON {
TP_VERSION Version;
PTP_POOL Pool; // 所属哪个线程池
PTP_CLEANUP_GROUP CleanupGroup;
PTP_CLEANUP_GROUP_CANCEL_CALLBACK CleanupGroupCancelCallback;
PVOID RaceDll;
struct _ACTIVATION_CONTEXT *ActivationContext;
PTP_SIMPLE_CALLBACK FinalizationCallback;
union {
DWORD Flags;
struct {
DWORD LongFunction : 1;
DWORD Private : 31;
} s;
} u;
} TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;
你最好不要自己去初始化该结构,而是应该使用如下函数:
VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);
该函数将结构中的各个字段设置为0,除了Version被设置为1。
当你不再需要使用该结构的时候,使用如下函数删除它:
VOID DestroyThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);
在初始化TP_CALLBACK_ENVIRON结构之后,该结构与一个工作项相关,然后你就可以使用这个结构来提交一个工作项给指定的线程池了:
VOID SetThreadpoolCallbackPool(
PTP_CALLBACK_ENVIRON pcbe, // 回调函数环境结构指针
PTP_POOL pThreadPool); // 线程池指针,由CreateThreadpool函数返回
如果不调用该函数,那么回调函数环境结构中的Pool成员的值就是NULL,表示不为任何定制的线程池相关,那么与此结构相关的工作项就会提交给默认线程池。
如果一个工作项处理的时间比较长,可以调用SetThreadpoolCallbackRunsLong函数,这会导致线程池更快地创建线程来处理这个工作项。
VOID SetThreadpoolCallbackRunsLong(PTP_CALLBACK_ENVIRON pcbe);
你可以呼叫SetThreadpoolCallbackLibrary来确保当某个工作项未完成的时候,一个DLL始终被加载到进程地址中。该函数也可以除去潜在的死锁。
VOID SetThreadpoolCallbackLibrary(
PTP_CALLBACK_ENVIRON pcbe,
PVOID mod); // 模块指针,就是模块句柄
线程池要处理很多的项目,因此很难确定这些项目什么时候处理结束,这也使得线程池的清除工作困难了。为了解决这种情况,线程池提供了一种机制——“清理组”。要注意的是,该机制不适合于默认线程池,只适合于定制的线程池。因为默认线程池的生命期与进程一样,系统会在进程结束之后清除默认线程池。
为了使用“清理组”机制,首先,你需要创建一个清理组:
PTP_CLEANUP_GROUP CreateThreadpoolCleanupGroup();
然后将已创建的清理组与线程池关联起来:
VOID SetThreadpoolCallbackCleanupGroup(
PTP_CALLBACK_ENVIRON pcbe, // 回调函数环境结构指针
PTP_CLEANUP_GROUP ptpcg, // 清理组指针
PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng); // 回调函数指针
该函数在内部将回调函数指针p的cbe参数CleanupGroup和CleanupCancelCallback成员设置为第2和第3个参数所提供的值。如果清理组被取消,第3个参数pfng表示的回调函数将会被调用,这个回调函数必须满足如下格式:
VOID CALLBACK CleanupGroupCancelCallback( // 函数名可以任意
PVOID pvObjectContext,
PVOID pvCleanupContext);
每次你调用CreateThreadpoolWork、CreateThreadpoolTimer、CreateThreadpoolWait和CreateThreadpoolIo的时候,如果传递给它们的最后一个参数pcbe不是NULL,那么一项就会加入相关的组清理中,当这样的项都完成之后,你调用CloseThreadpoolWork、CloseThreadpoolTimer、CloseThreadpoolWait、ClsoeThreadpoolIo的时候,又暗中地将这样的项从组清理中删除了。
这个时候,想要删除线程池,可以调用如下函数:
VOID CloseThreadpoolCleanupGroupMembers(
PTP_CLEANUP_GROUP ptpcg, // 组清理指针
BOOL bCancelPendingCallbacks, // 是否取消即将开始执行的回调函数
PVOID pvCleanupContext); // 传入CleanupGroupCancelCallback的参数
这个函数同以前的WaitForThreadpool*函数类似,当一个线程呼叫它时,它等待,直到所有的在线程工作组的项完成处理。如果第2个参数为TRUE,会取消所有还没有开始执行的项目,然后等待当前正在执行的项目,直到它们执行完成后该函数返回。如果第2个参数为TRUE,而且SetThreadpoolCallbackCleanupGroup的最后一个参数pfng传递了一个回调函数指针,那么这个回调函数就会为每个被取消的项目调用一次,CloseThreadpoolCleanupGroupMembers函数的第3个参数pvCleanupContext就会传入回调函数的第2个参数pvCleanupContext。
如果在呼叫CloseThreadpoolCleanupGroupMembers函数的时候,传递FLASE给第2个参数,那么该函数就会等待线程池中队列中的所有的项完成之后才返回,此时回调函数不会被调用,因此可以传递NULL给pvCleanupContext参数。
当ClsetThreadpoolCleanupGroupMembers函数返回之后,你需要呼叫CloseThreadpoolCleanupGroup来关闭一个线程池清理组:
VOID WINAPI CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg);
最后,要调用DestroyThreadpoolEvironment和CloseThreadpool函数来清除回调函数环境结构和关闭线程池。
小结
通过前面的叙述,线程池的操作流程是比较固定的:
1、定义异步回调函数
2、创建相关项
3、提交或设置项
4、线程池执行
5、关闭相关项
自己写了一段代码,总结了前面的知识,代码中省略了第1步,即没有定义回调函数,因为这是根据需要而编写的。如果代码中有错误,还请大家指出。
PTP_POOL pThreadpool= CreateThreadpool(NULL); // 创建线程池
// 设置线程池线程数量上下限
SetThreadpoolThreadMinimum(pThreadpool, 2);
SetThreadpoolThreadMaximum(pThreadpool, 10);
// 初始化“回调函数环境”结构
TP_CALLBACK_ENVIRON tcbe;
InitializeThreadpoolEnvironment(&tcbe);
// 将该回调函数环境结构与线程池相关联
SetThreadpoolCallbackPool(&tcbe, pThreadpool);
// 创建清理组
PTP_CLEANUP_GROUP pTpcg= CreateThreadpoolCleanupGroup();
// 将回调函数环境结构与清理组关联起来
SetThreadpoolCallbackCleanupGroup(&tcbe, pTpcg, NULL);
// 现在可以创建一些项,提交给线程池
PTP_WORK pTpWork = CreateThreadpoolWork(,&tcbe);// 创建一个工作项
SubmitThreadpoolWork(pTpWork); // 提交工作项
PTP_TIMER pTpTimer = CreateThreadpoolTimer(,&tcbe);// 创建一个定时器项
SetThreadpoolTimer(pTpTimer, ); // 提交定时器
PTP_WAIT pTpWait = CreateThreadpoolWait(,&tcbe);// 创建一个等待项
SetThreadpoolWait(pTpWait, ); // 提交等待项
PTP_IO pTpIO = CreateThreadpoolIo(,&tcbe); // 创建一个IO项
StartThreadpoolIo(pTpIO); // 开始执行IO项
// 等待所有项完成
CloseThreadpoolCleanupGroupMembers(pTpcg, FALSE, NULL);
// 关闭各个项
CloseThreadpoolWork(pTpWork);
CloseThreadpoolTimer(pTpTimer);
CloseThreadpoolWait(pTpWait);
CloseThreadpoolIo(pTpIO);
CloseThreadpoolCleanupGroup(pTpcg); // 关闭线程池清理组
DestroyThreadpoolEnvironment(&tcbe); // 删除回调函数环境结构
CloseThreadpool(pThreadpool); // 关闭线程池
usidc52012-02-14 21:27
传 统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即 时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于 不停的创建线程,销毁线程的状态。
- 线程池管理器: 用于创建并管理线程池工作线程 : 线程池中实际执行的线程任务接口 : 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。任务队列 : 线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
CThreadPool();
void SetMaxNum(int maxnum){m_MaxNum = maxnum;}
usidc52012-02-14 21:30代码来源:codeproject
1) 步骤一
#include "ThreadPool.h"
2)步骤二
从IRunObject派生自己的处理类
class CMyRunObject : public IRunObject
{
public:
CMyRunObject();
~CMyRunObject();
void Run();
void Initialize()
{
// 初始化
}
bool AutoDelete()
{
// 是否自动删除(线程运行完后,自动删除IRunObject对象
return true;
}
void DeleteInstance()
{
// 删除实例,当作析构来用
delete this;
}
};
派生类中必须实现的函数:Run, Initialize, AutoDelete, DeleteInstance
3)步骤三
启动线程池
CThreadPool m_Pool;
CMyRunObject *pRunObject = new CMyRunObject();
m_Pool.Run(pDeliver);
关闭线程池
m_pool.Destroy();
重启线程池
m_pool.Destroy();
m_pool.SetPoolSize(10); // 池的初始线程数量
m_pool.SetPoolMaxSize(100); // 池的最大线程数
m_pool.Create();
ThreadPool源代码:
http://blog.vckbase.com/Files/wangjun/ThreadPool.rar
usidc52012-02-14 21:33很多时候,我们需要一组线程来解决问题。当然可以创建一些线程来完成工作,然后关闭掉。当又需要时,重复上述过程即可。然而,无论是客户端还是服务端,完全没必要如此,只需要实现创建一组线程,按需分配,不必创建--关闭--创建--关闭…
也许我们也能自己写成符合需求的线程池组件,但是,系统也提供了线程池组件。我认为就应该重复利用(出于学习的目的另当别论)。在这里,主要针对XP系统,提供一组C++ wrap过的线程池组件和模仿ATL线程池。当然,使用系统的线程池有一定的限制(设置线程堆栈大小等)。但还是能满足大部分的需求。
首先,来看看系统线程池(据《Windows核心编程》所说,在内部都是用完成端口来管理的)
Windows 线程池中的线程有两种类型,一种可以用来处理异步I/O, 另一种则不能。前者依赖于IO完成端口,IOCP是一种Windows内核对象,它可以将线程和I/O端口绑定在特定的系统资源上,对带有完成端口的I/O进行处理是一个复杂的过程。
[list=1]
这样,当该设备的IO运行完成时,非IO组件就知道调用哪个函数,以便能够处理已完成IO请求
其次,来看如何使用,简单展示下QueueUserWorkItemPool,其他的则在Example中给出
[pre]class A
{
enum { Count = 1000000 };
public:
void DoWork()
{
LONG lVal = 0;
for(DWORD i = 0; i != Count; ++i)
{
InterlockedExchangeAdd(&lVal, i);
}
}
void DoWork(int nNum)
{
LONG lVal = 0;
for(DWORD i = 0; i != nNum; ++i)
{
InterlockedExchangeAdd(&lVal, i);
}
}
};
A a;
QueueUserWorkItemPool::Call(&A::DoWork, &a);
QueueUserWorkItemPool::CallEx(&A::DoWork, &a, nCount);
[/pre]
然而,在对于BindIOCompletionCallbackPool的设计时,为了区分每个不同的回调函数,把自己弄成了模板类,需要提供一个额外的模板参数来进行区分--
BindIOCompletionCallbackPool<1>::Call和BindIOCompletionCallbackPool<2>::Call
如果你有更好的建议,希望你能提出,大家共同提高。
所有的源码和示例以提供:(需要更改后缀,把.gif改为.rar,用右键下载)
猛击这里猛击这里
在上一篇中,我介绍了一种线程池实现方式(利用系统提供的线程池API)。在这里,我介绍一个自己实现的线程池方案。
线程池的概念我就不再赘述了,可以参看这里,这里。线程池是一种模式,类似于操作系统中的缓冲区的概念。
下载:
CSDN下载地址
目的:
提供对一组线程的管理,提高多个线程在系统中运行效率,重复利用资源以减少对系统的开销。该组件提供方便的异步接口,可以自定制,动态管理。
特点:
- Policy-based 的实现
- 调度策略:FIFO、LIFO、Priority
- 执行策略:普通执行、优先级执行、循环执行、超时执行、事件等待执行
- 方便同STL组件搭配
扩展方向:
- 大小策略: 静态大小,动态增长
- 调度策略: 新增deadline方式
- 增加单元测试,增加示例
关系图:
代码示例:
- const size_t threadCnt = 4;
- typedef async::threadpool::ThreadPool ThreadPool;
- ThreadPool pool(threadCnt);
- assert(pool.Size() == threadCnt);
- pool.Call(&FirstTask);
- pool.Call(&SecondTask);
- pool.Call(&FirstTask);
- pool.Call(&FirstTask);
- system("pause");
- WaitHandle wait(pool);
- wait.Call();
- LoopTask<ThreadPool> loop(pool, 1);
- LoopTask<ThreadPool> loop1(pool, 2);
- LoopTask<ThreadPool> loop2(pool, 3);
- wait.Call();
- std::tr1::shared_ptr<Runnable> runable(new Runnable);
- async::threadpool::Call(pool, runable);
- wait.Stop();
- system("pause");
- loop.Stop();
- loop1.Stop();
- loop2.Stop();
- system("pause");
还存在的问题:
目前具体问题倒是没发现,不过肯定还存在BUG,希望各位在使用的朋友可以提供反馈
usidc52012-02-15 12:53threadpool是基于boost库实现的一个线程池子库,但线程池实现起来不是很复杂。我们从threadpool中又能学到什么东西呢?
它是基于boost库实现的,如果大家对boost库有兴趣,看看一个简单的实现还是可以学到点东西的。
threadpool基本功能
1、任务封装,包括普通任务(task_func)和优先级任务(prio_task_func)。
2、调度策略,包括fifo_scheduler(先进先出)、lifo_scheduler(后进先出)、prio_scheduler(优先级)。
3、结束策略,包括wait_for_all_tasks(全部任务等待)、wait_for_active_tasks(激活任务等待)、immediately(立即结束)。
4、动态修改线程池个数功能。
5、基于future封装的异步返回值获取功能。
在sorceforge上有一个用boost编写的线程池。该线程池和boost结合的比较好,并且提供了多种任务执行策略,使用也非常简单。 下载地址:http://threadpool.sourceforge.net/ 这个线程池不需要编译,只要在项目中包含其头文件就可以了。
一、源代码分析
quickstart分析(/threadpool/libs/threadpool/quickstart)
这个例子的代码很简单,但已经全部展示了线程池的核心内容,包括建立、调度、同步等操作。
view plaincopy to clipboardprint?
// Create fifo thread pool container with two threads.
pool tp(2);
// Add some tasks to the pool.
tp.schedule(&first_task);
tp.schedule(&second_task);
// Wait until all tasks are finished.
tp.wait();
// Create fifo thread pool container with two threads.
pool tp(2);
// Add some tasks to the pool.
tp.schedule(&first_task);
tp.schedule(&second_task);
// Wait until all tasks are finished.
tp.wait();
pool的定义具体见pool.hpp,但使用了pimpl模式,核心代码见pool_core.hpp文件。
下面是pool的定义
typedef thread_pool<task_func, fifo_scheduler, static_size, resize_controller, wait_for_all_tasks> fifo_pool;
typedef fifo_pool pool;
从上面可以知道,pool实际就是fifo_pool,从模板参数可以看到,使用了fifo_scheduler和wait_for_all_tasks。
对于线程池有点理解的都知道,一般都是那几样东西,线程的封装,条件变量,队列数据结构。
所以简单的能做的很简单,复杂的的就看你的策略需求了。
对基于boost库的threadpool子库来说,上面的三样东西都是现成的,线程封装和条件变量直接使用thread子库就行,队列使用stl的标准容器。
task_adaptors.hpp
对线程任务的封装,所谓task,我们可以理解成需要运行的函数。
threadpool最大限度的使用了function和bind功能来封装函数,这点和thread子库类似。
文件中涉及的内容主要有三个:task_func、prio_task_func和looped_task_func。
对普通task的封装
typedef function0<void> task_func;
如果对bind和function熟悉的应该很好理解。
对优先级任务的封装
class prio_task_func
这个类很简单,重载了两个方法,
operator()是仿函数的用法,
operator<是用于优先级比较使用的,用于stl容器的元素比较。
size_policies.hpp
对size的封装,包括empty_controller、resize_controller和static_size。
shutdown_policies.hpp
对线程池结束的策略封装,包括wait_for_all_tasks、wait_for_active_tasks和immediately。
这几个类很简单,具体操作封装在pool中。
线程池运行过程中,包括队列中等待的task,线程正在运行的task。
所以结束的时候,对这些task的策略操作是有选择的。
scheduling_policies.hpp
对任务调度测试的封装,包括fifo_scheduler、lifo_scheduler和prio_scheduler。
实际上,这三个类的相似程度很高,大家可能更喜欢用继承和虚函数实现。
前面说到保存task的队列数据结构,在这里就看的很清楚了。
fifo和lifo使用的是std::deque,prio使用的是std::priority_queue,其他部分代码没什么好说的了。
pool_adaptors.hpp
对全局schedule函数的几种封装。
future.hpp
好像thread子库也有future,但不清楚是否是一样的内容。
threadpool的future是为了封装异步函数调用返回值实现的。
简单点理解,就是schedule任务的时候,把一个指针在两者间绑定起来,后面就可以通过future来获取返回值了。
当然,获取返回值的过程应该是阻塞的,任务未完成时只能wait。
locking_ptr.hpp
LockingPtr的简单封装,具体可google《volatile - Multithreaded Programmer's Best Friend》。
threadpool大量使用了volatile关键字,所以需要LockingPtr保护。
scope_guard.hpp
对函数对象的封装,利用C++析构函数时调用一个在构造函数时绑定的函数对象。
worker_thread.hpp
对工作线程的封装,这个封装不是指底层线程api封装,因为这部分是由boost的thread子库提供的。
封装针对的是循环执行task的逻辑函数(线程跑起来就loop run某个函数,从队列中获取task执行,空闲时等待。)
我们重点看的是run和create_and_attach。
这两个函数连起来看,就很清楚了,create_and_attach通过bind方式生成一个thread执行run方法。
run方法中的这条语句就是一个简单的loop操作,
while(m_pool->execute_task()) {}
所以,当execute_task返回值为false时,run函数就结束了,bind该函数的thread也就结束了。
ok,来到这里,有必要简单的把整个调用过程说明一下。
// Create fifo thread pool container with two threads.
pool tp(2);
该操作会调用pool的构造函数
view plaincopy to clipboardprint?
thread_pool(size_t initial_threads = 0)
: m_core(new pool_core_type)
, m_shutdown_controller(static_cast<void*>(0), bind(&pool_core_type::shutdown, m_core))
{
size_policy_type::init(*m_core, initial_threads);
}
thread_pool(size_t initial_threads = 0)
: m_core(new pool_core_type)
, m_shutdown_controller(static_cast<void*>(0), bind(&pool_core_type::shutdown, m_core))
{
size_policy_type::init(*m_core, initial_threads);
}
由于pimpl模式,所以所有代码都封装在m_core内实现的。
pool默认的线程个数为0,通过size_policy_type::init来初始化。
而size_policy_type是一个模板参数,pool对应的是fifo,所以也就是static_size类型了。
//static_size类的init函数
view plaincopy to clipboardprint?
static void init(Pool& pool, size_t const worker_count)
{
pool.resize(worker_count);
}
static void init(Pool& pool, size_t const worker_count)
{
pool.resize(worker_count);
}
//pool_core的resize函数
这个函数有点长,主要是做动态配置线程个数的逻辑操作,create_and_attach也是在这里调用的。
view plaincopy to clipboardprint?
//worker_thread的create_and_attach函数
static void create_and_attach(shared_ptr<pool_type> const & pool)
{
shared_ptr<worker_thread> worker(new worker_thread(pool));
if(worker)
{
//run是线程的loop函数
worker->m_thread.reset(new boost::thread(bind(&worker_thread::run, worker)));
}
}
//worker_thread的create_and_attach函数
static void create_and_attach(shared_ptr<pool_type> const & pool)
{
shared_ptr<worker_thread> worker(new worker_thread(pool));
if(worker)
{
//run是线程的loop函数
worker->m_thread.reset(new boost::thread(bind(&worker_thread::run, worker)));
}
}
view plaincopy to clipboardprint?
//worker_thread的run函数
void run()
{
scope_guard notify_exception(bind(&worker_thread::died_unexpectedly, this));
while(m_pool->execute_task()) {} //loop直到返回值为false
notify_exception.disable();
m_pool->worker_destructed(this->shared_from_this());
}
//worker_thread的run函数
void run()
{
scope_guard notify_exception(bind(&worker_thread::died_unexpectedly, this));
while(m_pool->execute_task()) {} //loop直到返回值为false
notify_exception.disable();
m_pool->worker_destructed(this->shared_from_this());
}
//pool_core的execute_task函数
这个函数有点长,简单点说,就是从队列中获取task然后执行,如果队列为空,则线程需要wait操作。
由于threadpool支持动态resize线程个数,从该函数我们也是可以看出来是如何做到的。
view plaincopy to clipboardprint?
// decrease number of threads if necessary
if(m_worker_count > m_target_worker_count)
{
return false; // terminate worker
}
// decrease number of threads if necessary
if(m_worker_count > m_target_worker_count)
{
return false; // terminate worker
}
pool内部使用了多个整数来记录现在个数,譬如m_worker_count和m_target_worker_count。
m_worker_count是当前激活运行中的线程个数。
m_target_worker_count是最新动态配置的线程个数。
当个数不匹配时,通过返回false方式结束线程。
// Add some tasks to the pool.
tp.schedule(&first_task);
view plaincopy to clipboardprint?
//thread_pool的schedule函数
bool schedule(task_type const & task)
{
return m_core->schedule(task);
}
//pool_core的schedule函数(和execute_task函数强相关)
bool schedule(task_type const & task) volatile
{
locking_ptr<pool_type, recursive_mutex> lockedThis(*this, m_monitor);
if(lockedThis->m_scheduler.push(task))
{
//task成功入队列后,notify_one一个线程。
lockedThis->m_task_or_terminate_workers_event.notify_one();
return true;
}
else
{
return false;
}
}
//thread_pool的schedule函数
bool schedule(task_type const & task)
{
return m_core->schedule(task);
}
//pool_core的schedule函数(和execute_task函数强相关)
bool schedule(task_type const & task) volatile
{
locking_ptr<pool_type, recursive_mutex> lockedThis(*this, m_monitor);
if(lockedThis->m_scheduler.push(task))
{
//task成功入队列后,notify_one一个线程。
lockedThis->m_task_or_terminate_workers_event.notify_one();
return true;
}
else
{
return false;
}
}
// Wait until all tasks are finished.
tp.wait();
//pool_core的wait函数
void wait(size_t const task_threshold = 0) const volatile
bool wait(xtime const & timestamp, size_t const task_threshold = 0) const volatile
wait函数是一个阻塞操作,内部逻辑实现使用了一个条件变量,提供超时等待方式。
二、boost线程池使用实例
线程池可以减少创建和切换线程的额外开销,利用已经存在的线程多次循环执行多个任务从而提高系统的处理能力,有关线程池的概念可google搜索,下面将其使用实例:
#include <sstream>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
using namespace boost::threadpool;
//
// Helpers
boost::mutex m_io_monitor;
{
boost::mutex::scoped_lock lock(m_io_monitor);//每个线程使用全局互斥来保证每次只有一个线程执行
cout << text;
}
string to_string(T const & value)
{
ostringstream ost;
ost << value;
ost.flush();
return ost.str();
}
// An example task functions
void task_1()
{
print(" task_1()/n");
}
{
print(" task_2()/n");
}
{
print(" task_3()/n");
}
{
print(" task_4()/n");
return 4;
}
{
print(" task_with_parameter(" + to_string(value) + ")/n");
}
bool looped_task()
{
print(" looped_task()/n");
return ++loops < 5;
}
int task_int()
{
print(" task_int()/n");
return 23;
}
void fifo_pool_test()
{
pool tp;
tp.schedule(boost::bind(task_with_parameter, 4));
{
tp.clear(); // remove all tasks -> no output in this test
}
size_t pending_threads = tp.pending();
size_t total_threads = tp.size();
dummy++;
tp.wait();
}
{
lifo_pool tp;
tp.size_controller().resize(0);
schedule(tp, &task_1);
tp.size_controller().resize(10);
tp.wait();
}
{
prio_pool tp(2);
schedule(tp, prio_task_func(1, &task_1));
schedule(tp, prio_task_func(10,&task_2));
}
void future_test()
{
fifo_pool tp(5);
future<int> fut = schedule(tp, &task_4);
int res = fut();
}
int main (int , char * const [])
{
fifo_pool_test();
lifo_pool_test();
prio_pool_test();
future_test();
return 0;
}
任务返回值的获取:
一般异步调用中,返回值的获取有同步获取和异步获取两种形式。
同步获取返回值:
int task_int_23()
{
cout<<"task_int_23()/n";
return 23;
}
future<int> res = schedule(tp, &task_int_23);
res.wait();
cout<<"get res value:"<<res.get()<<endl;
异步获取返回值:
不知道是设计者就不打算使用异步回调获取返回值还是我看的不够仔细,异步获取返回值的方式还真没有找着,只好自己简单的写了一个回调的仿函数来实现异步返回值的获取。
//R为任务函数的返回值类型
template<class R>
class callback_task
{
typedef boost::function<void (R)> callback;
typedef boost::function<R ()> function;
private:
callback c_;
function f_;
public:
//F: 任务执行函数 C:结果回调函数
template<class F,class C>
callback_task(F f,C c)
{
f_ = f;
c_ = c;
}
void operator()()
{
c_(f_());
}
};
通过这个对象可以很容易的实现异步结果的回调。
//task_int_23的结果回调函数
void callback(int k)
{
cout<<"get callback value:"<<k<<endl;
}
//通过回调的形式获取任务的返回值
tp.schedule(callback_task<int>(&task_int_23,&callback));
usidc52012-03-06 20:201.简介
condition是一个简单的同步对象,用于使一个线程等待一个特定的条件成立(比如
资源可用)。一个condition对象总是和一个mutex对象配合使用。mutex在交给condition
对象的wait系列函数时,必须已经通过lock对象加上了锁。当线程陷入等待时,condtion
对象将释放mutex上的锁,当wait返回时,mutex上的锁会重新加上,这一unlock/lock
动作由conditon对象自动完成。
2.使用
namespace boost
{
class condition : private boost::noncopyable // Exposition only.
// Class condition meets the NonCopyable requirement.
{
public:
condition();
~condition();
void notify_one();
// 唤起等待队列中的一个线程
void notify_all();
// 唤起等待队列中的所有线程
template <typename Lock> void wait(Lock& lock);
// ScopedLock 是一个lock对象,符合ScopedLock 概念
// 释放lock中mutex上的锁,阻塞该线程,直到任何人调用了this->notify_one()或
// this->notify_all(),然后重新lock mutex。
template <typename Lock, typename Predicate>
void void wait(Lock& lock, Predicate pred);
// ScopedLock 是一个lock对象,符合ScopedLock 概念
// 相当于while (!pred()) wait(lock)。
template <typename Lock>
bool timed_wait(Lock& lock, const xtime& xt);
// wait(Lock& lock)的限时版,当XT到达时,函数返回false,当因notify而返回时
// 函数返回true
template <typename Lock, typename Predicate>
bool timed_wait(Lock& lock, const xtime& XT, Predicate pred);
// wait(Lock& lock, Predicate pred)的限时版,当XT到达时,函数返回false,当
// 因notify和pred而而返回时函数返回true
};
};
3.例子
一个经典的消息队列的实现
#include <iostream>
#include <vector>
#include <boost/utility.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>
class bounded_buffer : private boost::noncopyable
{
public:
typedef boost::mutex::scoped_lock lock;
bounded_buffer(int n) : begin(0), end(0), buffered(0), circular_buf(n) { }
void send (int m) { // 加入消息
lock lk(monitor);
while (buffered == circular_buf.size())
buffer_not_full.wait(lk);
circular_buf[end] = m;
end = (end+1) % circular_buf.size();
++buffered;
buffer_not_empty.notify_one();
}
int receive() { // 取走消息
lock lk(monitor);
while (buffered == 0)
buffer_not_empty.wait(lk);
int i = circular_buf[begin];
begin = (begin+1) % circular_buf.size();
--buffered;
buffer_not_full.notify_one();
return i;
}
private:
int begin, end, buffered;
std::vector<int> circular_buf;
boost::condition buffer_not_full, buffer_not_empty;
boost::mutex monitor;
};
usidc52012-03-06 20:27
http://www.cppblog.com/fwxjj/archive/2006/12/04/15975.html
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
#include <iostream>
int number;
boost::mutex m;
boost::condition not_full;
boost::condition not_empty;
void writer()
{
while (1) {
boost::mutex::scoped_lock sl(m);
if (number == 5) {
not_full.wait(m);
}
++number;
std::cout << "after w: " << number << std::endl;
not_empty.notify_one();
}
}
void reader()
{
while (1) {
boost::mutex::scoped_lock sl(m);
if (number == 0) {
not_empty.wait(m);
}
--number;
std::cout << "after r: " << number << std::endl;
not_full.notify_one();
}
}
void main()
{
boost::thread trd1(&writer);
boost::thread trd2(&reader);
trd1.join();
trd2.join();
}
void main()
{
boost::thread trd1(&writer);
boost::thread trd11(&writer);
boost::thread trd2(&reader);
trd1.join();
trd11.join();
trd2.join();
}
while (number == 5) {
not_full.wait(m);
}
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/xtime.hpp>
#include <iostream>
int number;
boost::mutex m;
boost::condition not_full;
boost::condition not_empty;
void writer()
{
while (1) {
boost::mutex::scoped_lock sl(m);
while (number == 5) {
not_full.wait(m);
}
++number;
std::cout << "after w: " << number << std::endl;
not_empty.notify_one();
}
}
void reader()
{
while (1) {
boost::mutex::scoped_lock sl(m);
while (number == 0) {
not_empty.wait(m);
}
--number;
std::cout << "after r: " << number << std::endl;
not_full.notify_one();
}
}
void main()
{
boost::thread trd1(&writer);
boost::thread trd11(&writer);
boost::thread trd2(&reader);
boost::thread trd22(&reader);
trd1.join();
trd11.join();
trd2.join();
trd22.join();
}
usidc52012-03-07 16:10译自http://www.boost.org/doc/libs/1_48_0/doc/html/thread.html
Anthony Williams
Copyright© 2007-8 Anthony Williams
Distributed under the Boost Software License,Version 1.0.(See accompanying file LICENSE_1_0.txt or copy athttp://www.boost.org/LICENSE_1_0.txt)
1 概述
Boost.Thread使得可移植的、含有多线程执行和共享数据的C++代码成为可能。它提供了用于管理线程本身,以及在线程间同步数据,或者为特定线程提供数据的单独副本的类和函数。
Boost.Thread原来由William E.Kempf设计和编写。当前版本进行了重写,以更好地遵循C++标准委员会发布的提案,特别是N2497、N2320、N2184、N2139和N2094。
要使用下面将描述的类和函数,可以包含每个类或者函数的描述中指定的头文件,也可以包含线程库的主要头文件:
这个文件包含了所有其他头文件。
2 自boost 1.40版本以来的修改
Boost 1.41.0版在线程库中增加了future,还做了其他一些小的修改。
2.1 自boost 1.35版本以来的修改
1.36.0版的Boost在线程库中增加了一些新的特征:
l 新的通用lock()和try_lock()函数可一次锁定多个互斥量
l 只要编译器支持,右值引用就可支持移动语义
l 一些Bug修正和增加遗漏的函数(包括严重的Win32条件变量bug)
l scoped_try_lock类型现在向后兼容1.34.0以及更早版本的Boost
l 支持为boost::thread构造函数提供额外参数,以传递给线程函数作为函数参数
l 为timed_lock和timed_wait函数增加了向后兼容性,以允许为超时值使用xtime
2.2 自boost 1.34版本以来的修改
从1.34版本发布以来,几乎Boost.Thread的每一行代码都经过了修改。但是,大多数接口修改都经过扩展,新代码基本上向后兼容老的代码。新特征和打破了兼容性的修改如下:
2.2.1 新特征
l boost::thread和各种锁类型的实例现在是可移动的(movable)
l 可以在中断点(interruption points)中断线程
l 通过使用boost::condition_variable_any,条件变量可以用于任何实现了Lockable概念的类型了(现在boost::condition是boost::condition_variable_any的typedef,以提供向后兼容性)。boost::condition_variable作为一个优化而提供,仅可用于boost::unique_lock<boost::mutex>(boost::mutex::scoped_lock)。
l 从boost::thread中分离出了线程ID,所以线程可以获取自身的ID(使用boost::this_thread::get_id())。线程ID拥有完整的比较操作符集合,可用作关联容器的关键字。
l 超时现在通过Boost日期时间库实现,通过boost::system_time的typedef支持绝对超时时间,还支持多种类型的相对超时时间。而对boost::xtime的支持仅用于向后兼容。
l 锁通过公共模板boost::lock_guard、boost::unique_lock、boost::shard_lock和boost::upgrade_lock实现,它们都以互斥量类型为模板。Lockable概念已经扩展,包括公共可用的lock()和unlock()成员函数,它们都被锁类型使用。
2.2.2 打破了兼容性的修改
下表列出了公共接口中所有打破了向后兼容性的修改:
l 移除了boost::try_mutex,归纳到boost::mutex.boost::try_mutex中的功能只是一个typedef,不再是一个单独的类。
l 移除了boost::recursive_try_mutex,归纳到boost::recursive_mutex中了。boost::recursive_try_mutex中的功能只是一个typedef,不再是一个单独的类。
l 移除了boost::detail::thread::lock_ops。依赖于lock_ops实现细节的代码将不能工作了,而互斥量类型不再需要lock()和unlock()成员函数了。
l 不再提供第二个参数类型为bool的scoped_lock构造函数。在以前的boost版本中,
将创建一个与互斥量关联的锁对象,但是在构造的时候不进行锁定。这个功能现在由第二个参数类型为boost::defer_lock_type的构造函数实现:
l scoped_lock类型的locked()成员函数名字改成了owns_lock()。
l 不能再获取代表当前线程的boost::thread实例了:默认构造的boost::thread对象不与任何线程关联。这种类型的线程对象仅仅用于支持比较运算符:而这个功能已经移动到boost::thread::id中了。
l boost::read_write_mutex被boost::shared_mutex取代。
l boost::mutex不可能是递归的了。在1.35版之前,boost::mutex在Windows中是递归的,但在POSIX中不是。
l 在boost::condition_variable_any::wait()中使用boost::recursive_mutex时,互斥量仅被解锁一层,而不是完全解锁。以前的版本不能保证这种行为,也没有进行过测试。
3 线程管理
3.1 概述
类boost::thread用于启动和管理线程。每个boost::thread对象代表单个执行中的线程,或者Not-a-Thread。一个执行中的线程只能由一个boost::thread对象来代表:boost::thread对象是不可复制的。
然而,boost::thread对象是可移动的,可以被存储到能够感知移动的容器中;也可以从函数中返回boost::thread对象,从而可以将线程创建的细节包装到函数中:
注意:
在支持右值引用的编译器中,boost::thread提供了移动构造函数和移动赋值运算符,满足C++0x的可移动构造和可移动赋值概念。因而,使用这样的编译器时,可将boost::thread用于支持这些概念的容器中。
对于不支持右值引用的编译器,移动支持由移动模拟层提供,容器必须显式探测移动模拟层。细节请看<boost/thread/detail/move.hpp>。
3.2 启动线程
传递一个可以不带参数调用的可调用类型对象给boost::thread构造函数,就可以启动一个新的线程。boost::thread对象被复制到内部存储中,然后在新创建的线程中被调用。如果对象不必(或者不能)是可复制的,则可使用boost::ref来传递一个引用给函数对象。这种情况下,boost.thread用户必须保证被引用对象的生存期包含新创建线程的生存期。
如果想用要求提供参数的函数或者可调用对象来构造boost::thread实例,则可为boost::thread构造函数提供额外参数:
参数被复制到线程内部结构中:如果要求引用,请使用boost::ref,就像使用可调用函数的引用一样。
可以传递的额外参数个数是没有限制的。
3.3 线程函数中的异常
如果传递给boost::thread构造函数的函数或者可调用对象传播boost::thread_interrupted类型之外的异常,则std::terminate()会被调用。
3.4 连接和分离
代表线程的boost::thread对象销毁的时候,线程成为已分离的。线程分离后将继续执行,直到提供给boost::thread构造函数的函数或者可调用对象执行完成,或者程序被终止。显式调用boost::thread对象的detach()成员函数也可以分离线程。这时,boost::thread对象不再代表已经分离的线程,而是代表Not-a-Thread。
可以使用boost::thread对象的join()或者timed_join()成员函数来等待线程执行完成。join()会阻塞调用线程,直到boost::thread对象代表的线程执行完成。如果boost::thread对象代表的线程已经执行完成,或者对象代表Not-a-Thread,则join()会立即返回。timed_join()的行为是类似的,只是timed_join()也会在指定的时间流逝后,而被等待线程还没有完成时返回。
3.5 中断
调用boost::thread对象的interrupt()成员函数可以中断运行中的线程。如果启用了中断,下次执行某特定的中断点(或者当前正在执行某中断点)时,被中断线程将抛出一个boost::thread_interrupted异常。如果不捕获这个异常,则被中断线程会被终止。和其他异常处理一样,线程栈会被展开,自动类型的对象的析构函数会被执行。
如果要避免被中断,线程可以创建一个boost::this_thread::disable_interruption实例。这种类型的对象会在构造函数中为创建它的线程禁止中断,在析构函数中恢复中断状态:
构造一个boost::this_thread::restore_interruption实例,传入一个boost::this_thread::disable_interruption对象,可以暂时撤销传入的boost::this_thread::disable_interruption对象的效果。这会恢复中断状态到boost::this_thread::disable_interruption构造时的状态,然后在boost::this_thread::restore_interruption对象销毁的时候再次禁止中断。
3.6 预定义的中断点
下述函数是中断点,如果当前线程开启了中断,并且当前请求了中断,则会抛出boost::thread_interrupted异常:
3.7 线程ID
boost::thread::id对象用于标识线程。调用boost::thread对象的get_id()成员函数可以获取线程的唯一ID,也可以在线程内调用boost::this_thread::get_id()来获取自身的ID。boost::thread::id对象是可复制的,可用于组合容器中,因为它提供了所有比较运算符。也可使用流插入符将线程ID写入到输出流中,尽管输出格式是没有指定的。
每个boost::thread::id实例要么代表某线程,要么代表Not-a-Thread。每个代表Not-a-Thread的实例都是相等的,但是不等于任何代表实际线程的实例。
usidc52012-03-07 16:13Anthony Williams
Copyright© 2007-8 Anthony Williams
Distributed under the Boost Software License,Version 1.0.(See accompanying file LICENSE_1_0.txt or copy athttp://www.boost.org/LICENSE_1_0.txt)
4 同步
4.1 互斥概念
互斥对象让数据竞争保护变得容易,使得对数据的线程安全同步成为可能。线程通过调用互斥对象的某个锁函数来获取所有权,通过调用相应的解锁函数来释放所有权。互斥量可以是递归的,也可以是非递归的,并且可能给一个或者多个线程同时授予所有权。Boost.Thread提供了带独占所有权语义的递归和非递归的互斥量,以及共享所有权(多个读/单个写)互斥量。
Boost.Thread支持四种基本的可锁定对象概念:Lockable、TimedLockable、SharedLockable和UpgradeLockable。每个互斥量类型和各种锁类型实现了这些概念中的一个或者多个。
4.1.1 Lockable概念
Lockable概念是独占所有权的模型。实现Lockable概念的类型需要提供下述成员函数:
必须调用unlock()来释放调用lock()或者try_lock()取得的所有权。
void lock()
n 作用:阻塞当前线程,直到可以获取所有权。
n 后置条件:当前线程拥有*this
n 异常:发生错误时抛出boost::thread_resource_error。
bool try_lock()
n 作用:试图为当前线程获取所有权,不阻塞。
n 返回:如果当前线程取得了所有权则返回true,否则返回false。
n 后置条件:如果返回true,当前线程拥有*this。
n 异常:发生错误时抛出boost::thread_resource_error。
void unlock()
n 前提条件:当前线程拥有*this。
n 作用:释放当前线程的所有权。
n 后置条件:当前线程不再拥有*this。
n 异常:无。
4.1.2 TimedLockable概念
TimedLockable概念改进了Lockable概念,增加了试图获取锁时的超时支持。
实现TimedLockable概念的类型必须满足Lockable概念的要求。此外还必须提供下述函数:
必须调用unlock()来释放调用timed_lock()取得的所有权。
bool timed_lock(boost::system_time const & abs_time)
n 作用:试图为当前线程获取所有权。阻塞当前线程,直到可以取得所有权,或者经过指定的时间。如果已经经过指定的时间,则行为同try_lock()。
n 返回:如果当前线程取得了所有权则返回true,否则返回false。
n 后置条件:如果返回true,当前线程拥有*this。
n 异常:发生错误时抛出boost::thread_resource_error。
template<typename DurationType> bool timed_lock(DurationType const& rel_time)
n 作用:等同于timed_lock(boost::get_system_time() + rel_time)
4.1.3 SharedLockable概念
SharedLockable概念改进了TimedLockable概念,同时支持独占所有权和共享所有权。采用的是标准多个读/单个写模型:至多只有一个线程拥有独占所有权,如果任何线程拥有独占所有权,则没有其他线程可以拥有共享或者独占所有权。或者,多个线程可以同时拥有共享所有权。
实现SharedLockable概念的类型除了要满足TimedLockable概念的要求外,还必须提供下述成员函数:
必须调用unlock_shared()释放调用lock_shared()、try_lock_shared()或者timed_lock_shared()取得的所有权。
void lock_shared()
n 作用:阻塞当前线程,直到可以取得所有权。
n 后置条件:当前线程拥有*this的共享所有权。
n 异常:发生错误时抛出boost::thread_resource_error。
bool try_lock_shared()
作用:试图为当前线程获取共享所有权,不阻塞。
n 返回:如果当前线程取得了所有权则返回true,否则返回false。
n 后置条件:如果返回true,当前线程拥有*this的共享所有权。
n 异常:发生错误时抛出boost::thread_resource_error。
bool timed_lock_shared(boost::system_time const& abs_time)
n 作用:试图为当前线程获取所有权。阻塞当前线程,直到可以取得所有权,或者经过指定的时间。如果已经经过指定的时间,则行为同try_lock_shared()。
n 返回:如果当前线程取得了共享所有权则返回true,否则返回false。
n 后置条件:如果返回true,当前线程拥有*this的共享所有权。
n 异常:发生错误时抛出boost::thread_resource_error。
void unlock_shared()
n 前提条件:当前线程拥有*this的共享所有权。
n 作用:释放当前线程对*this的共享所有权。
n 后置条件:当前线程不再拥有*this的共享所有权。
n 异常:无
4.1.4 UpgradeLockable概念
UpgradeLockable概念改进了SharedLockable概念,除了支持独占所有权和共享所有权之外,还支持可升级的所有权。这是对SharedLockable概念提供的多个读/单个写模型的扩展:多个线程拥有共享所有权的时候,单个线程可拥有可升级的所有权。拥有可升级所有权的线程可以在任何时候试图升级到独占所有权。如果此时没有其他线程拥有共享所有权,则升级会立即完成,线程拥有独占所有权。必须调用unlock()来释放取得的独占所有权,就像释放调用lock()获取的独占所有权一样。
如果拥有可升级所有权的线程在试图升级的时候,有其他线程拥有共享所有权,则升级企图会失败,线程被阻塞,直到可以取得独占所有权。
就像可以升级一样,所有权也可以降级:UpgradeLockable概念实现的独占所有权可以降级成可升级所有权或者共享所有权,可升级所有权可以降级成共享所有权。
实现UpgradeLockable概念的类型除了要满足SharedLockable概念的要求之外,还必须提供下述成员函数:
必须调用unlock_upgrade()来释放调用lock_upgrade()取得的所有权。如果调用unlock_xxx_and_lock_yyy()函数改变了所有权类型,则必须调用新的所有权等级对应的解锁函数来释放所有权。
void lock_upgrade()
n 作用:阻塞当前线程,直到取得可升级所有权。
n 后置条件:当前线程拥有*this的可升级所有权。
n 异常:发生错误时抛出boost::thread_resource_error。
void unlock_upgrade()
n 前提条件:当前线程拥有*this的可升级所有权。
n 作用:释放当前线程的可升级所有权。
n 后置条件:当前线程不再拥有*this的可升级所有权。
n 异常:无。
void unlock_upgrade_and_lock()
n 前提条件:当前线程拥有*this的可升级所有权。
n 作用:原子地释放当前线程拥有的对*this的可升级所有权,获取对*this的独占所有权。如果有其他线程当前拥有共享所有权,则阻塞直到可以取得独占所有权。
n 后置条件:当前线程拥有*this的独占所有权。
n 异常:无。
void unlock_upgrade_and_lock_shared()
n 前提条件:当前线程拥有*this的可升级所有权。
n 作用:原子地释放当前线程拥有的对*this的可升级所有权,请求共享所有权,不阻塞。
n 后置条件:当前线程拥有*this的共享所有权。
n 异常:无。
void unlock_and_lock_upgrade()
n 前提条件:当前线程拥有*this的独占所有权。
n 作用:原子地释放当前线程拥有的对*this的独占所有权,请求可升级所有权,不阻塞。
n 后置条件:当前线程拥有*this的可升级所有权。
n 异常:无。
4.2 锁类型
4.2.1 lock_guard模板类
boost::lock_guard非常简单:构造函数请求获取参数提供的Lockable概念实现的所有权。析构函数释放所有权。这提供了简单的对Lockable对象的RAII风格的锁,使得线程安全的锁定和解锁变得容易。此外,构造函数lock_guard(Lockable & m,boost::adopt_lock_t)可获取已经由当前线程持有的锁的所有权。
lock_guard(Lockable & m)
n 作用:存储对m的引用,调用m.lock()。
n 异常:抛出由m.lock()产生的异常。
lock_guard(Lockable & m,boost::adopt_lock_t)
n 前提条件:当前线程拥有等价于调用m.lock()取得的、对m的锁。
n 作用:存储对m的引用,取得对m锁定状态的所有权。
n 异常:无。
~lock_guard()
n 作用:对构造函数传入的Lockable对象调用unlock()。
n 异常:无。
4.2.2 unique_lock模板类
boost::unique_lock比boost::lock_guard更复杂:因为它不仅仅提供一个RAII风格的锁,还允许延迟锁定请求到显式调用成员函数lock()的时候,允许试图以非阻塞的方式请求锁,允许带有请求超时。结果,只有在锁对象已经锁住Lockable对象时,析构函数才会调用unlock(),或者采用Lockable对象上的锁。
如果提供的Lockable类型模型化了TimedLockable概念(如boost::unique_lock<boost::timed_mutex>)或者Lockable概念(boost::unique_lock<boost::mutex>),则特化的boost::unique_lock也模型化了TimedLockable概念或者Lockable概念。
如果一个boost::unique_lock实例的mutex()函数返回指向Lockable m的指针,owns_lock()返回true,则称这个实例是拥有Lockable m的锁状态的。拥有Lockable对象锁状态的对象销毁时,其析构函数会调用mutex()->unlock()。
boost::unique_lock的成员函数不是线程安全的。特别是,boost::unique_lock是设计用于模型化某特定线程对某Lockable对象的所有权的,释放锁状态所有权的成员函数(包括析构函数)必须由获取锁状态的相同线程调用。
unique_lock()
n 作用:创建一个没有与互斥量关联的锁对象。
n 后置条件:owns_lock()返回false,mutext()返回NULL。
n 异常:无。
unique_lock(Lockable & m)
n 作用:存储m的引用,调用m.lock()。
n 后置条件:owns_lock()返回true,mutex()返回&m。
n 异常:m.lock()抛出的任何异常。
unique_lock(Lockable& m,boost::adopt_lock_t)
n 前提条件:当前线程拥有m的独占锁。
n 作用:存储m的引用,取得m的锁状态的所有权。
n 后置条件:owns_lock()返回true,mutex()返回&m。
n 异常:无。
unique_lock(Lockable& m,boost::defer_lock_t)
n 作用:存储m的引用。
n 后置条件:owns_lock()返回false,mutex()返回&m。
n 异常:无。
unique_lock(Lockable & m,boost::try_to_lock_t)
n 作用:存储m的引用,调用m.try_lock(),如果返回true,则取得锁状态的所有权。
n 后置条件:mutex()返回&m。如果try_lock()返回true,则owns_lock()返回true,否则owns_lock()返回false。
n 异常:无。
unique_lock(Lockable& m,boost::system_time const & abs_time)
n 作用:存储m的引用,调用m.timed_lock(abs_time),如果返回true,则取得锁状态的所有权。
n 后置条件:mutex()返回&m。如果timed_lock()返回true,则owns_lock()返回true,否则owns_lock()返回false。
n 异常:任何由m.timed_lock(abs_time)抛出的异常。
~unique_lock()
n 作用:如果owns_lock()返回true,则调用mutex()->unlock()。
n 异常:无。
bool owns_lock() const
n 返回:如果*this拥有与自身关联的Lockable对象的锁,则返回true。
n 异常:无。
Lockable* mutex() const
n 返回:与*this关联的Lockable对象的指针,或者,没有关联的对象时,返回NULL。
n 异常:无。
operator unspecified_bool_type() const
n 返回:如果owns_lock()返回true,则返回一个在布尔上下文中与true等价的值,否则返回一个在布尔上下文中与false等价的值。
n 异常:无。
bool operator!() const
n 返回:!owns_lock()
n 异常:无。
Lockable* release()
n 作用:移除*this与Lockable对象的关联,而不影响Lockable对象的锁状态。如果owns_lock()返回true,则确保正确解锁Lockable对象是调用代码的责任。
n 返回:返回调用时与*this关联的Lockable对象指针,或者,没有关联的对象时返回NULL。
n 异常:无。
n 后置条件:*this不再与任何Lockable对象关联。mutex()返回NULL,owns_lock()返回false。
4.2.3 shared_lock模板类
与boost::unique_lock一样,boost::shared_lock模型化Lockable概念,不同的是,boost::shared_lock请求获取指定的Lockable对象的共享所有权,而不是唯一所有权。锁定一个boost::shared_lock实例请求的是共享所有权。
与boost::unique_lock不仅仅提供RAII风格的锁一样,boost::shared_lock也允许延迟锁定到显式调用成员函数lock()时,允许以非阻塞的方式试图获取锁,允许带有超时。结果,只有在锁对象已经锁住Lockable对象的时候,析构函数才会调用unlock(),否则则采用Lockable对象上的锁。
如果一个boost::shared_lock实例的mutex()函数返回指向Lockable m的指针,owns_lock()返回true,则称这个实例是拥有Lockable m的锁状态的。拥有Lockable对象锁状态的对象销毁时,其析构函数会调用mutex()->unlock_shared()。
boost::shared_lock的成员函数不是线程安全的。特别是,boost::shared_lock是设计用于模型化某特定线程对某Lockable对象的共享所有权的,释放锁状态所有权的成员函数(包括析构函数)必须由获取锁状态的相同线程调用。
shared_lock()
n 作用:创建一个没有与互斥量关联的锁对象。
n 后置条件:owns_lock()返回false,mutex()返回NULL。
shared_lock(Lockable& m)
n 作用:存储m的引用,调用m.lock_shared()。
n 后置条件:owns_lock()返回true,mutex()返回&m。
n 异常:m.lock_shared()抛出的任何异常。
shared_lock(Lockable& m,boost::adopt_lock_t)
n 前提条件:当前线程拥有m的独占锁。
n 作用:存储m的引用,取得m锁状态的所有权。
n 后置条件:owns_lock()返回true,mutex()返回&m。
n 异常:无。
shared_lock(Lockable& m,boost::defer_lock_t)
n 作用:存储m的引用。
n 后置条件:owns_lock()返回false,mutex()返回&m。
n 异常:无。
shared_lock(Lockable& m,boost::try_to_lock_t)
n 作用:存储m的引用,调用m.try_lock_shared(),如果返回true,则取得锁状态的所有权。
n 后置条件:mutex()返回&m。如果try_lock_shared()返回true,则owns_lock()返回true,否则owns_lock()返回false。
n 异常:无。
shared_lock(Lockable& m,boost::system_time const & abs_time)
n 作用:存储m的引用,调用m.timed_lock(abs_time),如果返回true,则取得锁状态的所有权。
n 后置条件:mutex()返回&m。如果timed_lock_shared()返回true,则owns_lock()返回true,否则owns_lock()返回false。
n 异常:m.timed_lock(abs_time)返回的任何异常。
~shared_lock()
n 作用:如果owns_lock()返回true,则调用mutex()->unlock_shared()。
n 异常:无。
bool owns_lock() const
n 返回:如果拥有与*this关联的Lockable对象的锁,则返回true。
n 异常:无。
Lockable* mutex() const
n 返回:与*this关联的Lockable对象的指针,或者,没有关联的对象时,返回NULL。
n 异常:无。
operator unspecified_bool_type() const
n 返回:如果owns_lock()返回true,则返回一个在布尔上下文中与true等价的值,否则返回一个在布尔上下文中与false等价的值。
n 异常:无。
bool operator!() const
n 返回:!owns_lock()
n 异常:无。
Lockable* release()
n 作用:移除*this与Lockable对象的关联,而不影响Lockable对象的锁状态。如果owns_lock()返回true,则确保正确解锁Lockable对象是调用代码的责任。
n 返回:返回调用时与*this关联的Lockable对象指针,或者,没有关联的对象时返回NULL。
n 异常:无。
n 后置条件:*this不再与任何Lockable对象关联。mutex()返回NULL,owns_lock()返回false。
4.2.4 upgrade_lock模板类
与boost::unique_lock一样,boost::upgrade_lock模型化Lockable概念,但不是请求Lockable对象的唯一所有权,而是请求可升级所有权。
与boost::unique_lock不仅仅提供RAII风格的锁一样,boost::upgrade_lock允许延迟请求锁定到显式调用成员函数lock()时,允许以非阻塞方式试图获取锁,允许带有超时。结果,只有在锁对象已经锁住Lockable对象的时候,析构函数才会调用unlock(),否则则采用Lockable对象上的锁。
如果一个boost::upgrade_lock实例的mutex()函数返回指向Lockable m的指针,owns_lock()返回true,则称这个实例是拥有Lockable m的锁状态的。拥有Lockable对象锁状态的对象销毁时,析构函数会调用mutex()->unlock_upgrade()。
boost::upgrade_lock的成员函数不是线程安全的。特别是,boost::upgrade_lock是设计用于模型化某特定线程对某Lockable对象的可升级所有权的,释放锁状态所有权的成员函数(包括析构函数)必须由获取锁状态的相同线程调用。
4.2.5 upgrade_to_unique_lock
boost::upgrade_to_unique_lock允许临时升级boost::upgrade_lock到独占所有权。构造boost::upgrade_to_unique_lock的时候,如果使用的boost::upgrade_lock引用拥有某Lockable对象的可升级所有权,则会升级到独占所有权。销毁boost::upgrade_to_unique_lock实例时,对Lockable对象的所有权降级到可升级所有权。
4.2.6 互斥量特定的类scoped_try_lock
为每个MutexType提供了作为成员typedef的scoped_try_lock。每个构造函数和成员函数的语义与具有相同MutexType的boost::unique_lock<MutextType>的相应函数相同,只是带有单个互斥量引用m的构造函数将调用m.try_lock(),而不是m.lock()。
4.3 锁函数
4.3.1 非成员函数lock(Lockable1,Lockable2,...)
n 作用:以可避免死锁的、非特定的、不确定的次序锁定作为参数提供的Lockable对象。以不同的次序在多个线程中并发地对相同互斥量(或者其他可锁定对象)调用这个函数是安全的,没用死锁的风险。如果对提供的任何Lockable对象的lock()或者try_lock()操作抛出异常,则函数退出前会释放所有先前已经取得的锁。
n 异常:对提供的Lockable对象调用lock()或者try_lock()时抛出的任何异常。
n 后置条件:提供的所有Lockable对象都被调用线程锁定。
4.3.2 非成员函数lock(begin,end)
n 前提条件:ForwardIterator的值类型必须实现了Lockable概念。
n 作用:以可避免死锁的、非特定的、不确定的次序锁定指定范围内的所有Lockable对象。以不同的次序在多个线程中并发地对相同互斥量(或者其他可锁定对象)调用这个函数是安全的,没用死锁的风险。如果对提供的任何Lockable对象的lock()或者try_lock()操作抛出异常,则函数退出前会释放所有先前已经取得的锁。
n 异常:对提供的Lockable对象调用lock()或者try_lock()时抛出的任何异常。
n 后置条件:指定范围内的所有Lockable对象都被调用线程锁定。
4.3.3 非成员函数try_lock(Lockable1,Lockable2,...)
n 作用:对作为参数提供的每个Lockable对象调用try_lock()。如果任何一个try_lock()返回false,则释放先前已经取得的所有锁,返回锁定失败对象的从零开始的索引。
n 如果对提供的任何Lockable对象的try_lock()操作抛出异常,则函数退出前会释放所有先前已经取得的锁。
n 异常:对提供的Lockable对象调用try_lock()时抛出的任何异常。
n 后置条件:如果返回-1,则提供的所有Lockable对象都被调用线程锁定。否则,任何已经被函数取得的锁都会被释放。
4.3.4 非成员函数try_lock(begin,end)
n 前提条件:ForwardIterator的值类型必须实现了Lockable概念。
n 作用:对指定范围内的每个Lockable对象调用try_lock()。如果任何一个try_lock()调用返回false,则任何已经取得的锁都会被释放,然后返回锁定失败的对象的迭代器。
n 如果对提供的任何Lockable对象的try_lock()操作抛出异常,则函数退出前会释放所有先前已经取得的锁。
n 返回:如果返回end,则指定范围内的所有Lockable对象都已经被调用线程锁定。否则,任何已经被函数取得的锁都会被释放。
4.4 互斥量类型
4.4.1 mutex类
boost::mutex实现了Lockable概念,提供一个独占所有权的互斥量。任何时刻只能有一个线程拥有某给定的boost::mutex的所有权。多个线程可以并发地调用lock()、try_lock()和unlock()。
成员函数native_handle()
n 作用:返回一个native_handle_type实例,可用于平台特定的API,以操作底层实现。如果没有这种实例,则native_handle()和native_handle_type是不存在的。
n 异常:无。
4.4.2 typedef try_mutex
boost::try_mutex是boost::mutex的typedef,以提供对先前的boost版本的向后兼容性。
4.4.3 timed_mutex
boost::timed_mutex实现了TimedLockable概念,提供一个独占所有权的互斥量。任何时刻只能有一个线程拥有某给定的boost::timed_mutex的所有权。多个线程可以并发地调用lock()、try_lock()和unlock()。
成员函数native_handle()
n 作用:返回一个native_handle_type实例,可用于平台特定的API,以操作底层实现。如果没有这种实例,则native_handle()和native_handle_type是不存在的。
n 异常:无。
4.4.4 recursive_mutex类
boost::recursive_mutex实现了Lockable概念,提供一个独占所有权的互斥量。任何时刻只能有一个线程拥有某给定的boost::recursive_mutex的所有权。多个线程可以并发地调用lock()、try_lock()和unlock()。已经拥有某boost::recursive_mutex实例的所有权的线程可以调用lock()和try_lock()以获取额外的所有权级别。对于单个线程取得的每个级别的所有权,都必须调用unlock(),只有这样,其他线程才可以取得所有权。
成员函数native_handle()
n 作用:返回一个native_handle_type实例,可用于平台特定的API,以操作底层实现。如果没有这种实例,则native_handle()和native_handle_type是不存在的。
n 异常:无。
4.4.5 recursive_try_mutex
boost::recursive_try_mutex是boost::recursive_mutex的typedef,以提供对先前的boost版本的向后兼容性。
4.4.6 recursive_timed_mutex类
boost::recursive_timed_mutex实现了TimedLockable概念,提供一个独占所有权的互斥量。任何时刻只能有一个线程拥有某给定的boost::recursive_timed_mutex实例的锁。多个线程可以并发地调用lock()、try_lock()、timed_lock()和unlock()。已经拥有某boost::recursive_timed_mutex实例的独占所有权的线程可以调用lock()、timed_lock()和try_lock()以获取额外的所有权级别。对于单个线程取得的每个级别的所有权,都必须调用unlock(),只有这样,其他线程才可以取得所有权。
成员函数native_handle()
n 作用:返回一个native_handle_type实例,可用于平台特定的API,以操作底层实现。如果没有这种实例,则native_handle()和native_handle_type是不存在的。
n 异常:无。
4.4.7 shared_mutex类
boost::shared_mutex实现了多个读/单个写的互斥量。它实现了UpgradeLockable概念。
多个线程可以并发地调用lock()、try_lock()、timed_lock()、lock_shared()、try_lock_shared()和timed_lock_shared()。
4.5 条件变量
4.5.1 概述
condition_variable和condition_variable_any提供了一种机制,让一个线程可以等待某特定条件成为true时来自另一个线程的通知。通常的使用模式是,某线程锁定一个互斥量,然后对一个condition_variable或者condition_variable_any实例调用wait。在等待中被唤醒时,线程检查条件是否是true,如果是,则继续执行。如果条件还不是true,线程再次调用wait,继续等待。在最简单的情况下,条件仅仅是一个布尔值:
注意传递给wait函数的lock:wait函数原子地将线程添加到等待条件变量的线程集中,并且解锁互斥量。线程被唤醒时,在wait函数返回前,互斥量会再次被锁定。这让其他线程可以取得互斥量,更新共享数据,并且保证与条件关联的数据是正确同步的。
同时,另一个线程设置条件为true,对条件变量调用notify_one或者notify_all,分别唤醒一个或者所有等待线程。
注意,更新共享数据前相同的互斥量被锁定,但是调用notify_one的时候不需要保持锁定。
这个例子使用了condition_variable类型的对象,但是使用condition_variable_any类型的对象也是可以的:condition_variable_any更通用,可以与任何类型的锁或者互斥量一同工作,而condition_variable要求传递给wait函数的是boost::unique_lock<boost::mutex>类型的实例。基于互斥量类型的知识,condition_variable可以在某些情况下进行一些优化;而condition_variable_any则通常有比condition_variable复杂的实现。
4.5.2 condition_variable类
condition_variable()
n 作用:构造一个condition_variable对象。
n 异常:发生错误时抛出boost::thread_resource_error。
~condition_variable()
前提条件:所有等待*this的线程已经被notify_one或者notify_all通知(但相应的wait或者timed_wait调用不必已经返回)
n 作用:销毁对象。
n 异常:无。
void notify_one()
n 作用:如果当前有任何线程阻塞在对*this的wait或者timed_wait调用上,解锁等待线程中的一个。
n 异常:无。
void notify_all()
n 作用:如果当前有任何线程阻塞在对*this的wait或者timed_wait调用上,解锁所有等待线程。
n 异常:无。
void wait(boost::unique_lock<boost::mutex>& lock)
n 前提条件:lock被当前线程锁定,并且没有其他线程当前在等待*this,或者在所有当前等待*this的线程中,提供给wait或者timed_wait调用的锁对象的mutex()成员函数,将与本次调用的lock->mutex()函数返回相同的值。
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。线程在被this->notify_one()或者this->notify_all()调用通知时解锁,或者假解锁。线程解锁后(无论何种原因),wait返回前,将再次调用lock.lock()请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
template<typename predicate_type>
void wait(boost::unique_lock<boost::mutex>& lock,predicate_type pred)
n 作用:等价于
bool timed_wait(boost::unique_lock<boost::mutex>& lock,boost::system_time const & abs_time)
n 前提条件:lock被当前线程锁定,并且没有其他线程当前在等待*this,或者在所有当前等待*this的线程中,提供给wait或者timed_wait调用的锁对象的mutex()成员函数,将与本次调用的lock->mutex()函数返回相同的值。
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。被this->notify_one()或者this->notify_all()调用通知时,或者boost::get_system_time()报告的时间等于或迟于abs_time指定的时间时,或者假解锁时,线程退出阻塞状态。线程退出阻塞状态时(无论何种原因),在wait调用返回之前,会调用lock.lock()再次请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 返回:如果因为到达abs_time指定的时间而返回,则返回false,否则返回true。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
template<typename duration_type>
bool timed_wait(boost::unique_lock<boost::mutex>& lock,duration_type const & rel_time)
n 前提条件:lock被当前线程锁定,并且没有其他线程当前在等待*this,或者在所有当前等待*this的线程中,提供给wait或者timed_wait调用的锁对象的mutex()成员函数,将与本次调用的lock->mutex()函数返回相同的值。
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。被this->notify_one()或者this->notify_all()调用通知时,或者参数rel_time指定的时间已经流逝时,或者假解锁时,线程退出阻塞状态。线程退出阻塞状态时(无论何种原因),在wait调用返回之前,会调用lock.lock()再次请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 返回:如果因为rel_time指定的时间已经流逝而返回,则返回false,否则返回true。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
n 注意:很难正确使用带有持续时间的timed_wait重载版本。多数情况下,使用带有断言的重载版本更好。
template<typename predicate_type>
bool timed_wait(boost::unique_lock<boost::mutex>& lock,boost::system_time const & abs_time,predicate_type pred)
n 作用:等价于
4.5.3 condition_variable_any类
condition_variable_any()
作用:构造一个condition_variable_any对象。
异常:发生错误时抛出boost::thread_resource_error。
~condition_variable_any()
前提条件:所有等待*this的线程已经被notify_one或者notify_all调用通知(但相应的wait或者timed_wait调用不必已经返回)
作用:销毁对象。
异常:无。
void notify_one()
n 作用:如果当前有任何线程阻塞在对*this的wait或者timed_wait调用上,解锁等待线程中的一个。
n 异常:无。
void notify_all()
n 作用:如果当前有任何线程阻塞在对*this的wait或者timed_wait调用上,解锁所有等待线程。
n 异常:无。
void wait(boost::unique_lock<boost::mutex>& lock)
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。线程在被this->notify_one()或者this->notify_all()调用通知时解除阻塞,或者假解锁。解除阻塞后(无论何种原因),wait返回前,将再次调用lock.lock()请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
template<typename lock_type,typename predicate_type>
void wait(lock_type& lock,predicate_type pred)
n 作用:等价于
template<typename lock_type>
bool timed_wait(lock_type& lock,boost::system_time const & abs_time)
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。被this->notify_one()或者this->notify_all()调用通知时,或者boost::get_system_time()报告的时间等于或迟于abs_time指定的时间时,或者假解锁时,线程退出阻塞状态。线程退出阻塞状态时(无论何种原因),在wait调用返回之前,会调用lock.lock()再次请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 返回:如果因为到达abs_time指定的时间而返回,则返回false,否则返回true。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
template<typename lock_type,typename duration_type>
bool timed_wait(lock_type& lock,duration_type const & rel_time)
n 作用:原子地调用lock.unlock(),并且阻塞当前线程。被this->notify_one()或者this->notify_all()调用通知时,或者参数rel_time指定的时间已经流逝时,或者假解锁时,线程退出阻塞状态。线程退出阻塞状态时(无论何种原因),在wait调用返回之前,会调用lock.lock()再次请求锁定。如果函数因为异常退出,也会调用lock.lock()再次请求锁定。
n 返回:如果因为rel_time指定的时间已经流逝而返回,则返回false,否则返回true。
n 后置条件:lock被当前线程锁定。
n 异常:发生错误时抛出boost::thread_resoure_error。如果对当前线程关联到的boost::thread对象执行interrupt()调用,则等待被中断,抛出boost::thread_interrupted。
n 注意:很难正确使用带有持续时间的timed_wait重载版本。多数情况下,使用带有断言的重载版本更好。
template<typename lock_type,typename predicate_type>
bool timed_wait(lock_type& lock,boost::system_time const & abs_time,predicate_type pred)
n 作用:等价于
4.5.4 typedef condition
[img]http://s1.sinaimg.cn/orignal/56dee71agb4cce2363db0[/img]
typedef condition用于提供对先前boost版本的向后兼容性。
4.6 一次性初始化
boost::call_one提供了一种机制,可以保证初始化例程只执行一次,没有数据竞争或者死锁。
4.6.1 typedef once_flag
[img]http://s13.sinaimg.cn/orignal/56dee71ag787ae36d6dec[/img]
应该用BOOST_ONCE_INIT初始化boost::once_flag类型的对象:
[img]http://s3.sinaimg.cn/orignal/56dee71agb4cce2577202[/img]
4.6.2 非成员函数call_once
[img]http://s7.sinaimg.cn/orignal/56dee71agb4cce2705cb6[/img]
n 要求:Callable是可拷贝构造(CopyConstructible)的。func应该没有边效应,调用func副本的效果应该等价于调用原版的func。
n 作用:对相同once_flag对象的call_once调用被串行化。如果先前没有对once_flag对象进行过有效的call_once调用,则参数func(或者其副本)被调用。如果func没有抛出异常,正常返回,则对call_once的调用是有效的。如果抛出异常,则异常被传播给调用者。如果先前已经对once_flag对象进行过有效的call_once调用,则call_once会返回,不调用func。
n 同步:对一个once_flag对象的有效call_once调用的完成,将与后续对相同once_flag对象的call_once调用同步。
n 异常:如果不能到达预定义的效果,则抛出thread_resource_error;或者func抛出的任何异常。
n 注意:传递给call_once的函数不得对传入的once_flag对象调用call_once。这可能会导致死锁,或者两次调用传入的函数。解决办法是让第二次调用立即返回,但这要求代码知道自己是被递归调用的,并且可以处理call_once实际上不调用函数的情况,这样函数才能避免递归地调用call_once。
[img]http://s3.sinaimg.cn/orignal/56dee71agb4cce2827d82[/img]
这个重载用于提供向后兼容性。call_once(func,flag)的效果应该与call_once(flag,func)的效果相同。
4.7 护栏(barrier)
护栏是一个简单的概念,也称作集结点(rendezvous),是多个线程间的同步点。护栏配置为用于特定数量(n)的线程。线程到达护栏时,必须等待所有n个线程都到达。一旦第n个线程到达,所有等待线程继续运行,护栏被复位。
4.7.1 barrier类
[img]http://s14.sinaimg.cn/orignal/56dee71agb4cce299007d[/img]
boost::barrier实例是不可复制、不可移动的。
构造函数
n 作用:构造一个用于count个线程的护栏。
n 异常:发生错误时抛出boost::thread_resource_error。
析构函数
n 前提条件:没有线程在等待*this。
n 作用:销毁*this。
n 异常:无。
成员函数wait
n 作用:阻塞直到count个线程对*this调用了wait。第count个线程调用wait时,所有等待线程解除阻塞,护栏被复位。
n 返回:每一批等待线程中仅有一个返回true,其他返回false。
n 异常:发生错误时抛出boost::thread_resource_error。
4.8 期货(future)
4.8.1 概述
期货库提供了一种处理异步期货值的方式,期货值可以由其他线程生成,或者由单个线程响应外部刺激生成,或者按需生成。
这种机制由四个模板类提供:boost::unique_future和boost::shared_future用于获取异步结果,boost::promise和boost::packaged_task用于生成异步结果。
一个boost::unique_future维持唯一一个到某结果的引用。可以使用移动构造函数或者移动赋值运算符在实例间转移所有权,但是至多只有一个实例可以维持到某给定异步结果的引用。结果准备好时,通过boost::unique_future<R>::get()以右值引用的方式返回,可以移动或者赋值成合适的类型。
另一方面,多个boost::shared_future实例可能引用相同的结果。多个实例间可以自由地拷贝和赋值,boost::shared_future<R>::get()返回的是常引用,所以多个boost::shared_future<R>::get()是安全的。可以将一个boost::unique_future实例移动到boost::shared_future实例中,从而转移关联的异步结果的所有权,但是不能进行相反的移动。
可以单独等待期货,或者使用boost::wait_for_any()和boost::wait_for_all()函数。
4.8.2 创建异步值
可以通过boost::promise或者boost::packaged_task来设置期货的值。boost::packaged_task是一个包装了某函数或者可调用对象的可调用对象。被调用的时候,boost::packaged_task会调用所含的函数,使用返回值填充期货。常见问题“如何从某线程返回一个值?”的答案是:将需要执行的函数封装成boost::packaged_task,然后传递给线程构造函数。这样,从boost::packaged_task取得的期货就可用于获取返回值。如果被封装的函数抛出异常,则异常被存储到期货中,代替返回值。
[img]http://s6.sinaimg.cn/orignal/56dee71agb4cce2a42835[/img]
boost::promise更底层一点:它直接提供一个函数,用于存储一个值或者异常到关联的期货中。因此期货可用于可能从多个源返回值的情况,或者单个操作产生多个值的情况。
[img]http://s4.sinaimg.cn/orignal/56dee71agb4cce2ad6793[/img]
4.8.3 等待回调和延迟期货
boost::promise和boost::packaged_task都支持将在等待回调。等待回调在线程阻塞在对某期货的wait()或者timed_wait()调用上的时候,在等待线程中被调用。该期货正在等待来自boost::promise或者boost::packaged_task的结果。这可通过对被等待的boost::promise或者boost::packaged_task调用set_wait_callback()成员函数来实现。
这就是延迟期货:直到某线程需要结果的时候,才真正计算出结果。下面的例子中,f.get()调用回调函数invoke_lazy_task来运行任务,设置结果。如果移除f.get(),则任务不会运行。
[img]http://s14.sinaimg.cn/orignal/56dee71agb4cce2c9145d[/img]
4.8.4 期货参考
state枚举
[img]http://s6.sinaimg.cn/orignal/56dee71ag787ae37b6645[/img]
unique_future模板类
[img]http://s6.sinaimg.cn/orignal/56dee71agb4cce2ea6d05[/img]
shared_future模板类
[img]http://s13.sinaimg.cn/orignal/56dee71agb4cce303b6ac[/img]
promise模板类
[img]http://s3.sinaimg.cn/orignal/56dee71ag787ae3813582[/img]
packaged_task模板类
[img]http://s4.sinaimg.cn/orignal/56dee71ag787ae382caf3[/img]
非成员函数wait_for_any()
[img]http://s16.sinaimg.cn/orignal/56dee71agf1000e01651f[/img]
n 前提条件:Fn类型应该是boost::unique_future或者boost::shared_future的特例,Iterator应该是value_type为boost::unique_future或者boost::shared_future的前向迭代器。
n 作用:等待至少一个指定的期货准备就绪。
n 返回:基于范围的重载形式返回一个迭代器,标识第一个被检测到准备就绪的期货。其他重载形式返回一个从零开始的索引,指示第一个被检测到准备就绪的期货。(第一个参数索引为0,第二个索引为1,等等)
n 异常:如果当前线程被中断,抛出boost::thread_interrupted;或者任何被等待期货的等待回调抛出的任何异常;如果内部等待结构不能分配内存,则抛出std::bad_alloc。
n 注意:wait_for_any()是一个中断点。
非成员函数wait_for_all()
[img]http://s5.sinaimg.cn/orignal/56dee71agb4cce34be734[/img]
n 前提条件:Fn类型应该是boost::unique_future或者boost::shared_future的特例,Iterator应该是value_type为boost::unique_future或者boost::shared_future的前向迭代器。
n 作用:等待所有期货准备就绪。
n 异常:对指定期货的wait()调用抛出的任何异常。
n 注意:wait_for_all()是一个中断点。
usidc52012-03-07 16:14Anthony Williams
Copyright© 2007-8 Anthony Williams
Distributed under the Boost Software License,Version 1.0.(See accompanying file LICENSE_1_0.txt or copy athttp://www.boost.org/LICENSE_1_0.txt)
5.1 概述
线程局部存储可以让每个线程拥有给定数据的单独实例。单线程应用可以使用静态或者全局数据,但在多线程应用中这可能导致竞争、死锁或者数据破坏。C语言中用于存储标准C库函数错误码的errno变量就是一个例子。支持多线程应用的编译器需要为每个线程提供单独的errno实例(这是POSIX要求的),以避免不同的线程竞争地读取或者更新这个值。
但编译器通常以扩展的声明语法(如static或者名字空间范围变量声明时的__declspec(thread)或者__thread)来提供这个功能,这种支持是不可移植的,并且有某些限制,如只支持POD类型。
5.2 使用boost::thread_specific_ptr的可移植的线程局部存储
boost::thread_specific_ptr提供了一种可移植的线程局部存储机制,可以工作在所有支持Boost.Thread的编译器中。每个boost::thread_specific_ptr实例代表某个在每个线程中必须有不同值的对象(如errno)的指针。当前线程的值可以通过get()成员函数,或者*和->运算符获取。开始时每个线程的值是NULL,使用reset()成员函数可以设置当前线程的值。
如果使用reset()改变了当前线程的指针值,则会调用清理函数销毁先前的值。也可以调用release()成员函数来设置指针值为NULL,并且取得先前的值,这样,销毁对象就是应用程序的责任了。
5.3 线程退出时的清理
线程退出的时候,每个boost::thread_specific_ptr实例关联的对象都被销毁。默认情况下,指针p所指的对象通过调用delete p来销毁,但是通过为特定boost::thread_specific_ptr实例的构造函数提供一个清理函数,就可以覆盖这种默认行为。这时,对象通过调用func(p)来销毁,其中func是提供给构造函数的清理函数。清理函数的调用次序是不确定的。如果清理函数设置已经被清理的boost::thread_specific_ptr实例所关联的值,则设置的值被添加到清理列表中。清理在没有boost::thread_specific_ptr实例有关联的值时完成。
注意:在某些平台上,不会为使用平台原生API创建的线程进行线程特定数据的清理。这种平台上,除非从线程中手动调用boost::on_thread_exit(),否则只会为使用boost::thread启动的线程执行清理。
5.4 thread_specific_ptr类
thread_specific_ptr()
n 要求:delete this->get()可正确执行。
n 作用:构造一个thread_specific_ptr对象,用于为每个线程存储一个类型为T的对象的指针。调用reset()或者线程退出的时候,会使用默认的基于delete的清理函数来销毁任何线程局部对象。
n 异常:发生错误时抛出boost::thread_resource_error。
explicit thread_specific_ptr(void (*cleanup_function)(T*))
n 要求:cleanup_function(this->get())不抛出任何异常。
n 作用:构造一个thread_specific_ptr对象,用于为每个线程存储一个类型为T的对象的指针。调用reset()或者线程退出的时候,会使用cleanup_function来销毁任何线程局部对象。
n 异常:发生错误时抛出boost::thread_resource_error。
~thread_specific_ptr()
n 作用:为当前线程调用this->reset()来清理关联的值,然后销毁*this。
n 异常:无。
n 注意:必须保证在销毁boost::thread_specific_ptr实例后,任何还在运行的线程不会调用实例的任何成员函数。
T* get() const
n 返回:当前线程关联的指针。
n 异常:无。
n 注意:boost::thread_specific_ptr实例对每个线程的初始值是NULL。
T* operator->() const
n 返回:this->get()。
n 异常:无。
T& operator*() const
n 要求:this->get()不是NULL。
n 返回:*(this->get())。
n 异常:无。
void reset(T* new_value=0);
n 作用:如果this->get()!=new_value,并且this->get()不是NULL,则调用delete this->get()或者cleanup_function(this->get())。存储new_value作为当前线程的指针值。
n 后置条件:this->get()==new_value。
n 异常:发生错误时抛出boost::thread_resource_error。
T* release()
n 作用:返回this->get(),存储NULL作为当前线程的指针值,不调用清理函数。
n 后置条件:this->get()==0。
n 异常:无。
6 日期和时间要求
从Boost 1.35.0开始,Boost.Thread将Boost.Date_Time用于所有要求超时值的操作。这包括(但不限于):
l boost::this_thread::sleep()
l timed_join()
l timed_wait()
l timed_lock()
接受绝对时间参数的重载形式则要求boost::system_time类型的对象。通常这通过在当前时间上加一个持续时间来获取,或者通过boost::get_system_time()调用来获取,例如:
对于接受TimeDuration参数的重载形式,则可以使用满足Boost.Date_Time Time Duration 要求的类型,例如:
6.1 typedef system_time
请看Boost.Date_Time库的boost::posix_time:ptime文档。
6.2 非成员函数get_system_time()
返回:当前时间。
异常:无。
7 致谢
Boost.Thread的初始实现由William Kempf编写,还得益于很多其他人的贡献。当前版本源自试图用新的代码重写William Kempf设计的Boost.Thread,以便可以使用Boost软件许可发布。然而,随着C++标准委员会积极讨论标准化的C++线程库,Boost.Thread进行了一些修改以反映委员会的提议,同时尽量保持向后兼容性。
特别感谢Roland Schwarz,他为原始的Boost.Thread库贡献了大量时间和代码,并且积极参与了重写。他提出了将平台特定的实现放入到单独目录中的模式,他的参与对改进当前实现的质量贡献巨大。
也要感谢Peter Dimov、Howard Hinnant、Alexander Terekhov、Chris Thomasson以及其他人对于代码实现细节的意见。
usidc52012-03-23 20:49简介: Boost C++ 库让并发编程变得既简单又有趣。学习如何使用两个 Boost 库 —— Interprocess (IPC) 库和 Message Passing Interface (MPI) 实现共享内存对象、同步文件锁和分布式通信等功能。
本文的标签: linux, 工具与及实用程序, 应用开发
标记本文!
发布日期: 2011 年 6 月 07 日
级别: 中级
原创语言: 英文
访问情况 : 5481 次浏览
评论: 0 (查看 | 添加评论 - 登录)
平均分 (2个评分)
为本文评分
使用非常流行的 Boost 库进行并发编程非常有意思。Boost 有几个用于并发编程领域的库:Interprocess (IPC) 库用于实现共享内存、内存映射的 I/O 和消息队列;Thread 库用于实现可移植的多线程;Message Passing Interface (MPI) 库用于分布式计算中的消息传递;Asio 库用于使用套接字和其他低层功能实现可移植的连网功能。本文介绍 IPC 和 MPI 库以及它们提供的一些功能。
本文中将学习如何使用 Boost IPC 库实现共享内存对象、消息队列和同步文件锁。通过使用 Boost MPI 库,了解 environment 和 communicator 类,以及如何实现分布式通信。
注意:本文中的代码已经用 gcc-4.3.4 和 boost-1.45 包测试过了。
常用缩写词
API:应用程序编程接口
I/O:输入/输出
POSIX:针对 UNIX 的便携式操作系统接口®
SDK:软件开发工具包
使用 Boost IPC 库
Boost Interprocess 是一个只由头文件组成的库,所以您需要做的只是在自己的源代码中包含适当的头文件并让编译器知道 include 路径。这是一个非常好的特性;您只需下载 Boost 源代码(见 参考资料 中的链接),然后就可以开始使用了。例如,要想在自己的代码中使用共享内存,就使用 清单 1 所示的 include。
清单 1. Boost IPC 库只由头文件组成
#include <boost/interprocess/shared_memory_object.hpp>
using namespace boost::interprocess;
//… your sources follow …
在把信息传递给编译器时,您要求进程根据安装相应地修改 include 路径。然后,编译代码:
bash-4.1$ g++ ipc1.cpp –I../boost_1_45_0
创建共享内存对象
我们先从传统的 "Hello World!" 程序开始。有两个进程:第一个进程把字符串 "Hello World!" 写入内存,另一个进程读取并显示此字符串。像 清单 2 这样创建共享内存对象。
清单 2. 创建共享内存对象
#include <boost/interprocess/shared_memory_object.hpp>
int main(int argc, char* argv[ ])
{
using namespace using boost::interprocess;
try {
// creating our first shared memory object.
shared_memory_object sharedmem1 (create_only, "Hello", read_write);
// setting the size of the shared memory
sharedmem1.truncate (256);
// … more code follows
} catch (interprocess_exception& e) {
// .. . clean up
}
}
sharedmem1 对象的类型是 shared_memory_object(在 Boost 头文件中声明并定义),它的构造函数有三个参数:
第一个参数 — create_only — 表示要创建这个共享内存对象而且还没有创建它。如果已经存在同名的共享对象,就会抛出异常。对于希望访问已经创建的共享内存的进程,第一个参数应该是 open_only。
第二个参数 — Hello — 是共享内存区域的名称。另一个进程将使用这个名称访问这个共享内存。
第三个参数 — read_write — 是共享内存对象的访问指示符。因为这个进程要修改共享内存对象的内容,所以使用 read_write。只从共享内存读取数据的进程使用 read_only 指示符。
truncate 方法以字节为单位设置共享内存的大小。最好把代码放在 try-catch 块中。例如,如果无法创建共享内存对象,就抛出类型为 boost::interprocess_exception 的异常。
使用共享内存对象写数据
使用共享内存对象的进程必须在自己的地址空间中映射对象。使用在头文件 mapped_region.hpp 中声明并定义的 mapped_region 类执行映射。使用 mapped_region 的另一个好处是可以对共享内存对象进行完全和部分访问。清单 3 演示如何使用 mapped_region。
清单 3. 使用 mapped_region 访问共享内存对象
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
int main(int argc, char* argv[ ])
{
using namespace boost::interprocess;
try {
// creating our first shared memory object.
shared_memory_object sharedmem1 (create_only, "Hello", read_write);
// setting the size of the shared memory
sharedmem1.truncate (256);
// map the shared memory to current process
mapped_region mmap (sharedmem1, 256);
// access the mapped region using get_address
std::strcpy(static_cast<char* >(region.get_address()), "Hello World!\n");
} catch (interprocess_exception& e) {
// .. . clean up
}
}
就这么简单。现在已经创建了您自己的 mapped_region 对象并使用 get_address 方法访问了它。执行了 static_cast,因为 get_address 返回一个 void*。
当主进程退出时共享内存会怎么样?
当主进程退出时,并不删除共享内存。要想删除共享内存,需要调用 shared_memory_object::remove。第二个进程的访问机制也很简单:清单 4 证明了这一点。
清单 4. 从第二个进程访问共享内存对象
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <cstring>
#include <cstdlib>
#include <iostream>
int main(int argc, char *argv[ ])
{
using namespace boost::interprocess;
try {
// opening an existing shared memory object
shared_memory_object sharedmem2 (open_only, "Hello", read_only);
// map shared memory object in current address space
mapped_region mmap (sharedmem2, read_only);
// need to type-cast since get_address returns void*
char *str1 = static_cast<char*> (mmap.get_address());
std::cout << str1 << std::endl;
} catch (interprocess_exception& e) {
std::cout << e.what( ) << std::endl;
}
return 0;
}
在清单 4 中,使用 open_only 和 read_only 属性创建共享内存对象。如果无法找到这个共享内存对象,就会抛出异常。现在,构建并运行 清单 3 和 清单 4 中的代码。应该会在终端上看到 "Hello World!"。
接下来,在第二个进程的代码(清单 4)中 std::cout 后面添加以下代码并重新构建代码:
// std::cout code here
shared_memory_object::remove("Hello");
// } catch(interprocess_exception& e) {
连续执行代码两次,第二次执行会显示 "No such file or directory",这证明共享内存已经被删除了。
回页首
使用消息队列实现进程间通信
现在,研究另一种流行的进程间通信机制:消息队列。每个参与通信的进程都可以在队列中添加消息和从队列读取消息。消息队列具有以下性质:
它有名称,进程使用名称访问它。
在创建队列时,用户必须指定队列的最大长度和一个消息的最大大小。
队列是持久的,这意味着当创建它的进程死亡之后它仍然留在内存中。可以通过显式地调用 boost::interprocess::message_queue::remove 删除队列。
在 清单 5 所示的代码片段中,进程创建了一个可包含 20 个整数的消息队列。
清单 5. 创建一个可包含 20 个整数的消息队列
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>
int main(int argc, char* argv[ ])
{
using namespace boost::interprocess;
try {
// creating a message queue
message_queue mq (create_only, // only create
"mq", // name
20, //max message count
sizeof(int) //max message size
);
// … more code follows
} catch (interprocess_exception& e) {
std::cout << e.what( ) << std::endl;
}
}
注意传递给 message_queue 的构造函数的 create_only 属性。与共享内存对象相似,对于以只读方式打开消息队列,应该把 open_only 属性传递给构造函数。
发送和接收数据
在发送方,使用队列的 send 方法添加数据。send 方法有三个输入参数:原始数据的指针 (void*)、数据的大小和优先级。目前,以相同的优先级发送所有数据。清单 6 给出代码。
清单 6. 向队列发送消息
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>
int main(int argc, char* argv[ ])
{
using namespace boost::interprocess;
try {
// creating a message queue
message_queue mq (create_only, // only create
"mq", // name
20, //max message count
sizeof(int) //max message size
);
// now send the messages to the queue
for (int i=0; i<20; ++i)
mq.send(&i, sizeof(int), 0); // the 3rd argument is the priority
} catch (interprocess_exception& e) {
std::cout << e.what( ) << std::endl;
}
}
在接收方,使用 open_only 属性创建队列。通过调用 message_queue 类的 receive 方法从队列获取消息。清单 7 给出 receive 的方法签名。
清单 7. message_queue::receive 的方法签名
void receive (void *buffer,
std::size_t buffer_size,
std::size_t &recvd_size,
unsigned int &priority
);
我们来仔细看一下。第一个参数是从队列接收的数据将被存储到的位置。第二个参数是接收的数据的预期大小。第三个参数是接收的数据的实际大小。第四个参数是接收的消息的优先级。显然,如果在执行程序期间第二个和第三个参数不相等,就是出现错误了。清单 8 给出接收者进程的代码。
清单 8. 从消息队列接收消息
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>
int main(int argc, char* argv[ ])
{
using namespace boost::interprocess;
try {
// opening the message queue whose name is mq
message_queue mq (open_only, // only open
"mq" // name
);
size_t recvd_size;
unsigned int priority;
// now send the messages to the queue
for (int i=0; i<20; ++i) {
int buffer;
mq.receive ((void*) &buffer, sizeof(int), recvd_size, priority);
if (recvd_size != sizeof(int))
; // do the error handling
std::cout << buffer << " " << recvd_size << " " << priority;
}
} catch (interprocess_exception& e) {
std::cout << e.what( ) << std::endl;
}
}
这相当简单。注意,仍然没有从内存中删除消息队列;与共享内存对象一样,这个队列是持久的。要想删除队列,应该在使用完队列之后添加以下行:
message_queue::remove("mq"); // remove the queue using its name
消息优先级
在发送方,做 清单 9 所示的修改。接收方代码不需要修改。
清单 9. 修改消息的优先级
message_queue::remove("mq"); // remove the old queue
message_queue mq (…); // create as before
for (int i=0; i<20; ++i)
mq.send(&i, sizeof(int), i%2); // 第 3 个参数为消息的优先级
// … rest as usual
再次运行代码时,应该会看到 清单 10 所示的输出。
清单 10. 在接收进程中看到的输出
1 4 1
3 4 1
5 4 1
7 4 1
9 4 1
11 4 1
13 4 1
15 4 1
17 4 1
19 4 1
0 4 0
2 4 0
4 4 0
6 4 0
8 4 0
10 4 0
12 4 0
14 4 0
16 4 0
18 4 0
清单 10 证实,第二个进程优先接收优先级高的消息。
回页首
同步对文件的访问
共享内存和消息队列很不错,但是文件 I/O 也是重要的进程间通信工具。对并发进程用于通信的文件访问进行同步并非易事,但是 Boost IPC 库提供的文件锁功能让同步变得简单了。在进一步解释之前,来看一下 清单 11,了解 file_lock 对象是如何工作的。
清单 11. 使用 file_lock 对象同步文件访问
#include <fstream>
#include <iostream>
#include <boost/interprocess/sync/file_lock.hpp>
#include <cstdlib>
int main()
{
using namespace boost::interprocess;
std::string fileName("test");
std::fstream file;
file.open(fileName.c_str(), std::ios::out | std::ios::binary |
std::ios::trunc);
if (!file.is_open() || file.bad())
{
std::cout << "Open failed" << std::endl;
exit(-1);
}
try {
file_lock f_lock(fileName.c_str());
f_lock.lock();
std::cout << "Locked in Process 1" << std::endl;
file.write("Process 1", 9);
file.flush();
f_lock.unlock();
std::cout << "Unlocked from Process 1" << std::endl;
} catch (interprocess_exception& e) {
std::cout << e.what( ) << std::endl;
}
file.close();
return 0;
}
代码首先打开一个文件,然后使用 file_lock 锁定它。写操作完成之后,它刷新文件缓冲区并解除文件锁。使用 lock 方法获得对文件的独占访问。如果另一个进程也试图对此文件进行写操作并已经请求了锁,那么它会等待,直到第一个进程使用 unlock 自愿地放弃锁。file_lock 类的构造函数接受要锁定的文件的名称,一定要在调用 lock 之前打开文件;否则会抛出异常。
现在,复制 清单 11 中的代码并做一些修改。具体地说,让第二个进程请求这个锁。清单 12 给出相关修改。
清单 12. 试图访问文件的第二个进程的代码
// .. as in Listing 11
file_lock f_lock(fileName.c_str());
f_lock.lock();
std::cout << "Locked in Process 2" << std::endl;
system("sleep 4");
file.write("Process 2", 9);
file.flush();
f_lock.unlock();
std::cout << "Unlocked from Process 2" << std::endl;
// file.close();
现在,如果这两个进程同时运行,有 50% 的机会看到第一个进程等待 4 秒后才获得 file_lock,其他情况都不变。
在使用 file_lock 时,必须记住几点。这里讨论的主题是进程间通信,重点在进程 上。这意味着,不是使用 file_lock 来同步同一进程中各个线程的数据访问。在与 POSIX 兼容的系统上,文件句柄是进程属性,而不是 线程属性。下面是使用文件锁的几条规则:
对于每个进程,每个文件使用一个 file_lock 对象。
使用相同的线程来锁定和解锁文件。
在解锁文件之前,通过调用 C 的 flush 库例程或 flush 方法(如果喜欢使用 C++ fstream 的话),刷新写入者进程中的数据。
结合使用 file_lock 和有范围(scope)的锁
在执行程序时,可能会出现抛出异常而文件没有解锁的情况。这种情况可能会导致意外的程序行为。为了避免这种情况,可以考虑把 file_lock 对象放在(boost/interprocess/sync/scoped_lock.hpp 中定义的)scoped_lock 中。如果使用 scoped_lock,就不需要显式地锁定或解锁文件;锁定发生在构造器内,每当您退出该范围,就会自动发生解锁。清单 13 给出对 清单 11 的修改,使之使用有范围的锁。
清单 13. 结合使用 scoped_lock 和 file_lock
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/file_lock.hpp>
//… code as in Listing 11
file_lock f_lock(fileName.c_str());
scoped_lock<file_lock> s_lock(f_lock); // internally calls f_lock.lock( );
// No need to call explicit lock anymore
std::cout << "Locked in Process 1" << std::endl;
file.write("Process 1", 9);
// … code as in Listing 11
注意:关于 Resource Acquisition Is Initialization (RAII) 编程习惯法的更多信息,参见 参考资料 中的链接。
回页首
了解 Boost MPI
如果您不熟悉 Message Passing Interface,那么在讨论 Boost MPI 之前,应该先浏览 参考资料 中提供的 MPI 参考资料。MPI 是一个容易使用的标准,它采用通过传递消息实现进程间通信的模型。不需要使用套接字或其他通信原语;MPI 后端管理所有底层处理。那么,使用 Boost MPI 有什么好处?Boost MPI 的创建者提供了更高层的抽象,并在 MPI 提供的 API 之上构建了一套简单的例程,比如 MPI_Init 和 MPI_Bcast。
Boost MPI 不是一个单独的库,不能在下载和构建之后直接使用。相反,必须安装任意 MPI 实现(比如 MPICH 或 Open MPI)并构建 Boost Serialization 库。关于如何构建 Boost MPI 的详细信息参见 参考资料。通常,使用以下命令构建 Boost MPI:
bash-4.1$ bjam –with-mpi
Windows® 用户可以从 BoostPro 下载预先构建的 MPI 库(见 参考资料)。这些库与 Microsoft® HPC Pack 2008 和 2008 R2 兼容(见 参考资料),适用于带 Service Pack 3 的 Windows XP 或更高版本的客户机操作环境。
回页首
用 MPI 实现 Hello World 程序
您必须了解 Boost MPI 库中的两个主要类:environment 类和 communicator 类。前者负责分布式环境的初始化;后者用于进程之间的通信。因为这里讨论的是分布式计算,我们有四个进程,它们都在终端上输出 "Hello World"。清单 14 给出代码。
清单 14. 使用 Boost MPI 的 Hello World
#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char* argv[])
{
boost::mpi::environment env(argc, argv);
boost::mpi::communicator world;
std::cout << argc << std::endl;
std::cout << argv[0] << std::endl;
std::cout << "Hello World! from process " << world.rank() << std::endl;
return 0;
}
现在,构建 清单 14 中的代码并链接 Boost MPI 和 Serialization 库。在 shell 提示上运行可执行程序。应该会看到 "Hello World! from process 0"。接下来,使用 MPI 分派器工具(例如,对于 Open MPI 用户,使用 mpirun;对于 Microsoft HPC Pack 2008,使用 mpiexec)并运行可执行程序:
mpirun –np 4 <executable name>
OR
mpiexec –n 4 <executable name>
现在应该会看到与 清单 15 相似的输出,其中的 mympi1 是可执行程序名称。
清单 15. 运行 MPI 代码的输出
1
mympi1
Hello, World! from process 3
1
mympi1
1
mympi1
Hello, World! from process 1
Hello, World! from process 2
1
mympi1
Hello, World! from process 0
在 MPI 框架中,已经创建了相同进程的四个拷贝。在 MPI 环境中,每个进程有惟一的 ID(由 communicator 对象决定)。现在,试试进程间通信。使用 send 和 receive 函数调用让一个进程与另一个进程通信。发送消息的进程称为主进程,接收消息的进程称为工作者进程。主进程和接收者进程的源代码是相同的,使用 world 对象提供的等级决定功能(见 清单 16)。
清单 16. 相互通信的进程 0、1 和 2 的代码
#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char* argv[])
{
boost::mpi::environment env(argc, argv);
boost::mpi::communicator world;
if (world.rank() == 0) {
world.send(1, 9, 32);
world.send(2, 9, 33);
} else {
int data;
world.recv(0, 9, data);
std::cout << "In process " << world.rank( ) << "with data " << data
<< std::endl;
}
return 0;
}
先看一下 send 函数。第一个参数是接收者进程的 ID;第二个是消息数据的 ID;第三个是实际数据。为什么需要消息标签?接收者进程在执行期间的特定点上可能希望处理具有特定标签的消息,所以这个方案会有帮助。对于进程 1 和 2,recv 函数被阻塞,这意味着程序会等待,直到从进程 0 收到标签 ID 为 9 的消息。当收到这个消息时,把信息存储在 data 中。下面是运行代码的输出:
In process 1 with data 32
In process 2 with data 33
如果在接收方有 world.recv(0, 1, data); 这样的代码,会发生什么?代码阻塞,但实际上是,接收者进程在等待一个永远不会到达的消息。
回页首
结束语
本文只讨论了这两个库提供的功能的很小一部分。这些库提供的其他功能包括 IPC 的内存映射 I/O 和 MPI 的广播功能。从易用性的角度来说,IPC 更好。MPI 库依赖于原生的 MPI 实现,而原生 MPI 库以及预先构建的 Boost MPI 和 Serialization 库的现成可用性仍然是个问题。但是,花点儿精力构建 MPI 实现和 Boost 的源代码是值得的。
usidc52012-07-04 17:18在编写多线程程序时,多个线程同时访问某个共享资源,会导致同步的问题,这篇文章中我们将介绍 C++11 多线程编程中的数据保护。
数据丢失
让我们从一个简单的例子开始,请看如下代码:
01
#include <iostream>
02
#include <string>
03
#include <thread>
04
#include <vector>
05
06
using std::thread;
07
using std::vector;
08
using std::cout;
09
using std::endl;
10
11
class Incrementer
12
{
13
private:
14
int counter;
15
16
public:
17
Incrementer() : counter{0} { };
18
19
void operator()()
20
{
21
for(int i = 0; i < 100000; i++)
22
{
23
this->counter++;
24
}
25
}
26
27
int getCounter() const
28
{
29
return this->counter;
30
}
31
};
32
33
int main()
34
{
35
// Create the threads which will each do some counting
36
vector<thread> threads;
37
38
Incrementer counter;
39
40
threads.push_back(thread(std::ref(counter)));
41
threads.push_back(thread(std::ref(counter)));
42
threads.push_back(thread(std::ref(counter)));
43
44
for(auto &t : threads)
45
{
46
t.join();
47
}
48
49
cout << counter.getCounter() << endl;
50
51
return 0;
52
}
这个程序的目的就是数数,数到30万,某些傻叉程序员想要优化数数的过程,因此创建了三个线程,使用一个共享变量 counter,每个线程负责给这个变量增加10万计数。
这段代码创建了一个名为 Incrementer 的类,该类包含一个私有变量 counter,其构造器非常简单,只是将 counter 设置为 0.
紧接着是一个操作符重载,这意味着这个类的每个实例都是被当作一个简单函数来调用的。一般我们调用类的某个方法时会这样 object.fooMethod(),但现在你实际上是直接调用了对象,如 object(). 因为我们是在操作符重载函数中将整个对象传递给了线程类。最后是一个 getCounter 方法,返回 counter 变量的值。
再下来是程序的入口函数 main(),我们创建了三个线程,不过只创建了一个 Incrementer 类的实例,然后将这个实例传递给三个线程,注意这里使用了 std::ref ,这相当于是传递了实例的引用对象,而不是对象的拷贝。
现在让我们来看看程序执行的结果,如果这位傻叉程序员还够聪明的话,他会使用 GCC 4.7 或者更新版本,或者是 Clang 3.1 来进行编译,编译方法:
1
g++ -std=c++11 -lpthread -o threading_example main.cpp
运行结果:
01
[lucas@lucas-desktop src]$ ./threading_example
02
218141
03
[lucas@lucas-desktop src]$ ./threading_example
04
208079
05
[lucas@lucas-desktop src]$ ./threading_example
06
100000
07
[lucas@lucas-desktop src]$ ./threading_example
08
202426
09
[lucas@lucas-desktop src]$ ./threading_example
10
172209
但等等,不对啊,程序并没有数数到30万,有一次居然只数到10万,为什么会这样呢?好吧,加1操作对应实际的处理器指令其实包括:
1
movl counter(%rip), %eax
2
addl $1, %eax
3
movl %eax, counter(%rip)
首个指令将装载 counter 的值到 %eax 寄存器,紧接着寄存器的值增1,然后将寄存器的值移给内存中 counter 所在的地址。
我听到你在嘀咕:这不错,可为什么会导致数数错误的问题呢?嗯,还记得我们以前说过线程会共享处理器,因为只有单核。因此在某些点上,一个线程会依照指令执行完成,但在很多情况下,操作系统会对线程说:时间结束了,到后面排队再来,然后另外一个线程开始执行,当下一个线程开始执行时,它会从被暂停的那个位置开始执行。所以你猜会发生什么事,当前线程正准备执行寄存器加1操作时,系统把处理器交给另外一个线程?
我真的不知道会发生什么事,可能我们在准备加1时,另外一个线程进来了,重新将 counter 值加载到寄存器等多种情况的产生。谁也不知道到底发生了什么。
正确的做法
解决方案就是要求同一个时间内只允许一个线程访问共享变量。这个可通过 std::mutex 类来解决。当线程进入时,加锁、执行操作,然后释放锁。其他线程想要访问这个共享资源必须等待锁释放。
互斥(mutex) 是操作系统确保锁和解锁操作是不可分割的。这意味着线程在对互斥量进行锁和解锁的操作是不会被中断的。当线程对互斥量进行锁或者解锁时,该操作会在操作系统切换线程前完成。
而最好的事情是,当你试图对互斥量进行加锁操作时,其他的线程已经锁住了该互斥量,那你就必须等待直到其释放。操作系统会跟踪哪个线程正在等待哪个互斥量,被堵塞的线程会进入 "blocked on m" 状态,意味着操作系统不会给这个堵塞的线程任何处理器时间,直到互斥量解锁,因此也不会浪费 CPU 的循环。如果有多个线程处于等待状态,哪个线程最先获得资源取决于操作系统本身,一般像 Windows 和 Linux 系统使用的是 FIFO 策略,在实时操作系统中则是基于优先级的。
现在让我们对上面的代码进行改进:
01
#include <iostream>
02
#include <string>
03
#include <thread>
04
#include <vector>
05
#include <mutex>
06
07
using std::thread;
08
using std::vector;
09
using std::cout;
10
using std::endl;
11
using std::mutex;
12
13
class Incrementer
14
{
15
private:
16
int counter;
17
mutex m;
18
19
public:
20
Incrementer() : counter{0} { };
21
22
void operator()()
23
{
24
for(int i = 0; i < 100000; i++)
25
{
26
this->m.lock();
27
this->counter++;
28
this->m.unlock();
29
}
30
}
31
32
int getCounter() const
33
{
34
return this->counter;
35
}
36
};
37
38
int main()
39
{
40
// Create the threads which will each do some counting
41
vector<thread> threads;
42
43
Incrementer counter;
44
45
threads.push_back(thread(std::ref(counter)));
46
threads.push_back(thread(std::ref(counter)));
47
threads.push_back(thread(std::ref(counter)));
48
49
for(auto &t : threads)
50
{
51
t.join();
52
}
53
54
cout << counter.getCounter() << endl;
55
56
return 0;
57
}
注意代码上的变化:我们引入了 mutex 头文件,增加了一个 m 的成员,类型是 mutex,在 operator()() 中我们锁住互斥量 m 然后对 counter 进行加1操作,然后释放互斥量。
再次执行上述程序,结果如下:
1
[lucas@lucas-desktop src]$ ./threading_example
2
300000
3
[lucas@lucas-desktop src]$ ./threading_example
4
300000
这下数对了。不过在计算机科学中,没有免费的午餐,使用互斥量会降低程序的性能,但这总比一个错误的程序要强吧。
防范异常
当对变量进行加1操作时,是可能会发生异常的,当然在我们这个例子中发生异常的机会微乎其微,但是在一些复杂系统中是极有可能的。上面的代码并不是异常安全的,当异常发生时,程序已经结束了,可是互斥量还是处于锁的状态。
为了确保互斥量在异常发生的情况下也能被解锁,我们需要使用如下代码:
01
for(int i = 0; i < 100000; i++)
02
{
03
this->m.lock();
04
try
05
{
06
this->counter++;
07
this->m.unlock();
08
}
09
catch(...)
10
{
11
this->m.unlock();
12
throw;
13
}
14
}
但是,这代码太多了,而只是为了对互斥量进行加锁和解锁。没关系,我知道你很懒,因此推荐个更简单的单行代码解决方法,就是使用 std::lock_guard 类。这个类在创建时就锁定了 mutex 对象,然后在结束时释放。
继续修改代码:
01
void operator()()
02
{
03
for(int i = 0; i < 100000; i++)
04
{
05
lock_guard<mutex> lock(this->m);
06
07
// The lock has been created now, and immediatly locks the mutex
08
this->counter++;
09
10
// This is the end of the for-loop scope, and the lock will be
11
// destroyed, and in the destructor of the lock, it will
12
// unlock the mutex
13
}
14
}
上面代码已然是异常安全了,因为当异常发生时,将会调用 lock 对象的析构函数,然后自动进行互斥量的解锁。
记住,请使用放下代码模板来编写:
view sourceprint?
01
void long_function()
02
{
03
// some long code
04
05
// Just a pair of curly braces
06
{
07
// Temp scope, create lock
08
lock_guard<mutex> lock(this->m);
09
10
// do some stuff
11
12
// Close the scope, so the guard will unlock the mutex
13
}
14
}
usidc52013-02-08 10:18介绍
本文旨在帮助有经验的Win32程序员来了解c++ 11线程库及同步对象 和 Win32线程及同步对象之间的区别和相似之处。
在Win32中,所有的同步对象句柄(HANDLE)是全局句柄.它们可以被共享,甚至可以在进程间复制。在C++11中,所有的同步对象都是栈(stack)对象,这意味着它们必须是可“分离(detached)”的(如果支持“分离”的话)以便能够被栈框架(stack frame)所析构。如果大量对象应该分离而你没有,那么它们便会无法实现自己的行动,而毁掉你的原本计划。(译者注:在pthread中,线程有joinable和unjoinable之分,具有joinable的线程在线程结束时,不会清空该线程所占用的栈空间,通常的做法是在pthrea_create创建线程后,再调用pthread_join(有点waitforsingleobject的意思)才会清空,而unjoinable的属性的线程在线程结束时,就会自动清空所占用空间)
所有的c++11同步对象都有一个native_handle()成员,它返回具体实现句柄(在win32,它就是一个handle)
在我的所有例子,我给出了win32伪代码。祝你愉快!
怪兽狂殴奥特曼
翻译于 2天前
1人顶
顶 翻译的不错哦!
背景知识
ox0000000.木有 :D。我也是c++11线程的新手。你需要自己去了解win32同步相关知识。这里可能不是合适的同步技术的教程,而是一个C++11机制的快速引导,以便对你所指定的计划有所帮助。
简单成就完美
一个简单例子:启动一个线程,然后等它结束:
1
void foo()
2
{
3
}
4
void func()
5
{
6
std::thread t(foo); // Starts. Equal to CreateThread.
7
t.join(); // Equal to WaitForSingleObject to the thread handle.
8
}
与win32线程不同,你可以在这里传递参数:
1
void foo(int x,int y)
2
{
3
// x = 4, y = 5.
4
}
5
void func()
6
{
7
std::thread t(foo,4,5); // Acceptable.
8
t.join();
9
}
这样,通过传递‘this’指针给std::thread让成员函数成为一个线程,变成了一件很简单的事情.如果std::thread得以析构,而你没有调用join(),它将会异常终止。脱离c++封装运行线程:
1
void foo()
2
{
3
}
4
void func()
5
{
6
std::thread t(foo);
7
// 在这里已经调用了detach方法,c++对象从win32对象中脱离出来,如果此时还调用join方法,就会抛出std::system_error()
8
t.detach();
9
}
除了join(),detach()方法,还有joinable(),get_id(),sleep_for(),sleep_until().它们都是自解释的,很好理解。
怪兽狂殴奥特曼
翻译于 2天前
1人顶
顶 翻译的不错哦!
使用互斥(Mutex)
std::mutex与win32的临界区(cirtical section)很类似。lock()如同EnterCriticalSection,unlock如同LeaveCriticalSection,try_lock则像TryEnterCriticalSection。
01
std::mutex m;
02
int j = 0;
03
void foo()
04
{
05
m.lock(); // 进入临界区域
06
j++;
07
m.unlock(); // 离开
08
}
09
void func()
10
{
11
std::thread t1(foo);
12
std::thread t2(foo);
13
t1.join();
14
t2.join();
15
// j = 2;
16
}
如上,你在lock一个 std::mutex 对象之后必须解锁(unlock)。如果你已经对其加锁,你不能再次lock。这与win32 不同,如果你已经在临界区(critical section)里,再次 EnterCriticalSection不会失败,但是会增加一个计数。
嗨,不要走开哦。前面提到不能对std::mutex重复lock。这里有std::recursive_mutex(谁发明的这名字),它的行为则与临界区(critical section)相似,可以重复lock。
1
std::recursive_mutex m;
2
void foo()
3
{
4
m.lock();
5
m.lock(); // now valid
6
j++;
7
m.unlock();
8
m.unlock(); // don't forget!
9
}
此外,还有 std::timed_mutex, std::recursive_timed_mutex,他们提供 try_lock_for/ try_lock_until方法,允许你等待一个lock,直到超时,或者达到定义的时间。
怪兽狂殴奥特曼
翻译于 2天前
1人顶
顶 翻译的不错哦!
C++11 Thread的线程本地存储(Thread Local Storage)
与TLS(thread local storage)类似,该功能允许你声明一个带有thread_local的声明符的变量。这意味着,每一个线程都有自己的该全局变量的实例(instance),该实例的变量名就是全局变量名称。
以前:
01
int j = 0;
02
void foo()
03
{
04
m.lock();
05
j++;
06
m.unlock();
07
}
08
void func()
09
{
10
j = 0;
11
std::thread t1(foo);
12
std::thread t2(foo);
13
t1.join();
14
t2.join();
15
// j = 2;
16
}
现在我们看:
01
thread_local int j = 0;
02
void foo()
03
{
04
m.lock();
05
j++; // j is now 1, no matter the thread. j is local to this thread.
06
m.unlock();
07
}
08
void func()
09
{
10
j = 0;
11
std::thread t1(foo);
12
std::thread t2(foo);
13
t1.join();
14
t2.join();
15
// j still 0. The other "j"s were local to the threads
16
}
然而,Visual Studio还不支持 tls(我想这里的tls应该是c++11 thread的tls)
怪兽狂殴奥特曼
翻译于 2天前
0人顶
顶 翻译的不错哦!
神秘的变量
条件变量(Conditional variables)是能够使线程等待特定条件的对象。在window系统里,这些对象属于用户模式(usr-mode),因而不能被其他进程所共享。在window系统,条件变量与临界区(critical section)有关,用来获取或者释放一个锁。std::condition_variable与std::mutex联用,也是这个原因。
01
std::condition_variable c;
02
// 我们使用mutex而不是recursive_mutex是因为该锁需要一次性获取和释放
03
std::mutex mu; // We use a mutex rather than a recursive_mutex because the lock has to be acquired only and exactly once.
04
void foo5()
05
{
06
std::unique_lock lock(mu); // Lock the mutex
07
c.notify_one(); // WakeConditionVariable. It also releases the unique lock 等待条件变量,它也会释放unque lock
08
}
09
void func5()
10
{
11
std::unique_lock lock(mu); // Lock the mutex
12
std::thread t1(foo5);
13
// 等价与SleepConditionVariableCS,它解锁mutex 变量nu,并允许foo5来加锁
14
c.wait(lock); // Equal to SleepConditionVariableCS. This unlocks the mutex mu and allows foo5 to lock it
15
t1.join();
16
}
这并不像看上去那么简单。c.wait() 可能会返回,即使c.notify_one()没有被调用(已知的这种情况是spurious wakeup -http://msdn.microsoft.com/en-us/library/windows/desktop/ms686301(v=vs.85).aspx)。通常,在Vista及以上操作系统,条件变量才被支持。
怪兽狂殴奥特曼
翻译于 2天前
0人顶
顶 翻译的不错哦!
未来的承诺
设想这样的情况,你希望一个线程做一些事情,然后返回你一个结果。同时,你在做一些其他的工作,该工作也许会也许不会花费你一点时间。你希望在某个特定的时间获取那个线程的结果。
在win32中,你可以这样
用CreateThread启动线程
在线程里,启动任务,当准备完毕后发送一个事件(event),并把结果放在全局变量里。
在主函数里(main)做其它的事情,然后在你想要结果的地方,调用WaitForSingleObject
在c++11,这个可以轻松被std::future实现,然后返回任何类型,因为它是一个模板。
01
int GetMyAnswer()
02
{
03
return 10;
04
}
05
int main()
06
{
07
std::future<int> GetAnAnswer = std::async(GetMyAnswer); // GetMyAnswer starts background execution
08
int answer = GetAnAnswer.get(); // answer = 10;
09
// If GetMyAnswer has finished, this call returns immediately.
10
// If not, it waits for the thread to finish.
11
}
你也可以使用 std::promise。该对象可以提供一些 std::future以后需求的特性。如果在任何东西放入承诺(promise)之前你调用 std::future::get(),将会导致等待,直到承诺值(promised value)出现。如果 std::promise::set_exception()被调用, std::future::get()则会抛出异常。如果 std::promise销毁了,而你调用 std::future::get(),你将会产生 broken_promise 异常。
01
std::promise<int> sex;
02
void foo()
03
{
04
// do stuff
05
// 在下面的调用之后,future::get()将会返回该值
06
sex.set_value(1); // After this call, future::get() will return this value.
07
08
// 调用之后,future::get()将会抛出这个异常
09
sex.set_exception(std::make_exception_ptr(std::runtime_error("TEST")));
10
}
11
int main()
12
{
13
future<int> makesex = sex.get_future();
14
std::thread t(foo);
15
16
// do stuff
17
try
18
{
19
makesex.get();
20
hurray();
21
}
22
catch(...)
23
{
24
// She dumped us :(
25
}
26
}
怪兽狂殴奥特曼
翻译于 2天前
0人顶
顶 翻译的不错哦!
代码
附上的代码包含所有上述我的所述。可以在visualstudio 2012 CTP版本的编译器下编译成功(除了tls机制)。(代码地址:http://www.codeproject.com/KB/cpp/540912/c11threads.zip)
还有什么
还有很多值得包括的事情,如:
信号量(Semaphores)
命名对象 (Named objects)
进程共享对象 (Shareable objects across processes.)
...
你应该做什么呢?通常当编写新的代码,如果足够适用,尽量选择C++标准。对于已存在的代码,我尽量保持使用win32调用,当需要移植它们到另外的平台,我则会用c++11函数来实现CreateThread、SetEvent 等等。
祝你好运!
- boost多线程库使用指南
- boost多线程库使用指南
- boost多线程库使用指南
- Boost库多线程
- boost库多线程特性
- Boost使用指南之Boost.Any
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- Boost-bind使用指南
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- boost bind使用指南
- Boost.Interprocess使用指南(1)
- sod
- 关于stream!=NULL问题
- ibatis-2.3.3学习
- 【Java】如何终止一个线程
- sicily 1031.Campus
- boost多线程库使用指南
- C++中的运算符重载(二)
- Invalid file name: must contain only [a-z0-9_.]
- hadoop简介
- WPF下、使用Double作为点坐标带来的问题或line直线颜色不明显的问题的解决方法
- 【MoreWindows工作笔记11】EnumClipboardFormats 剪切板内容的数据格式
- Lucene 的Query Parser(查询语法)
- TRUNC函数的用法
- 内核文件系统的注册