C++多线程学习---线程管理基础

1.堤防函数已经结束,线程依然访问局部变量

看以下例子:
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患:悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
} // 3. 新线程可能还在运行
这个例子中 some_local_state是一个局部变量,在函数退出后局部变量已经不存在了,但是如果线程尚未结束的话,会调用不存在的局部变量,发生错误。因此使用一个能访问局部变量的函数去创建线程是一个糟糕的注意。

2.等待线程完成

如果需要等待线程,调用std::thread的join()函数,上述例子如果将my_thread.detach()替换为my_thread.join(),就可以确保局部变量在线程完成后才被销毁。这种情况下原始线程在其生命周期中没有什么事可做,使得用一个独立的线程去执行函数变得收益甚微,但在实际编程中,原始线程要么有自己的工作要
做;要么会启动多个子线程来做一些有用的工作,并等待这些线程结束。

3.特殊情况下的等待
需要对一个还未销毁的 std::thread 对象使用join()或detach()。如果想要分离一个线程,可以在线程启动后,直接使用detach()进行分离。如果打算等待对应线程,则需要细心挑选调用join()的位置。当在线程运行之后产生异常,在join()调用之前抛出,就意味着很这次调用会被跳过。
等待线程完成:
struct func; // 定义在清单2.1中
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
try
{
do_something_in_current_thread();
}
catch(…)
{
t.join(); // 1
throw;
}
t.join(); // 2
}
上述例子在线程正常退出后,会跳到2处。如果抛出异常,则会执行到1处。以此保证线程退出后,函数才结束。

还可以采用一致RAII的方式,提供一个类,在析构函数中使用join();
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_)
{}
~thread_guard()
{
if(t.joinable()) // 1
{
t.join(); // 2
}
}
thread_guard(thread_guard const&)=delete; // 3
thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
} // 4
当线程执行到4处时,局部对象要被逆序销毁了。因此thread_guard对象g时第一个被销毁的,这是线程在析构函数中被加入2到原始线程中,即使线程中抛出异常,这个销毁依旧会发生。joinable时标记当前线程是否已加入到1中,因为join只能对给定的对象调用一次,所以对给已加入的线程在此进行加入操作时会导致错误。
4.后台运行线程
后台线程也被成为守护线程。这类线程的特点是发后即忘.
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
例如让多文档同时编辑多个文档,可以设计成这里就可以让文档处理窗口运行在分离的线程上。
void edit_document(std::string const& filename)
{
open_document_and_display_gui(filename);
while(!done_editing())
{
user_command cmd=get_user_input();
if(cmd.type==open_new_document)
{
std::string const new_name=get_filename_from_user();
std::thread t(edit_document,new_name); // 1
t.detach(); // 2
}
else
{
process_user_input(cmd);
}
}
如果用户选择打开一个新文档,为了让迅速打开文档,需要启动一个新线程去打开新文档①,并分离线程②。与当前线程做出的操作一样,新线程只不过是打开另一个文件而已。所以,edit_document函数可以复用,通过传参的形式打开新的文件。这个例子也展示了传参启动线程的方法:不仅可以向std::thread 构造函数①传递函数名,还可以传递函数所需的参数(实参)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值