1.1 第一个线程代码示例-线程创建示例
多线程编程当中,每一个程序运行都至少会有一个线程,一般的main函数都作为主线程的入口,这里面是一个进程包含一个主线程,一个进程里面包含多个子线程,所以一般在主线程当中(也就是main函数中)再去启动一个子线程。
需要包含头文件:#include <thread>
用到的类:thread th(ThreadMain); 线程创建并启动
阻塞等待函数:th.join();当子线程退出之后才解除阻塞
如果不阻塞等待子线程退出的话,也就是把th.join();去掉,那么主线程运行完,就把th对象给销毁了,而这时子线程还在运行,这时就会弹窗报错。
问题线程的话,是创建好,运行完回调函数就自动退出么?而主函数会把在内部创建的线程对象同时给销毁,也就是说应该先退出子线程再销毁线程对象。
#include <thread>
#include <iostream>
//Linux 中要链接动态库 -lpthread
using namespace std;
void ThreadMain()
{
cout << "begin sub thread main " << this_thread::get_id() << endl; // get_id()获取当前线程ID
for (int i = 0; i < 10; i++)
{
cout << "in thread " << i << endl;
this_thread::sleep_for(chrono::seconds(1));//1000ms
}
cout << "end sub thread main " << this_thread::get_id() << endl;
}
int main(int argc, char* argv[])
{
cout << "main thread ID " << this_thread::get_id() << endl;
//线程创建并启动
thread th(ThreadMain);
cout << "begin wait sub thread " << endl;
//阻塞等待子线程退出
th.join();
cout << "end wait sub thread " << endl;
return 0;
}
#编译
g++ -o main main3.c -lpthread
结果:
线程的退出:我们这里创建了一个子线程,假如我们让他运行10s后再退出,这里就涉及到一个问题,假如里面是一个死循环,那么就会把单个CPU的资源耗尽,而有些任务处理的时候并不是计算任务,而只是在等待某一个结果,那么这个时候就可以在子线程中选择释放CPU资源,这里使用其中的一种方式,就是sleep,sleep就是当前线程释放CPU多长时间。
上面第8行和第21行,这两个打印输出可能是同时执行的,因为是多核并发,因此最后反应在屏幕上可能导致两行字叠在一块儿了。
1.2 thread对象生命周期和线程等待和分离
这里面导致错误的原因有多个,第一个首先是主线程退出了,那么我们先要保证主线程不要退出
但是结果还是跟上面一样。
我们希望主线程和子线程同时运行,然后我们又不想维护th这个对象(当然还有一种方式就是维护这个对象)那怎么办?
#include <thread>
#include <iostream>
//Linux -lpthread
using namespace std;
bool is_exit = false;//通过这个标志位通知子线程退出
void ThreadMain()
{
cout << "begin sub thread main " << this_thread::get_id() << endl;
for (int i = 0; i < 10; i++)
{
if (is_exit) break;
cout << "in thread " << i << endl;
this_thread::sleep_for(chrono::seconds(1));//1000ms
}
cout << "end sub thread main " << this_thread::get_id() << endl;
}
int main()
{
{
//thread th(ThreadMain);
//th.detach();
//主子线程分离,子线程就变成了在后台运行的线程,与主线程无关
//坑:主线程退出后 子线程不一定退出,那造成一个什么现象,主线程退出之后,主线程的全局空间,
//栈空间等全部都释放掉了,一旦子线程访问了这些空间,那么程序就会崩溃掉,在Windows中,写完程序
//关闭的时候弹出一个错误窗口,那么多半是你还在运行的线程访问了静态成员变量或者全局变量,因为
//变量都被销毁掉了,你还在访问它,那么就会出现错误。
}
{
thread th(ThreadMain);
this_thread::sleep_for(chrono::seconds(1));//1000ms
is_exit = true; //通知子线程退出
cout << "主线程阻塞,等待子线程退出" << endl;
th.join(); //主线程阻塞,等待子线程退出
cout << "子线程已经退出!" << endl;
}
//getchar();
return 0;
}
那么如何避免这样的情况呢?如果你用detach()函数分离了一个子线程,那么最后程序却崩溃了,那你的子线程就不要访问外部的变量了,就只访问线程函数内部的变量就行了,或者就是在主线程退出的时候通知一下我们,然后我们去退出,但是这种方式在实际问题中问题也比较多,所以大部分情况下不做detach了,而你又必须维系th这样的一个对象,不然你把对象删了,而子线程还在跑就会报错,那么我们就在整个程序析构的时候调用join函数就行了。
1.3 全局函数作为线程入口分析参数传递内存
#include <thread>
#include <iostream>
#include <string>
//Linux -lpthread
using namespace std;
class Para{
public:
Para() { cout << "Create Para" << endl; }
Para(const Para& p)
{
cout << "Copy Para" << endl;
this->name = p.name;
}
~Para() { cout << "Drop Para" << endl; }
string name;
};
void ThreadMain(int p1, float p2, string str, Para p4){
//延时100ms,确保f1被释放掉,因为此时主线程已经走到th.join();了
//f1传过来后是拷贝,因此外部f1的销毁这里不受影响
this_thread::sleep_for(100ms);
cout << "ThreadMain " << p1 << " " << p2 << " " << str << " " << p4.name << endl;
}
int main(int argc, char* argv[]){
thread th;
//在栈空间中,一对大括号之后就会释放
{
//当这个f1释放之后,当调用thread创建线程这一行代码的时候,后面的参数都做了一份拷贝
float f1 = 12.1f;
Para p;
p.name = "test Para class";
//所有的参数做复制
th = thread(ThreadMain, 101, f1, "test string para", p);
}
th.join();
return 0;
}
#编译
g++ -o main main3.c -lpthread
这是Linux下的输出
这是Windows下VSstudio的输出,有点搞不明白两次结果为什么不一样,对Windows的结果解释:大括号中间创建一次,传递到thread函数拷贝构造一次,再次调用回调函数时候再次拷贝构造一次。所以一共创建了三次。所以使用值传递的方式会有很大的拷贝开销,应该多多考虑使用引用传递和指针传递。