Boost线程简介(转自Boost中文站)C++ Boost Thread 编程指南
#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线程库允许你根据不同的需要使用最有效率的互斥体类型。Boost线程库提供了6中互斥体类型,下面是按照效率进行排序: boost::mutex, boost::try_mutex, boost::timed_mutex, boost::recursive_mutex, boost::recursive_try_mutex, boost::recursive_timed_mutex 如果互斥体上锁之后没有解锁就会发生死锁。这是一个很普遍的错误,Boost线程库就是要将其变成不可能(至少时很困难)。直接对互斥体上锁和解锁对于 Boost线程库的用户来说是不可能的。mutex类通过teypdef定义在RAII中实现的类型来实现互斥体的上锁和解锁。这也就是大家知道的 Scope Lock模式。为了构造这些类型,要传入一个互斥体的引用。构造函数对互斥体加锁,析构函数对互斥体解锁。C++保证了析构函数一定会被调用,所以即使是 有异常抛出,互斥体也总是会被正确的解锁。 这种方法保证正确的使用互斥体。然而,有一点必须注意:尽管Scope Lock模式可以保证互斥体被解锁,但是它并没有保证在异常抛出之后贡献资源仍是可用的。所以就像执行单线程程序一样,必须保证异常不会导致程序状态异 常。另外,这个已经上锁的对象不能传递给另一个线程,因为它们维护的状态并没有禁止这样做。 List2给出了一个使用boost::mutex的最简单的例子。例子中共创建了两个新的线程,每个线程都有10次循环,在std::cout上打印出 线程id和当前循环的次数,而main函数等待这两个线程执行完才结束。std::cout就是共享资源,所以每一个线程都使用一个全局互斥体来保证同时 只有一个线程能向它写入。 许多读者可能已经注意到List2中传递数据给线程还必须的手工写一个函数。尽管这个例子很简单,如果每一次都要写这样的代码实在是让人厌烦的事。别急, 有一种简单的解决办法。函数库允许你通过将另一个函数绑定,并传入调用时需要的数据来创建一个新的函数。 List3向你展示了如何使用Boost.Bind库来简化List2中的代码,这样就不必手工写这些函数对象了。 例2: #include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <iostream> boost::mutex io_mutex; struct count { count(int id) : id(id) { } void operator()() { for (int i = 0; i < 10; ++i) { boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << i << std::endl; } } int id; }; int main(int argc, char* argv[]) { boost::thread thrd1(count(1)); boost::thread thrd2(count(2)); thrd1.join(); thrd2.join(); return 0; } 例3: // 这个例子和例2一样,除了使用Boost.Bind来简化创建线程携带数据,避免使用函数对象 #include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/bind.hpp> #include <iostream> boost::mutex io_mutex; void count(int id) { for (int i = 0; i < 10; ++i) { boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << i << std::endl; } } int main(int argc, char* argv[]) { boost::thread thrd1( boost::bind(&count, 1)); boost::thread thrd2( boost::bind(&count, 2)); thrd1.join(); thrd2.join(); return 0; } 3 条件变量 有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没 有数据就必须等待数据被压栈。这种情况下的同步使用互斥体是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。 条件变量的使用总是和互斥体及共享资源联系在一起的。线程首先锁住互斥体,然后检验共享资源的状态是否处于可使用的状态。如果不是,那么线程就要等待条件 变量。要指向这样的操作就必须在等待的时候将互斥体解锁,以便其他线程可以访问共享资源并改变其状态。它还得保证从等到得线程返回时互斥体是被上锁得。当 另一个线程改变了共享资源的状态时,它就要通知正在等待条件变量得线程,并将之返回等待的线程。 List4是一个使用了boost::condition的简单例子。有一个实现了有界缓存区的类和一个固定大小的先进先出的容器。由于使用了互斥体 boost::mutex,这个缓存区是线程安全的。put和get使用条件变量来保证线程等待完成操作所必须的状态。有两个线程被创建,一个在 buffer中放入100个整数,另一个将它们从buffer中取出。这个有界的缓存一次只能存放10个整数,所以这两个线程必须周期性的等待另一个线 程。为了验证这一点,put和get在std::cout中输出诊断语句。最后,当两个线程结束后,main函数也就执行完毕了。 1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <boost/thread/condition.hpp> 4 #include <iostream> 5 6 const int BUF_SIZE = 10; 7 const int ITERS = 100; 8 9 boost::mutex io_mutex; 10 11 class buffer 12 { 13 public: 14 typedef boost::mutex::scoped_lock 15 scoped_lock; 16 17 buffer() 18 : p(0), c(0), full(0) 19 { 20 } 21 22 void put(int m) 23 { 24 scoped_lock lock(mutex); 25 if (full == BUF_SIZE) 26 { 27 { 28 boost::mutex::scoped_lock 29 lock(io_mutex); 30 std::cout << 31 "Buffer is full. Waiting ![]() 32 << std::endl; 33 } 34 while (full == BUF_SIZE) 35 cond.wait(lock); 36 } 37 buf[p] = m; 38 p = (p+1) % BUF_SIZE; 39 ++full; 40 cond.notify_one(); 41 } 42 43 int get() 44 { 45 scoped_lock lk(mutex); 46 if (full == 0) 47 { 48 { 49 boost::mutex::scoped_lock 50 lock(io_mutex); 51 std::cout << 52 "Buffer is empty. Waiting ![]() 53 << std::endl; 54 } 55 while (full == 0) 56 cond.wait(lk); 57 } 58 int i = buf[c]; 59 c = (c+1) % BUF_SIZE; 60 --full; 61 cond.notify_one(); 62 return i; 63 } 64 65 private: 66 boost::mutex mutex; 67 boost::condition cond; 68 unsigned int p, c, full; 69 int buf[BUF_SIZE]; 70 }; 71 72 buffer buf; 73 74 void writer() 75 { 76 for (int n = 0; n < ITERS; ++n) 77 { 78 { 79 boost::mutex::scoped_lock 80 lock(io_mutex); 81 std::cout << "sending: " 82 << n << std::endl; 83 } 84 buf.put(n); 85 } 86 } 87 88 void reader() 89 { 90 for (int x = 0; x < ITERS; ++x) 91 { 92 int n = buf.get(); 93 { 94 boost::mutex::scoped_lock 95 lock(io_mutex); 96 std::cout << "received: " 97 << n << std::endl; 98 } 99 } 100 } 101 102 int main(int argc, char* argv[]) 103 { 104 boost::thread thrd1(&reader); 105 boost::thread thrd2(&writer); 106 thrd1.join(); 107 thrd2.join(); 108 return 0; 109 } 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: 1 #include <boost/thread/thread.hpp> 2 #include <boost/thread/mutex.hpp> 3 #include <boost/thread/tss.hpp> 4 #include <iostream> 5 6 boost::mutex io_mutex; 7 boost::thread_specific_ptr<int> ptr; 8 9 struct count 10 { 11 count(int id) : id(id) { } 12 13 void operator()() 14 { 15 if (ptr.get() == 0) 16 ptr.reset(new int(0)); 17 18 for (int i = 0; i < 10; ++i) 19 { 20 (*ptr)++; 21 boost::mutex::scoped_lock 22 lock(io_mutex); 23 std::cout << id << ": " 24 << *ptr << std::endl; 25 } 26 } 27 28 int id; 29 }; 30 31 int main(int argc, char* argv[]) 32 { 33 boost::thread thrd1(count(1)); 34 boost::thread thrd2(count(2)); 35 thrd1.join(); 36 thrd2.join(); 37 return 0; 38 } posted @ 2008-08-02 22:15 风荷小筑 阅读(40) 评论(0) 编辑 收藏 网摘 |
|