一、线程的基本概念
- 每个进程(执行起来的可执行程序)都有一个主线程,主线程随进程默默启动
- 线程都要有函数入口,main函数即为主线程的入口
- 线程不是越多越好,每个线程需要一个独立的堆栈空间(大约1M),线程切换要保存很多中间状态,耗费时间
- 线程是轻量级的进程,一个进程中的线程共享地址空间,全局变量、全局内存、全局引用都可以在线程之间传递,所以多线程的开销远远小于多进程
二、线程的创建和启动
1、范例演示线程运行的开始
线程包含在头文件< thread >中,线程必须指定执行的入口函数,
void myPrint()
{
// this_thread::get_id()可以获取当前执行线程的ID(独一无二)
cout << "thread id " << this_thread::get_id() << " is starting..." << endl;
//-------------
cout << "thread is finished" << endl;
return;
}
int main()
{
//创建子线程1,指定线程执行入口是myPrint;(2)执行线程
thread myThread1(myPrint);
//阻塞主线程,当子线程1执行完毕再开始执行
myThread1.join();
//子线程1结束后创建子线程2,指定线程执行入口是myPrint;(2)执行线程
thread myThread2(myPrint);
//detach后,子线程和主线程失去关联,独立运行,
//如果主线程先结束,则子线程驻留在后台,由C++运行时库接管
//所以子线程2的打印不一定可以出现在终端上,取决于它和主线程执行的快慢
myThread2.detach();
cout << "Hello World!" << endl;
return 0;
}
2、用对象创建线程
class Stu
{
public:
int age;
Stu(int i) : age(i)
{
cout << "Stu的构造函数被执行" << endl;
}
Stu(const Stu &stu) : age(stu.age)
{
cout << "Stu的拷贝函数被执行" << endl;
}
~Stu()
{
cout << "Stu的析构函数被执行" << endl;
}
void operator()()
{
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
}
};
int main()
{
// 用对象创建线程
Stu student1(18); // 执行构造函数
thread th1(student1); // 执行拷贝函数,将对象student1拷贝一份到子线程中,
//所以即便使用detach时,主线程先结束,student1的拷贝对象也还是存在
th1.join(); //当线程th1结束后,执行被拷贝对象的析构函数
cout << "I love China1" << endl;
return 0; //当主线程结束后,执行student1对象的析构函数
}
3、线程传参详解
线程可以共享进程的内存空间,线程拥有自己独立内存。
主线程的值,被拷贝一份到子线程中,即便是引用时,传入的也是值的副本,也就是说子线程中的修改影响不了主线程中的值。
注意:1、指针传参时,很多资料显示,子线程和主线程的参数地址相同,但是,自测是不同的地址!
2、引用传参时,也是拷贝,参数地址在子线程和主线程中不一致
void test(int ti, char* tj, const int &tk) //在引用前必须加const,否则会出错,可能是因为临时对象具有常性
{
cout << "子线程开始" << endl;
//ti的内存地址0x292fcf0{4},tj的内存地址0x292fcf8 {"this is a test"}
//tk的内存地址0xf86588 {5}
cout << &ti << " " << &tj << " " << &tk << endl;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
//i的内存地址0x001efdfc {4},j的内存地址0x001efdf0 {"this is a test"},
//k的内存地址0x001efef4 {5}
int i = 4;
char j[] = "this is a test";
int k = 5;
thread t(test, i, j, k); // 线程传参格式
t.join();
cout << "主线程结束!" << endl;
return 0;
}
如果需要将在子线程中的修改能够反映到主线程中,需要使用std::ref(),此时用于接收ref()的那个参数前不能加const。
class A {
public:
int ai;
A(int i) : ai(i) {
}
};
//接收ref()的那个参数前不能加const,因为我们会改变那个值
void test(int& ti, const A& t)
{
cout << "子线程开始" << endl;
cout << ti << " " << t.ai << endl; // 4 5
ti++;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
int i