让我们先来看看std::thread类,它负责管理线程的执行过程。
启动线程
我们创建一个std::thread类的实例来启动一个新线程,用一个线程函数作为构造函数的参数。如
void do_work();
std::thread t(do_work);
std::thread类也接受一个函数对象作为参数。
class do_work
{
public:
void operator()();
};
do_work dw;
std::thread t(dw);
注意这里只是传了一个对象的拷贝进去。如果想传递对象本身(你应该确保在线程结束前它没有被销毁),可以使用std::ref。
do_work dw;
std::thread t(std::ref(dw));
很多创建线程的API允许传递一个参数给线程,例如long或void*。Std::thread也支持参数,并且可以是任意类型任意个数的参数。
void do_more_work(int i,std::string s,std::vector<double> v);
std::thread
t(do_more_work,42,"hello",std::vector<double>(23,3.141));
也可以用std::ref传引用。
void foo(std::string&);
std::string s;
std::thread t(foo,std::ref(s));
知道了怎么启动线程,那我们如何等待线程结束呢?新标准中有个术语叫joining来表示与线程协同工作, 我们使用成员函数join()
void do_work();
std::thread t(do_work);
t.join();
如果你不想joining你的线程,可以销毁线程对象或者调用detach()。
void do_work();
std::thread t(do_work);
t.detach();
启动线程就这么简单。接下来我会介绍一下线程间如何共享数据。
数据保护
同许多线程API一样,C++0x用互斥来保护共享数据。有四种互斥类型:
Non-recursive (std::mutex)
Recursive (std::recursive_mutex)
允许锁超时的non-recursive (std::timed_mutex)
允许锁超时的recursive (std::recursive_timed_mutex)
如果你试图在一个线程上锁(lock)一个non-recursive mutex两次而当中没有unlock它的话,会产生未知结果。递归recur6sive mutex只是增加锁的计数,因此必须确保你unlock和lock的次数相同,其他线程才可能锁这个mutex。
通常我们用模板类std::unique_lock<>和std::lock_guard<>来lock和unlock一个mutex。这些类在构造函数中lock一个mutex,在析构函数中unlock它。因此,如果你用的是局部变量,你的mutex会在退出作用域时自动被unlock。
std::mutex m;
my_class data;
void foo()
{
std::lock_guard<std::mutex> lk(m);
process(data);
} // mutex unlocked here
std::lock_guard
只能像上面这样使用。而
std::unique_lock
允许延迟
lock
、设置超时,以及在对象销毁之前
unlock
。
如果你选择
std::timed_mutex
来设置锁超时的话,那需要使用
std::unique_lock:
std::timed_mutex m;
my_class data;
void foo()
{
std::unique_lock<std::timed_mutex>
lk(m,std::chrono::milliseconds(3)); // wait up to 3ms
if(lk) // if we got the lock, access the data
process(data);
} // mutex unlocked here
由于这些
lock
类是模板,因此他们可以用于所有标准的
mutex
类型,以及提供了
lock()
和
unlock()
函数的扩展类型。
避免死锁
有时候,我们需要锁多个
mutex
。如果控制不力,可能导致死锁(
deadlock
):两个线程都试图锁相同的
mutex
,每个线程都锁住
一个
mutex
,而等待另外一个线程释放其他的
mutex
。
C++0x
考虑到了这个问题,你可以使用
std::lock
函数来一次锁住多个
mutex
,
而不必冒着死锁的危险来一个一个地锁:
struct X
{
std::mutex m;
int a;
std::string b;
};
void foo(X& a,X& b)
{
std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);
std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);
std::lock(lock_a,lock_b);
// do something with the internals of a and b
}
在上面的例子中,如果你不使用
std::lock
的话,将很可能导致死锁(如一个线程执行
foo(x,y),
另一个执行
foo(y,x)
)。
加上
std::lock
后,则是安全的。
在初始化时保护数据
如果你的数据需要在初始化时被保护,就不能再使用mutex了。因为在初始化结束后,这会引起不必要的同步。C++0x提供了很多方法来在初始化时保护数据。
1)假定你的构造函数是用constexpr关键字声明并且满足常量初始化的条件。在这种情况下,一个静态存储区的对象在静态初始阶段会确保在其他代码运行之前被初始化。对于std::mutex来说,这是最佳选择,因为它消除了全局mutex初始化时产生紊乱的可能性。
class my_class
{
int i;
public:
constexpr my_class():i(0){}
my_class(int i_):i(i_){}
void do_stuff();
};
my_class x; // static initialization with constexpr constructor
int foo();
my_class y(42+foo()); // dynamic initialization
void f()
{
y.do_stuff(); // is y initialized?
}
2)在一个块作用域(block scope)中使用静态变量。在C++0x中,块作用域的静态变量在函数第一次被调用时初始化。如果另一个线程在初始化完成之前试图调用该函数,它必须等待。
void bar()
{
static my_class z(42+foo()); // initialization is thread-safe
z.do_stuff();
}
3)如果以上情况都不适用(对象可能是动态创建),那么最好使用std::call_once和std::once_flag。从名字就可以看出,std::call_once用于与一个std::once_flag实例协作,指定的函数将只会执行一次。
my_class* p=0;
std::once_flag p_flag;
void create_instance()
{
p=new my_class(42+foo());
}
void baz()
{
std::call_once(p_flag,create_instance);
p->do_stuff();
}
同std::thread构造函数一样,std::call_once也可以接受函数对象作为参数,并且接受多个参数。再次强调,默认是传拷贝。如果要传引用,请使用std::ref.
等待事件
如果想在线程间共享数据,通常需要一个线程等待另一个线程执行某些操作。我们希望这不要花费CPU时间。如果线程只是等待访问共享数据,那mutex锁就足够了。不过,这样做有时并达不到想要的结果。
最简单的方法是让线程Sleep一段时间,然后去检查是否可以进行想要的操作。一定要确保你用来保护指示事件已经发生的数据的mutex在线程休眠的时候已经unlock(这话是不是听着很别扭?呵呵,我也不知道怎么翻译会容易让人理解一些。不过看了下面这段代码,相信你会明白的):
std::mutex m;
bool data_ready;
void process_data();
void foo()
{
std::unique_lock<std::mutex> lk(m);
while(!data_ready)
{
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
lk.lock();
}
process_data();
}
这是最简单的方法,但是并不好。有两个原因。第一,在数据准备好之后,线程在被唤醒后去检查数据之前平均需要等待5毫秒。这有时会造成明显的滞后。尽管可以通过减少等待时间来解决,但是这会带来第二个问题:每隔10毫秒,线程必须醒来,获得mutex,检查flag,即使什么也没有发生。这将耗费CPU时间和增加对mutex的抢占。因此,这将潜在地减慢了正在等待的线程去执行任务。
如果你发现你的代码是那么写的,那么请用条件变量来代替。不要让线程等待一段确定的时间,你可以让线程休眠直到收到另一个线程通知。这可以有效地让等待线程的CPU使用率为0。我们可以用条件变量来重写foo函数:
std::mutex m;
std::condition_variable cond;
bool data_ready;
void process_data();
void foo()
{
std::unique_lock<std::mutex> lk(m);
while(!data_ready)
{
cond.wait(lk);
}
process_data();
}
注意上面的代码把 lock对象lk作为参数传给了wait()函数。条件变量在wait()函数的unlock这个mutex,在退出函数的时候再将其lock。这样可以确保线程在休眠时被保护的数据可以被其他线程修改。设置data_ready标志的代码可以这样写:
void set_data_ready()
{
std::lock_guard<std::mutex> lk(m);
data_ready=true;
cond.notify_one();
}
你仍然需要检查数据是否已经准备好,因为条件变量有可能被恶意唤醒,此时wait()函数将返回尽管它没有被另一个线程通知。如果你担心这种情况,你可以让标准库来帮你搞定。你只需要指明你在等待什么即可。
void foo()
{
std::unique_lock<std::mutex> lk(m);
cond.wait(lk,[]{return data_ready;});
process_data();
}
转载自: http://abigbadboy.blog.163.com/blog/static/2597155201231692437456/
个人心得:
线程这个东西,跟指针一样,用得好解决很多问题效果很显著,用得不好,各种问题就来了。
无论是 CreateThread . AfxBeginThread , _beginthread,_beginthreadx,boost::thread ,线程最好的结束方法是让起自己执行完毕,自己结束,这样就是最安全的。 强制性结束的线程,可能有很多问题 。
最近使用boost线程的时候 在网上看到很多人都说使用detach可以强制结束线程。
我自己测试了哈 ,感觉没的效果。 我用的是boost.1.47.0 release.
后来看了源代码,那个detach 根本就没实现。 其他版本不知道,这个版本只有个声明。
意味着这个函数无效。。
所以无论什么线程,最好让其自己在内部结束。