以下代码来源于《深入实践Boost:Boost程序库开发的94个秘笈》一书
Boost.Thread库提供了跨操作系统的处理线程的一致接口。这不是一个只包含头文件的库,所以,所有例子都需要链接libboost_thread和libboost_system库。
创建一个执行线程
一个例子,假设需要在绘制用户界面的线程中创建和填充一个大文件:
#include <algorithm>
#include <fstream>
#include <iterator>
void set_not_first_run();
bool is_first_run();
// 长时间执行的函数
void fill_file_with_data(char fill_char, std::size_t size, const char* filename)
{
std::ofstream ofs(filename);
std::fill_n(std::ostreambuf_iterator<char>(ofs), size, fill_char);
set_not_first_run();
}
// ...
// 在绘制用户界面的线程某处
if (is_first_run())
{
// 这将执行很长一段时间,其间用户界面将冻结·········
fill_file_with_data(0, 8 * 1024 * 1024, "save_file.txt");
}
使用boost::bind库
#include <boost/thread.hpp>
// ...
// 在绘制用户界面的线程某处
if (is_first_run())
{
boost::thread(boost::bind(
&fill_file_with_data, 0, 8 * 1024 * 1024, "save_file.txt"
)).detach();
}
工作原理
boost::thread变量接受一个可以不带参数(使用boost::bind提供一个)调用的函数化对象,并创建一个单独的执行线程。该函数化对象将被复制到构造执行线程并将在那里运行。
然后,调用detach()函数,它将做下列工作:
- 执行线程将从boost::thread变量脱离,但将继续执行。
- boost::thread变量将持有一个非线程(Not-A-Thread)状态
如果没有调用detach(),boost::thread的析构函数会注意到,它仍然持有一个线程,并将调用std::terminate,这将终止我们的程序。
如果要确保在做一些其他工作之前一个文件已被创建和写入呢?
if (is_first_run())
{
boost::thread t(boost::bind(
&fill_file_with_data, 0, 8 * 1024 * 1024, "save_file.txt"
));
// 做一些工作
// ...
// 等待线程完成
t.join();
}
对公共资源的同步访问
要从不同的线程中访问一些共同的资源:
#include <cassert>
#include <cstddef>
#include <boost/thread/thread.hpp>
int shared_i = 0;
void do_inc()
{
for (std::size_t i = 0; i < 30000; ++i)
{
// do something...
const int i_snapshot = ++shared_i;
// do something about i_snapshot...
}
}
void do_dec()
{
for (std::size_t i = 0; i < 30000; ++i)
{
// do something...
const int i_snapshot = --shared_i;
// do something about i_snapshot...
}
}
void run()
{
boost::thread t1(&do_inc);
boost::thread t2(&do_dec);
t1.join();
t2.join();
// assert(shared_i == 0); // 糟糕!
std::cout << "shared_i == " << shared_i;
}
这里,shared_i很可能不会等于0,需要确保在某一时刻只有一个线程修改shared_i变量,才能保证这里的shared_i等于0。锁与互斥的使用
1.首先创建一个互斥量:
#include <boost/thread/mutex.hpp>
#include <boost/thread/locks.hpp>
int shared_i = 0;
boost::mutex i_mutex;
2.把所有修改或获取shared_i变量的数据的操作,放置在临界区内:
{ // 临界区开始
boost::lock_guard<boost::mutex> lock(i_mutex);
} // 临界区结束
工作原理
boost::mutex类处理所有的同步工作。当一个线程试图通过使用boost::lock_guard<boost::mutex> 变量锁定它并没有其他线程持有锁时,它会成功获得对一段代码的独占访问,直到锁被解锁或销毁。如果其他线程已经持有一个锁,则试图获取锁的线程将等待,直到另一个线程释放锁。
boost::lock_guard类存储互斥量的引用,在单参数的构造函数中调用lock(),在析构函数钟调用unlock(),所以前面例子使用大括号确定这个类的作用域,使得它在一开始构造时调用lock(),作用域结束时调用了析构unlock()。
利用原子性快速访问公共资源
前面,在对公共资源的访问中,执行了两个系统调用(锁定和解锁互斥量),而只是为了得到一个整数的值,运行缓慢。可以通过使用原子变量来解决:
#include <iostream>
#include <cassert>
#include <cstddef>
#include <boost/thread/thread.hpp>
#include <boost/atomic.hpp>
boost::atomic<int> shared_i(0);
void do_inc()
{
for(std::size_t i = 0; i <= 30000; ++i)
{
// do something...
const int i_snapshot = ++shared_i;
// do something other about i_snapshot
}
}
void do_dec()
{
for(std::size_t i = 0; i <= 30000; ++i)
{
// do something...
const int i_snapshot = --shared_i;
// do something other about i_snapshot;
}
}
int main()
{
boost::thread t1(&do_inc);
boost::thread t2(&do_dec);
t1.join();
t2.join();
assert(shared_i == 0);
std::cout << "shared_i = " << shared_i << std::endl;
return 0;
}
// 这里在linux下编译时吃了很大的坑
// g++ -lboost_thread -lboost_system -o bthread boost_thread.cpp
// 一直编译出错,查了很久,最后编译改成这样
// g++ -o bthread boost_thread.cpp -lboost_thread -lboost_system
// 将链接库放在最后,输出目标放在最前,编译成功
工作原理
处理器提供不能被其他处理器内核干扰的特定的原子操作。这些操作对于一个操作系统看起来是瞬间发生的。Boost.Atomic提供系统原子操作的包装类,并提供一个统一的、可移植的接口。
创建work_queue类
场景:有发布任务的线程和执行被发布的任务的线程,需要设计一个类,可以安全地被这两种类型的线程使用。这个类必须有得到一个任务的方法(或被阻塞并等待一个任务,直到它被另一个线程发布),还要有检查是否有一个任务并得到该任务的方法(如果没有剩余任务,就返回一个空的任务),以及一个发布任务的方法。
做法:实现的类接近std::queue<task_t> 的功能,也将有线程同步。
#include <deque>
#include <boost/function.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/condition_variable.hpp>
class work_queue
{
public:
typedef boost::function<void()> task_type;
private:
std::deque<task_type> tasks;
boost::mutex tasks_mutex;
boost::condition_variable cond;
public:
void push_task(const task_type& task)
{
boost::unique_lock<boost::mutex> lock(tasks_mutex);
tasks.push_back(task);
lock.unlock();
cond.notify_one(); // 唤醒处于cond.wait(lock)的等待线程
}
// 非阻塞的函数,用于获得推送的任务或空任务
task_type try_pop_task()
{
task_type ret;
boost::lock_guard<boost::mutex> lock(tasks_mutex);
if(!tasks.empty())
{
ret = tasks.front();
tasks.pop_front();
}
return ret;
}
// 阻塞函数,用于获取一个被推送的任务或者当任务由另一个线程推送时起阻塞的作用
task_type pop_task()
{
boost::unique_lock<boost::mutex> lock(tasks_mutex)
while(tasks.empty())
{
cond.wait(lock); // 暂停执行线程,直到其他线程通知该线程
}
task_type ret = tasks.front();
tasks.pop_front();
return ret;
}
}
使用类
work_queue g_queue;
void do_nothing(){}
const std::size_t tests_tasks_count = 3000;
void pusher()
{
for(std::size_t i = 0; i < tests_tasks_count; ++i)
{
g_queue.push_task(&do_nothing);
}
}
void poper_sync()
{
for(std::size_t i = 0; i < tests_tasks_count; ++i)
{
g_queue.pop_task()();
}
}
int main()
{
boost::thread pop_sync1(&poper_sync);
boost::thread pop_sync2(&poper_sync);
boost::thread pop_sync3(&poper_sync);
boost::thread push1(&pusher);
boost::thread push2(&pusher);
boost::thread push3(&pusher);
// 等待所有任务弹出
pop_sync1.join();
pop_sync2.join();
pop_sync3.join();
push1.join();
push2.join();
push3.join();
assert(!g_queue.try_pop_task());
g_queue.push_task(&do_nothing);
assert(g_queue.try_pop_task());
return 0;
}
多读者单写者锁
一个集合可能会被多个线程访问,但很少会被修改,如果用前面的方法来处理多线程共享资源访问的办法,任何操作都要申请获得一个mutex变量的独占锁,所以即使是获得资源也将导致在被锁定的互斥量上等待,现在,解决的方法是对于不修改数据的操作,将boost::unique_locks替换为boost::shared_lock:
#include <boost/thread/shared_mutex.hpp>
struct user_info
{
std::string address;
unsigned short age;
// ...
};
class users_online
{
typedef boost::shared_mutex mutex_t;
mutable mutex_t users_mutex;
std::map<std::string,user_info> users;
public:
bool is_online(const std::string& username) const
{
boost::shared_lock<mutex_t> lock(users_mutex);
return users.find(username) != users.end();
}
unsigned short get_age(const std::string& username) const
{
boost::shared_lock<mutex_t> lock(users_mutex);
return users.at(username).age;
}
void set_online(const std::string& username,const user_info& data)
{
boost::lock_guard<mutex_t> lock(users_mutex);
users.insert(std::make_pair(username,data));
}
// ...
}
工作原理
boost::shared_mutex允许多个线程同时获取数据,允许共享锁(读锁),允许多个对资源的同时访问,仅当要修改数据时,才需要独占地拥有互斥量。
当尝试独占地锁定被共享锁定的资源时,操作将被阻塞,直到没有读锁剩余,并且仅当那个资源被独占地锁定后,才迫使新的共享锁等待,直到独占锁被释放。
当确实只需要独占的锁时,不要使用boost::shared_mutex,因为它比通常的boost::mutex类稍微慢一点,然而,在另一些情况下,可能带来很大的性能增益,例如,对于4个读线程,共享互斥的运行速度几乎比boost::mutex快4倍以上
创建对每个线程都是独占的变量
前面,work_queue的例子中,每个任务都可以在多个线程之一中执行,但不知道是哪一个线程,如果要使用一些连接来发送一个任务的执行结果:
#include <boost/noncopyable.hpp>
class connection:boost::noncopyable
{
public:
// 打开一个连接是一个缓慢的工作
void open();
void send_result(int result);
// ...
};
解决方案:
- 当需要发送数据时,打开一个新的连接(这是缓慢的)
- 让所有线程都有单个的连接,并将它们包装在互斥量中(这也是缓慢的)
- 建立一个连接池,从中以线程安全的方式获得一个连接,并使用它(这需要编写很多代码,但这个解决方案是快速的)
- 使每个线程有单个的连接(快速并且简单地实现)
实现:
制作一个线程局部变量:
#include <boost/thread/tss.hpp>
connection& get_connection();
boost::thread_specific_ptr<connection> connection_ptr;
connection& get_connection()
{
connection* p = connection_ptr.get();
if(!p)
{
connection_ptr.reset(new connection);
p = connection_ptr.get();
p->open();
}
return *p;
}
使用线程特定的资源:
void task()
{
int result;
// some calculate
// ...
// 发送结果
get_connection().send_result(result);
}
工作原理
boost::thread_specific_ptr变量对每个线程都拥有独立的指针。最初,这个指针等于NULL,所以,检查p,当p是NULL时,打开一个连接,当从已经初始化这个指针的线程进入get_connection()时,!p为false,将返回已经打开的连接。当线程退出时,将为该指针调用delete,所以并不需要担心内存泄露。
中断线程
中断停止一个线程很简单
boost::thread t1(&do_somethingfunc());
if(need_stop)
{
t1.interrupt();
}
操纵一组线程
对于这样的例子:
boost::thread t1(&some_function());
boost::thread t2(&some_function());
boost::thread t3(&some_function());
t1.join();
t2.join();
t3.join();
boost::thread_group类可以操纵一组线程:
boost::thread_group threads;
// 启动10个线程
for(unsigned int i = 0; i < 10; ++i)
{
threads::create_thread(&some_function);
}
//加入所有线程
threads.join_all();
// 也可以调用threads.interrupt_all();中断所有线程