C++标准库没有涉及线程,在C++中,虽然不可能写出标准兼容的多线程程序,程序员可以使用特定操作系统提供的线程库来写出多线程程序来。可是,这至 少导致两个突出的问题:操作系统普遍提供的是C库,在C++中使用要更小心,每个操作系统都有自己的一套支持多线程的库;另外,代码不标准,不可移植。 Boost.Threads 可以解决这些问题。BY firebird32
创建线程
boost::thread 类描述线程的执行。缺省构造器创建一个当前线程的执行的实例。重载构造器调用一个无参也无返回值的函数对象,这个构造器启动一个新线程,其调用函数对象执行线程。
刚 开始好像这样设计不如典型的C实现方式,创建一个线程,一个参数是void指针类型,可传递一个被新线程调用的函数,另一个参数可传递特定的数据到线程。 但是,Boost.Threads 用函数对象代替函数指针,这使得函数对象携带线程需要的数据。这种方法更灵活,并且是线程安全的。对于和函数对象库的组合,比如 Boost.Bind ,这种设计允许你容易地传递任何数量的数据到新创建的线程中。
boost::thread 的 == 和 != 方法用来比较两个线程对象是否在一个线程中;可以调用join()方法等待线程结束。例1是一个使用boost::thread 类的简单例子,创建一个新线程,在新线程中打印"Hello world, I'm a thread!",在main线程中等待这个线程的结束。
例1:
#include <boost/thread/thread.hpp>
#include <iostream>
void hello()
{
std::cout <<
"Hello world, I'm a thread!"
<< std::endl;
}
int main(int argc, char* argv[])
{
boost::thread thrd(&hello);
thrd.join();
return 0;
}
互斥体
我 们要知道在多线程程序中多个线程同时访问共享资源时如何同步。假如一个线程试图修改共享数据的值,同时另一个线程试图读这个值,结果是不确定的。为防止这 种情况发生,要用专门的原子类型和原子操作。有一种叫做互斥(mutex)。互斥在同一时刻仅允许一个线程访问共享资源。当一个线程要访问共享资源的时 候,它首先要“锁定(lock)”互斥,假如另一个线程先一步锁定了互斥,这个线程要等待直到另一个线程“解除锁定(unlock)”互斥,这就保证了同 时仅有一个线程访问共享资源。
互斥有几个变种。 Boost.Threads 支持两类互斥,简单互斥和递归互斥。简单互斥仅能被锁定一次,假如同一线程试图锁定互斥两次,将造成死锁,导致这个线程永远等待下去。对于递归互斥,单个 线程可以锁定互斥多次,也必须解除锁定相同的次数,这样才能使其他线程锁定互斥。
对于这两种互斥中,一个线程可以通过如下三种途径锁定互斥:
1.尝试且锁定互斥,等待直到没有其他线程锁定互斥。
2.尝试且锁定互斥,假如其他线程已经锁定互斥就立即返回。
3.尝试且锁定互斥,等待直到没有其他线程锁定互斥或直到指定的超时时间到。
Boost.Threads 提供以下六种互斥类型:boost::mutex, boost::try_mutex, boost::timed_mutex, boost::recursive_mutex, boost::recursive_try_mutex, 和 boost::recursive_timed_mutex。
假如一个互斥被锁定,但是没有被解锁,就会导致死锁。这个错误非常普遍,因 此Boost.Threads的设计可以避免这种情况的发生。不直接使用锁定、解锁操作是可行的,使用锁对象,锁对象的构造中锁定,在析构的时候解锁。 C++语言的规则确保析构总是被调用,因此当异常发生时,互斥将被解锁。
虽然这种机制对使用互斥有帮助,但是当异常发生时只能保证互斥被解锁,却可能使共享数据处于无效状态。因此当异常发生时,使数据处于不一致状态使程序员的责任。另外,共享的资源要在几个线程之间共享,确保这些线程都能访问到它。
例2是使用boost::mutex类的简单例子。创建两个线程,循环10次,写ID到std::cout。main线程等待这两个线程结束。std::cout对象是共享资源,因此每个线程使用全局互斥来保证每次只有一个线程写它。
例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;
}
条件变量
有时候锁定共享资源且使用它满足不了应用。有时共享资源在能够使用前需要满足一些特定状态。例如,某线程试图把数据从栈中取出,要是数据没有到达它需要等待。互斥不能满足这种同步。另一种同步类型,叫做条件变量,能够满足这种情况。
条 件变量总是和互斥、共享资源一起使用。一个线程首先锁定互斥,然后坚持共享资源是否处于特定状态,假如不在所需要的状态,线程在条件变量上等待。在等待期 间这个操作引发互斥解锁,因此另一线程可以改变共享资源的状态。当等待操作结束会确保互斥又被锁定。当另一线程改变了共享资源的状态,其需要通知等待条件 变量的线程,确使等待线程从等待操作返回。
例4是使用boost::condition类的简单例子。
#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是不可重入的,以为它用静态数据保存字符串来分解成标记。
有 两种途径可使一个不可重入函数变成可重入函数。一种途径使修改函数接口,譬如可以传递一个指向数据的指针或引用来代替静态数据。这种方案简单且性能最佳, 但是需要改变公共接口,导致应用代码的大量修改。另一种途径是不改变公共接口,用线程局部存储(有时也叫线程特定存储)代替静态数据。
线 程局部存储时数据和特定线程相关联。多线程库给出存取线程局部存储的接口,可存取当前线程的数据实例。每个线程提供其自己的数据实例,因此对同时存取就不 会有任何问题。但是,线程局部存储比静态数据或局部数据要慢。因此这也不总是最好的方案。但是它是不改变公共接口的唯一方案。
Boost.Threads 通过智能(smart)指针 boost::thread_specific_ptr 来存取线程局部存储。首先每个线程试图获取这个智能指针的一个实例,其有一个 NULL 值,因此第一次使用的时候检查指针要是为 NULL,就需要初始化。Boost.Threads确保线程局部存储的数据在线程退出的时候被清除。
例5是使用 boost::thread_specific_ptr 的简单例子。
例5:
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/tss.hpp>
#include <iostream>
boost::mutex io_mutex;
boost::thread_specific_ptr<int> ptr;
struct count
{
count(int id) : id(id) { }
void operator()()
{
if (ptr.get() == 0)
ptr.reset(new int(0));
for (int i = 0; i < 10; ++i)
{
(*ptr)++;
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << id << ": "
<< *ptr << std::endl;
}
}
int id;
};
int main(int argc, char* argv[])
{
boost::thread thrd1(count(1));
boost::thread thrd2(count(2));
thrd1.join();
thrd2.join();
return 0;
}
仅运行一次的例程
一 般初始化例程仅执行一次,不能被多次执行,这就需要保证初始化例程是线程安全的,也就是说仅有一个线程能调用执行。这种情况叫做“仅运行一次的例程”。一 个“仅运行一次的例程”在应用程序中只能被调用一次。假如多个线程试图在同时执行这个例程,仅有一个实际能执行,其他都将等待这个线程执行完毕后才返回。 Boost.Threads 通过boost::call_once 函数和boost::once_flag 标志类型和一个特定的宏定义BOOST_ONCE_INIT来支持“仅运行一次的例程”。
例6 展示了 boost::call_once 的简单使用。
例6:
#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;
}