第二章 线程管控
线程的基本管控
-
发起线程
void do_some_work(); std::thread my_thread(do_some_work);
class background_task { public: void operator()()const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);//f将会被复制到属于新线程的存储空间中,由新线程调用 std::thread mythread([]{ do_something(); do_something_else(); });
若程序不等待线程结束,在线程运行结束前,需保证它所访问的外部数据始终正确,有效。
struct func { int &i; func(int&i_):i(i_){} void operator()() { for(unsigned j=0;j<100000;++j) { do_something(i);//可能会导致悬空引用 } } }; void oops() { int some_local_state=0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach();//不等待新线程结束,oops函数可能已经结束,而新线程还在继续运行,some_local_state已被销毁,新线程访问谁? //解决方式:1.汇合新线程2.将数据完全复制 }
-
等待线程完成(join())
-
在出现异常情况下等待
struct func; 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(...)//若捕捉到异常,则调用join(),确保线程在f()结束前终结。 { t.join(); throw; } t.join(); }
class thread_guard { std::thread &t; public: explicit thread_guard(std::thread&t_):t(t_){} ~thread_guard() { if(t.joinable()) { t.join(); } } thread_guard(thread_guard const &)=delete; thread_guard& operator=(thread_guard const &)=delete; }; struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); //当主线程执行到f()末尾时,按构建的逆序,销毁局部对象。 thread_guard g(t);//实例g在f()函数结束时会被析构,调用join()。 do_something_in_current_thread();//即使该函数抛出异常,也会在析构时确保join()被调用。 }
-
在后台运行线程(detach())
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);//开启新线程 t.detach();//分离 } else { process_user_input(cmd); } } }
-
-
向线程函数传递参数
-
方式:直接向std::thread的构造函数添加更多参数。
线程具有内部存储空间,参数会按照默认方式先复制到该处,新创建的执行线程才能直接访问它们。然后,这些副本被当成临时变量,以右值形式传给新线程上的函数或可调用对象。即使函数的相关参数按设想应该是引用。void f(int i,std::string const&s); std::thread t(f,3,"hello");//字符串字面内容以char const*的形式传入,在新线程内部转为string。
问题:主线程已经结束并销毁空间,而转换未及时进行,转换指向已经销毁的内容?
解决方式:构造一个临时对象,提前转换。
std::thread t(f,3,string("hello"));
若想要 non const 引用。
std::thread t(update_data_for_widget,w,std::ref(data));//传入data并非副本,而是引用。
类的成员函数设定为线程函数:
class X
{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x);
unique_ptr指针
void process_big_object(std::unique_ptr<big_object>);
std::unique_ptr<big_object>p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));
在调用std::thread 的构造函数时,依据std::move§所指定的操作,big_object对象的归属权会发生转移,先进入新创建的线程内部存储空间,再转移给process_big_object()函数。
std::move§未进行移动操作,仅仅将p转换成右值;创建线程时,发生一次转移(由线程库内部进行复制);process_big_object()开始执行时,再次转移(函数调用复制参数)。
移交线程归属权
std::thread支持移动操作的意义是,函数可以便携的向外部转移线程的归属权
//从函数内部返回std::thread对象
std::thread f()
{
void some_function();
return std::thread(some_function);
}
//归属权转移到函数内部
void f(std::thread t);
void g()
{
void some_function();
f(std::thread(some_fuction));
std::thread t(some_function);
f(std::move(t));
}
新类scoped_thread,在离开其对象的所在作用域前,确保线程已经完结。
class scoped_thread
{
std::thread t;
public:
explicit socped_thread(std::thread t_):t(std::move(t_))
{
if(!t.joinable())
{
throw std::logic_error("No thread");
}
}
~scoped_thread()
{
t.join();
}
scoped_thread(scoped_thread const &)=delete;
scoped_thread& operator=(scoped_thread const &)=delete;
};
在thread_guard中:析构函数需判断线程是否依然可汇合。
class joining_thread
{
std::thread t;
public:
joining_thread()noexcept=default;
template<typename Callable,typename ...Args>
explicit joining_thread(Callable&&func,Args &&...args):
t(std::forward<Callable>(func),std::forward<Args>(args)...)//给线程传参
{}
explicit joining_thread(std::thread t_)noexcept:t(std::move(t_))
{}
joining_thread(joining_thread&&other)noexcept:t(std::move(other.t))
{}
joining_thread& operator=(joining_thread&&other)noexcept
{
if(joinable())
join();
t=std::move(other);
return *this;
}
~joining_thread()noexcept
{
if(joinable())
join();
}
void swap(joining_thread&other)noexcept
{
t.swap(other.t);
}
std::thread::id get_id()const noexcept
{
return t.get_id();
}
bool joinable()const noexcept
{
return t.joinable();
}
void join()
{
t.join();
}
void detach()
{
t.deatach();
}
std::thread&as_thread()noexcept
{
return t;
}
//const 调用该版本,effective C++
const std::thread&as_thread()const noexcept
{
return t;
}
};
在运行时选择线程数量
std::thread::hardware_concurrency();
//并行版的 std::accumulate()的简单实现
template<typename Iterator,typename T>
struct accumulate_block
{
void operator()(Iterator first,Iterator last,T&result)
{
result=std::accumulate(first,last,result);
}
};
template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
unsigned long const length=std::distance(first,last);
if(!length)
return init;
unsigned long const min_per_thread=25;//每个线程处理最低定量为25
unsigned long const max_threads=(length+min_per_thread-1)/min_per_thread;//线程的最大数量
unsigned long const hardware_threads=std::thread::hardware_concurrency();//硬件支持量
unsigned long const num_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads);//运行线程不应超过硬件支持数
unsigned long const block_size=length/num_threads;//各线程需要分担的元素数量
std::vector<T>results(num_threads);//存放每个线程结果
std::vector<std::thread> threads(num_threads-1);//存放线程,主线程算一
Iterator block_start=first;
for(unsigned long i=0;i<(num_threads-1);++i)
{
Iterator block_end=block_start;
std::advance(block_end,block_size);
threads[i]=std::thread(
accumulate_block<Iterator,T>(),block_start,block_end,std::ref(results[i])
);
block_start=block_end;
}
accumulate_block<Iterator,T>()(
block_start,last,results[num_threads-1]);//主线程处理最后一小块
for(auto&entry:threads)
{
entry.join();
}
return std::accumulate(results.begin(),results.end(),init);
}
识别线程
std::thread t(do_something); t.get_id();
std::this_thread::get_id();