最近在看《C++并发编程实战》,虽然翻译略差,还是开篇记录一下。
启动线程
最简单的启动线程的方式:
void do_something();
std::thread my_thread(do_something());
std::thread
可以传入任何callable的类型,所以你可以用一个重载了operator()
的类的实例构造std::thread
。
class A
{
public:
void operator()() const
{
std::cout << "operator()";
}
};
A a;
std::thread my_thread(a);
my_thread.join();
这里输出operator()
。然而看到std::thread my_thread(a)
,是不是感觉它很像函数声明?考虑当一个临时未命名的变量传递给std::thread
,如
std::thread my_thread(A());
编译器会认为其声明了函数my_thread()
,参数为一个指向返回类型为A的函数指针。下面的代码展示了该过程:
std::thread my_thread(A())
{
std::cout << "Functioh new_thread";
return std::thread();
}
A test()
{
std::cout << "test\n";
return *new A();
}
A (*p)();
p = test;
my_thread(p);
该例输出Functioh new_thread
。为了避免这种情况,可以使用:
std::thread my_thread((A())); //添加额外的括号防止其杯解释为函数声明
std::thread my_thread{A()}; //统一初始化语法
当然也可以使用lambda:
std::thread my_thread([]{
do_something();
});
等待线程完成
当你启动了一个线程,需要确保在std::thread
对象被销毁前join()
或者detach()
。如果在线程启动后且join()
有异常产生,join()
的调用有可能被跳过。下面的代码提供了一个简单的解决方式:
struct func
{
int& i;
func(int& i_): i(i_){}
void operator()()
{
for unsigned j = 0; j < 1000000; ++j)
{
std::cout << j << std::endl;
}
}
};
void f()
{
int i = 0;
func my_func(i);
std::thread t(my_func);
try
{
do_something_in_current_thread();
}
catch(...)
{
t.join();
throw;
}
t.join();
}
该段代码在try/catch内外两次join()
,十分冗余。RAII提供了更清晰简明的方法:
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 i = 0;
func my_func(i);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
当执行到f
的末尾,thread_guardg
会被销毁,其析构函数会等待t
执行完成。thread_guard
的拷贝和赋值构造均被禁用,为了防止出现拷贝或赋值该对象这种危险的动作。如果无需等待,可以直接detach
该线程。
后台运行线程
std:thread::detach()
将线程与当前线程分离,没有直接的方法与其通信,其所有权和控制权交给C++ Runtime。UNIX中,分离的进程被称作守护线程(daemon thread),通常它们是长时间运行的,执行一些后台任务。《C++并发编程实战》给了一个示例,比较简单不再讲解。
最后一次更新于2022-05-21
0 条评论