写在前面,本文是本人学习过程中类似于笔记的记录(可供初学者作为基础教程),如有疑问或错误可在评论区指出。
请勿转载,谢谢观看。
C++11多线程(一) 创建线程
创建线程
线程概念就不多做讲述,直接进入正题:
利用thread创建线程
#include<thread>
void f(){...}//省略执行步骤;
thread t1(f);
这就是最简单的创建方法——利用函数进行创建。
thread一旦被创建出来就会开始运行,它的执行动作就是我们在f()中所编写的代码。
具体代码如下:
#include<iostream>
#include<thread>
using namespace std;
void f() {//
cout << "正在执行 id:" << this_thread::get_id() << endl;//this_thread是一个命名空间,其中的get_id()获得当前线程的id
}
int main() {//主线程也是一个线程也可以用get_id()来获得它的id
thread t1(f);
//如果创建线程的函数需要参数的话,直接在f后传入即可。例如:
//void f(int a,int b);
//thread t(f,1,2);
thread t2(f);
//创建一个函数就可以用来创建线程,也可以使用仿函数(重载了operator()的类)、对象方法、匿名函数来进行创建线程。
//线程在创建时就开始执行了
t1.join();
cout << "helloworld"<<endl;
t2.join();
//join()方法指的是阻塞主线程来等待调用join()的线程。
//join中文意思是加入,可理解为:等待t2.join()然后执行下一步。
}
此外,线程还有.detach()的方法,detach()会使线程脱离主线程进入后台执行,有可能出现主线程运行完但子线程还在运行的情况,因此不建议使用。
thread::detach(),thread::join()
正如上文所说,thread对象一旦被创建便会开始执行传给他的函数。
但有些时候我们会不得不需要等待某个线程运行给出结果给另一个线程,我们就可以使用join().
仔细回想刚刚的范例代码注释,join()会让调用它(join())的线程阻塞直到线程执行完毕。
就比如,我们在主线程中创建了线程t1,然后调用了t1.join(),那就代表在t1线程执行完毕之前,主线程都会一直阻塞等待。等到t1执行完毕才会进行下一步动作。
阻塞不理解可以想象成暂停
再来看看detach()函数,detach函数会将线程分离出去不受主线程的管理。
上述代码中如果我们使用detach()的话就会报错,想想原因。
/\/\/\/\/\/\/\/\/\//\/\/\/\/\/\/\/\/\//\/\/\/\/\/\/\/\/\//\/\/\/\/\/\/\/\/\//\/\/\/\/\/\/\/\/\//\/\/\/\/\/\/\/\/\/
因为我们在主线程中调用t1.detach()后,主线程就不再管t1了,但又因为我们主线程较短,所以就会出现主线程结束,t1还在运行,这样就会爆出异常。(你可以把主线程理解为进程,进程包含线程,进程都结束了,子线程t1还在运行,那肯定是错的)。
你可以通过使用 将main函数中的创建线程部分设为死循环,或者加长main函数的执行时间等办法来观察detach的运行状况。
//死循环
while(1){
thread t1(f);
thread t2(f);
t1.detach();
t2.detach();
}
不安全的线程
上述代码的理想执行状况如下:
但那只是理想中;
我们还可能会出现其他意想不到的情况:
正如上图所示:为什么两个正在执行id会出现在同一行?
又比如下面这个情况
思考一下为什么会出现上述情况
因为两个函数线程都在执行,而输出屏幕却只有一个,因此信息就会发生混乱。这样的线程是不具备安全性的,要想使其具有安全性,我们就可以使用锁来进行编程。
多运行几次你可能会发现:helloworld都是最后才输出,但在代码中明明是t2.join()在最后。
再次思考一下为什么?
因为正如注释所写,线程在创建的时候就开始运行,t2在后台的运行是比cout<<helloworld快的。因此t1、t2的输出总是更快进入缓冲区(有关概念可自行百度)。
因此helloworld总是最后输出。看下面这段代码:
#include<iostream>
#include<thread>
using namespace std;
void f() {//
cout << "正在执行 id:" << this_thread::get_id() << endl;
}
void f2() {//
for (int i = 0; i < 2000000; i++);//利用一个for循环拖延运行时间
//std::this_thread::sleep_for(std::chrono::milliseconds(100));//等待100ms
//也可以利用上述代码,具体不深入解释
cout << "正在执行 id:" << this_thread::get_id() << endl;
}
int main() {//主线程也是一个线程也可以用get_id()来获得它的id
thread t1(f);
thread t2(f2);
t1.join();
cout << "helloworld" << endl;
t2.join();
}
创建线程的其他方法
一、函数对象
#include<iostream>
#include<thread>
#include<string>
using namespace std;
class Test
{
public:
Test() = default;
~Test() = default;
void operator()()//重载了()运算符
{
cout << "正在执行 id:" << this_thread::get_id() << endl;
}
private:
};
int main() {
Test a, b;//创建两个对象
cout << "HelloWorld" << endl;//注意这段代码和上面的区别
thread t1(a);
thread t2(b);
t1.join();
t2.join();
}
二、匿名函数
#include<iostream>
#include<thread>
using namespace std;
int main() {//主线程也是一个线程也可以用get_id()来获得它的id
thread t1([]() {
cout << "正在执行 id:" << this_thread::get_id() << endl;
});
cout << "匿名函数" << endl;
t1.join();
}
三、成员变量
#include<iostream>
#include<thread>
using namespace std;
class Test
{
public:
Test();
~Test();
void f() {
i++;
cout << i << "正在执行 id:" << this_thread::get_id() << endl;
};
private:
int i;
};
Test::Test()
{
i = 0;
}
Test::~Test()
{
}
int main() {
Test T;
thread t1(&Test::f, &T);//注意参数列表的不同。
cout << endl;
thread t2(&Test::f, &T);//第二个变量如果不带&的话,会调用拷贝构造函数创建一个副本进行i++的操作,如果想对一个对象进行操作的话需要带上&,实际情况中还是建议使用&以免发生意想不到的情况;
t1.join();
t2.join();
}
四、补充
thread 的一些其他:
int hardware_concurrency()
获取cpu最大线程数
bool joinable()
返回是否可以join()
一个线程join()和detach()只能一次,不要忘记调用了,推荐使用join().
本文仅用于本人学习过程中类似于笔记的记录,如有疑问和错误请在评论区指出。
请勿转载,谢谢您的观看。