一、依赖隐式转换的危险
void f(int i, std::string const& s);
void oops( int some_param )
{
char buffer[1024];
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, buffer);
t.detach();
}
在 std::thread t
的构造函数中,想要依赖隐式转换,将字面值buffer
转换为期待的 string 对象。但是 std::thread t
的构造函数只会复制buffer
。
怎么解决?
在buffer
传递到 std::thread t
的构造函数之前,就将它显示转换为 std::string 对象
void f(int i, std::string const& s);
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, std::string(buffer));//显示转换
t.detach();
}
注意:
-
向线程传递一个引用的变量时,在一些情况下会出现错误
class func{
public:
string &a;
explicit func(string &a_) : a(a_) {}
};
void fu(string str, func &b)
{
b.a = str;
}
int main()
{
string st("hhh");
string str("嘿嘿");
func my_func(st);
std::thread my_thread(fu, str, my_func);//报错
my_thread.join();
cout << st << endl;
return 0;
}
my_thread
构造函数无视函数期待的参数类型,并盲目的拷贝my_func
。当线程调用fu
函数时,传递给函数的参数是my_func
内部拷贝的引用,而非它本身的引用。
怎么解决?
使用 std::ref
将参数my_func
转换成引用的形式:
std::thread my_thread(fu, str, std::ref(my_func));
当然,也可以使用bind的方式来解决:
class func1{
public:
int &a;
explicit func1(int &a_) : a(a_) {}
int operator()(int c)
{
for(;a < c; ++a)
{
cout << a << endl;
}
cout << a << endl;
return a;
}
};
int main()
{
int st = 6;
int c = 10;
func1 f(st);
/*
* f::operator ()(int)实际上是一个operator()(func1 * this, int)
* 因为类的成员函数,并没有在类内占空间,而是利用签名在类外进行查找
* 传一个对象引用进去,让函数是针对的是哪个实例
* */
std::thread my_thread (&func1::operator(), &f, c);
my_thread.join();
return 0;
}
二、一个有趣的情况:提供的参数可以移动,但不能拷贝
"移动"是指:原始对象中的数据转移给另一对象,而转移的这些数据就不再在原始对象中保存了。
比如:std::unique_ptr
移动构造函数(move constructor)和移动赋值操作符(move assignment operator)允许一个对象在多个 std::unique_ptr 实现中传递。在使用"移动"转移原对象后,就会留下一个空指针(NULL)。
当对象是一个临时变量时,自动进行移动操作,例如:string str = string("hhh");
。但当对象是一个命名变量,那么转移的时候就需要使用 std::move() 进行显示移动:
void process_big_object( std::unique_ptr<big_object> );
void main()
{
std::unique_ptr<big_object> p_ob(new big_object);
p_ob->prepare_data(42);
std::thread t( process_big_object, std::move(p) ); //①
t.join();
}
①运行过程:
对象p_ob
的所有权就被首先转移到新创建线程的的内部存储中,之后传递给process_big_object
函数。
thread 的特性:
std::thread 实例不像 std::unique_ptr 那样能占有一个动态对象的所有权,但是它能占有其他资源:每个实例都负责管理一个执行线程。执行线程的所有权可以在多个 std::thread 实例中互相转移,这是依赖于 std::thread 实例的可移动且不可复制性。
不可复制性保证了在同一时间点,一个 std::thread 实例只能关联一个执行线程。
可移动性使得程序员可以自己决定,哪个实例拥有实际执行线程的所有权。
三、转移线程所有权的基础用法
void some_function();
void some_other_function();
std::thread t01(some_function);
std::thread t02 = std::move(t01);//t01的所有权就转移给t02
/*
* 与一个临时 std::thread 对象启动了相关联的线程
* 由于std::thread(some_other_function)是一个临时对象,所以会隐式的调用移动操作
* */
t01 = std::thread(some_other_function);
std::thread t03 = std::move(t02);
t01 = std::move(t03); // ①赋值操作将使程序崩溃
最后一个移动操作使程序崩溃的原因是:
在 t01 将some_function线程
的所有权就转移给t02后,t01 又启动了some_other_function线程
。当 t03( some_other_function线程
) 赋给了t01时,由于t01已经有了一个关联的线程,因此系统直接调std::terminate() 终止程序继续运行。这样做是为了保证与 std::thread 的析构函数的行为一致。
- ( std::terminate() 是noexcept函数,不抛出异常。)
- “不抛出异常,与 std::thread 的析构函数的行为一致”
解析:
线程对象被析构前,需要显式的等待线程完成,分离它;
进行赋值时,赋一个新值给 std::thread 对象来"丢弃"一个线程。用这种方式来触发它的析构函数,这是绝对不行的。
四、转移线程所有权的两种想法
1,使用函数来返回新线程的所有权
std::thread f()
{
void some_function();
return std::thread(some_function);
}
std::thread g(int a)
{
void some_other_function(int);
std::thread t(some_other_function,a);
return t;
}
2,在函数中转移线程所有权
void func(std::thread t);
void gunc()
{
void some_function();
func( std::thread(some_function) );
std::thread t(some_function);
func( std::move(t) );
}
五、迈出线程自动化管理的第一步
为了确保线程程序在主线程退出前完成:
class Threa
{
std::thread t;
public:
explicit Threa(std::thread t_):
t( std::move(t_) )
{
if(!t.joinable())//检测是否为可执行线程,当线程不可加入时,抛出异常
throw std::logic_error(“No thread”);
}
~Threa()
{
t.join();
}
Threa( Threa const& )=delete;
Threa& operator= (Threa const&)=delete;
};
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
std::cout << i << std::endl;
}
};
void f()
{
int some_local_state;
th t(std::thread(func(some_local_state)));
do_something_in_current_thread();
}
当主线程到达f()函数的末尾时,scoped_thread对象开始析构,并阻塞至线程结束。
使用vector实现初步的线程自动化管理
void do_work(unsigned id);
void f()
{
std::vector<std::thread> threads;
for(unsigned i=0; i < 20; ++i)
{
threads.push_back(std::thread(do_work,i)); // 产生线程
}
std::for_each(threads.begin(), threads.end(),
std::mem_fn(&std::thread::join));
//mem_fn把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
}
网友评论