文章目录
p19 某个线程只可以join()一次
只要调用了join()
,隶属于该线程的任何存储空间即会因此清除,std::thread
对象遂不再关联到已结束的线程。
事实上,它(std::thread
)与任何线程均无关联。其中的意义是,对于某个给定的线程,join()
仅能调用一次;只要std::thread
对象曾经调用过join()
,线程就不再可汇合(joinable),成员函数joinable()
将返回false。
p22 只有当joinable()返回true时才能调用detach()
若没有与std::thread
对象关联的任何线程,便不能凭空调用detach()
。
只有当t.joinable()
返回true时才可调用t.detach()
。
P21 在std::thread对象析构前,必须明确是等待还是分离线程
在std::thread
对象析构前,必须明确,是等待线程完成(join)还是要与之分离(detach)。
如果等待std::thread
对象销毁之际还没决定好,那std::thread
的析构函数将会调用std::terminate()
终止整个程序。
示例代码:
#include <thread>
#include <iostream>
void do_lengthy_work()
{
std::cerr << __FUNCTION__;
};
int main(void)
{
{
std::thread t(do_lengthy_work);
//t.join()
//理应在这里join或detach
}
while (1)
{
}
}
这里我们既不join也不detach,那么就会导致std::terminate()
被调用,导致程序终止。
P25 移动语义
若源对象是临时变量,移动就会自动发生。
若源对象是具名变量,则必须通过调用std::move()
直接请求转移。
标准库中有一些类的归属权语义是 是只可移动但不可复制的,如std::thread
、std::unique_ptr
。
P23 向线程传递参数
线程具有内部存储空间,参数会按照默认方式复制到该处,新创建的执行线程才能直接访问它们。
然后,这些副本被当成临时变量,以右值形式传给新线程上的函数或可调用对象。
——即使函数的相关参数应该是引用,上述过程依然会发生。
Example
void f(int i, std::string const& s);
void oops(int some_param)
{
char buf[1024];
sprintf(buf, "%d", some_param);
std::thread t(f, 3, buf);
t.detach();
}
本例中,向新线程传递的参数是buf
,类型是char*
,指向一个局部数组。
我们原本涉嫌,buf
会在新线程内转换成std::string
对象,但是,在此完成之前,oops()
函数极有可能已经退出,buf
所指的局部变量已经销毁。
这一问题的根源在于,
buf
到std::string
的转换未及时发生,可以显式地转换为std::string
传递:
std::thread t(f, 3, std::string(buf));
与之相反的是想要传递引用:
void func(Data& data);
int main()
{
Data d;
std::thread t(func, d);//这里会编译失败
}
解决办法是:std::thread t(func, std::ref(data))
P25 将类的成员函数设定为线程函数
class X{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work, &my_x);
若要将某个类的成员函数设定为线程函数,应传入一个函数指针,指向该成员函数。
此外,还要给出合适的对象指针,作为该函数的第一个参数。
p41 std::mutex和类模板std::lock_guard<>
在C++中,通过构造std::mutex
的实例来创建互斥,调用lock()
成员函数对其加锁,调用unlock()
解锁。
但不推荐直接条约成员函数的做法。
因为,若按此处理,在函数以外的每条代码路径上都要调用unlock()
,包括由于异常退出的路径。(注:这个路径应该指的是代码结束路径,如return、exit,总之,跳出的时候)
推荐用C++标准库提供的类模板std::lock_guard<>
。
示例代码:
#include <algorithm>
#include <list>
#include <mutex>
std::list<int> some_list;
std::mutex some_mutex;
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}
void list_contains(int value_to_find)
{
std::lock_guard<std::mutex> guard(some_mutex);
if(std::find(some_list.begin(), some_list.end(), value_to_find) == some_list.end())
return false;
else
return true;
}
如果使用的是C++17,还可以将
std::lock_guard<>
简化为std::lock_guard
,省略模板参数列表。这是一个新特性,名为类模板参数推导。
P75 条件变量std::condition_variable, std::condition_variable_any。
使用示例:
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
using data_chunk = int;
std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
bool more_data_to_prepare = true;
data_chunk
prepare_data ()
{
return data_chunk();
}
void
data_preparation_thread ()
{
while (more_data_to_prepare) {
data_chunk data = prepare_data();
{
std::lock_guard<std::mutex> lk (mut);
data_queue.push (data);
}
data_cond.notify_one();
}
}
void
data_processing_thread ()
{
while (true) {
std::unique_lock<std::mutex> lk (mut);
data_cond.wait (lk, [] { return !data_queue.empty(); });
data_chunk data = data_queue.front();
data_queue.pop();
lk.unlock();
// process data ...
// next continue ...
}
}
要注意的是:
-
wait()
wait
只有在条件为true(例如上面的lambda表达式)才返回,并保持互斥;- 否则,线程继续进入休眠,并解锁互斥。
wait()
中线程可能苏醒并查验条件,若条件不满足则继续睡眠,这种叫做伪唤醒。- C++标准规定,伪唤醒的数量和频率都不确定,因此,若判断函数有副作用,不建议选取它来查验条件。
-
调用
notify_one()
通知条件变量。 -
必须使用
std::unique_lock
,而不能使用std::lock_guard
,因为需要中途解锁、重加锁,std::lock_guard
不支持这些功能。
- 在线程间传递数据的常见方法是使用队列。
- 在必要范围之外锁住互斥是不当的设计思维。