共计 4980 个字符,预计需要花费 13 分钟才能阅读完成。
获取线程 id
- std::this_thread::get_id()
#include <iostream>
#include <thread>
using namespace std;
std::thread::id main_thread_id = std::this_thread::get_id(); // 留神这里!!!void is_main_thread()
{cout << std::this_thread::get_id() << endl; // 留神这里!!if (main_thread_id == std::this_thread::get_id()) // 留神这里!!cout << "This is the main thread" << endl;
else
cout << "This is not main thread" << endl;
}
int main()
{is_main_thread();
thread th(is_main_thread);
th.join();
return 0;
}
输入:
1
This is the main thread
2
This is not main thread
线程调用对象的参数传递
援用作为参数
援用作为参数时,产生参数拷贝而不是援用传递;
同时应用 std::ref() 与 thread::detach()
时,须要思考主线程中的部分属性资源(对象)是否被子线程应用,并且主线程是否先于子线程完结。
应用 thread::detach() 主线程与子线程拆散独立运行,应用 std::ref() 子线程真正援用所传递实参。当实参在主线程中领有部分属性,并且主线程先于子线程完结,那么主线程实参资源开释,子线程的形参援用便指向未定义的资源,到这里祝贺你,驶入了通向未定义的快车道。
C++ 自身有援用(&),为什么又引入 std::ref 呢?
- 次要是思考函数式编程(如 std::thread, std::bind)在应用时,是对 参数间接拷贝,而不是援用;
- std::thread, std::bind 一个函数模板,它的原理是依据已有的模板生成一个函数,然而因为 std::thread, std::bind 不晓得生成的函数执行的时候,传递进来的参数是否还无效。所以它抉择参数传递而不是援用传递。如果援用传递,std::ref 和 std::cref 就排上用场了。
#include <iostream>
#include <thread>
using namespace std;
void thread_func(const int &i)
{
cout << "thread_func begin" << endl;
cout << "i =" << i << endl;
const_cast<int &>(i) = i * 2;
cout << "i =" << i << endl;
for (int i=0; i<1000; ++i)
{ }
// 操作 obj.i ...
cout << "thread_func end" << endl;
}
int main()
{
cout << "main begin" << endl;
int i1 = 10;
cout << i1 << endl;
thread th1(thread_func, i1); // 留神这里 i1!!!th1.join();
// th1.detach(); // 实参 i1 被拷贝,子线程可失常运行
cout << i1 << endl;
cout << "=================" << endl;
int i2 = 10;
cout << i2 << endl;
thread th2(thread_func, std::ref(i2)); // 留神这里 std::ref(i2)!!!th2.join();
// th2.detach(); // 实参被援用,主线程完结时部分 i2 开释,子线程援用开释资源,行为未定义!cout << i2 << endl;
cout << "main end" << endl;
return 0;
}
输入:
main begin
10
thread_func begin
i = 10
i = 20
thread_func end
10 // 留神这里输入!!!=================
10
thread_func begin
i = 10
i = 20
thread_func end
20 // 留神这里输入!!!main end
补充证实:留神类内拷贝构造函数打印
#include <iostream>
#include <thread>
using namespace std;
class Base {
public:
Base(int i = 0) : m_i(i)
{cout << "Base(int i)" << m_i << endl;
}
Base(const Base &obj) : m_i(obj.m_i)
{cout << "Base(const Base &obj)" << m_i << endl;
}
~Base()
{cout << "~Base()" << m_i << endl;
}
public:
int m_i;
};
void thread_func(const Base &obj)
{
cout << "thread_func begin" << endl;
cout << obj.m_i << endl;
for (int i=0; i<1000; ++i)
{ }
// 操作 obj.m_i ...
cout << "thread_func end" << endl;
}
int main()
{
cout << "main begin" << endl;
Base obj1{1};
Base obj2{2};
cout << "=================" << endl;
thread th1(thread_func, obj1); // 留神这里 obj1 !!!
th1.join();
// th1.detach(); // 实参 obj1 被拷贝,子线程可失常运行
cout << "=================" << endl;
thread th2(thread_func, std::ref(obj2)); // 留神这里 std::ref(obj2) !!!
th2.join();
// th2.detach(); // 实参被援用,主线程完结时部分 obj2 开释,子线程援用开释资源,行为未定义!cout << "=================" << endl;
cout << "main end" << endl;
return 0;
}
输入:
main begin
Base(int i) 1
Base(int i) 2
================= // 留神这里,拷贝构造函数被调用!!!Base(const Base &obj) 1 //(调用两次起因可见上一章)Base(const Base &obj) 1
~Base() 1
thread_func begin
1
thread_func end
~Base() 1
================= // 留神这里,拷贝构造函数未被调用!!!thread_func begin
2
thread_func end
=================
main end
~Base() 2
~Base() 1
指针作为参数
指针作为形参时,形参加实参指向同一地址;
同时应用 指针 与 thread::detach()
时,须要思考主线程中的部分属性资源(对象)是否被子线程应用,并且主线程是否先于子线程完结。
应用 thread::detach() 主线程与子线程拆散独立运行,指针作为形参时,形参加实参指向同一地址。当实参在主线程中领有部分属性,并且主线程先于子线程完结,那么主线程实参资源开释,子线程的形参援用便指向未定义的资源,到这里祝贺你,驶入了通向未定义的快车道。
#include <iostream>
#include <thread>
using namespace std;
void thread_func(const char *pt)
{cout << "thread_func begin" << pt[0] << endl;
cout << "pt addr :" << static_cast<const void *>(pt) << endl;
for (int i=0; i<1000; ++i)
{ }
// 操作 pt ...
cout << "thread_func end" << endl;
}
int main()
{
cout << "main begin" << endl;
const char *pt1 = "1"; // 部分资源
const char *pt2 = new char('2'); // 主线程治理的资源
thread th1(thread_func, pt1);
th1.join();
// th1.detach(); // 实参、形参指向同一资源,未定义快车道
thread th2(thread_func, pt2);
th2.join();
// th2.detach(); // 实参、形参指向同一资源,未定义快车道
cout << "pt1 addr :" << static_cast<const void *>(pt1) << endl;
cout << "pt2 addr :" << static_cast<const void *>(pt2) << endl;
delete pt2;
cout << "main end" << endl;
return 0;
}
输入:【实参、形参指向同一地址】
main begin
thread_func begin 1
pt addr : 0x405043 // 留神这里 A
thread_func end
thread_func begin 2
pt addr : 0xf617f0 // 留神这里 B
thread_func end
pt1 addr : 0x405043 // 留神这里 A'pt2 addr : 0xf617f0 // 留神这里 B'
main end
产生隐式转换的对象作为参数
当应用可能产生隐式转换的对象作为参数时,应防止产生隐式类型转换,而是是在调用线程被动生成长期对象
当应用可能产生隐式转换的对象作为参数时,将在子线程执行流应用主线程中实参结构对象。当实参在主线程中领有部分属性,并且主线程先于子线程完结,那么主线程实参资源开释,此时隐式转换结构初值未定义,到这里祝贺你,驶入了通向未定义的快车道。
#include <iostream>
#include <thread>
using namespace std;
class Base {
public:
Base(int i = 0) : m_i(i)
{cout << "Base(int i)" << std::this_thread::get_id() << endl;}
Base(const Base &obj) : m_i(obj.m_i)
{cout << "Base(const Base &obj)" << std::this_thread::get_id() << endl;}
~Base()
{cout << "~Base()" << std::this_thread::get_id() << endl;}
public:
int m_i;
};
void thread_func(const Base &)
{ }
int main()
{
cout << "main begin" << endl;
cout << "main thread id" << std::this_thread::get_id() << endl;
int i = 10;
thread th1(thread_func, i);
th1.join();
// th2.detach(); // 隐式转换产生在子线程中,此时无奈保障主线程未执行完结,即无奈保障主线程初始资源无效!!!cout << "=================" << endl;
thread th2(thread_func, Base(i)); // 留神这里,Base(i) 被动生成长期对象!!!th2.join();
cout << "main end" << endl;
return 0;
}
输入:
main begin
main thread id 1
Base(int i) 2 // 留神这里,子线程中应用主线程局部变量产生隐式转换!!!~Base() 2
=================
Base(int i) 1
Base(const Base &obj) 1 // 留神这里,隐式转换产生在主线程(调用线程)中,初始值此时无效!!!Base(const Base &obj) 1
~Base() 1
~Base() 1
~Base() 3
main end
总结
- 如果根底数据类型作为参数,举荐应用值传递而不是援用。
- 如果自定义数据类型作为参数,应防止产生隐式类型转换。做法是在调用线程被动生成长期对象,而后在子线程应用援用接管。
- 尽量应用 join 而不是 detach(越简略越牢靠)。
正文完