目录
哈喽
多线程是什么
了解进程和线程
多线程头文件的用途
构造函数(constructor)用法
析构函数(destructor)
get_id用法:获取线程id
join的用法
detach的用法
joinable的用法
试一下
最后
hello!
我是YR,
今天,我来总结一下C++中多线程的用法。
C++里的多线程还是非常重要的。
如果要认识线程,首先要认识进程。
进程是什么?
指系统中能独立执行的程序称之为进程。是操作系统资源分配的基本单位。双cpu才能实现多进程,每个cpu执行一个进程。单cpu只是不断的切换执行程序,所以看起来像是同时执行多个进程,当实际上切换中有较大开销。
线程是什么?线程可以看做是轻量级的进程,是任务调度和执行的基本单位。一个进程中至少包括一个线程或者多个线程。一个线程就是一个程序中的一条执行线索,如果要一进程中实现多段代码同时交替运行,就需要产生多个线程,并指定每个线程上面需要运行的代码段,这就是多线程。
举个例子,你需要写程序和吃饭。如果你选择先写程序再吃饭或者先吃饭再写程序,这就是单线程。
如果你比较聪明,选择了一边写程序一边吃饭,这就是多线程。
C++11 新标准中引入了5个头文件来支持多线程编程,
它们分别是
:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
#include
using namespace std;
class Complex{
double re;
double im;
public:
Complex(const double r, const double i){ //带参数的构造函数
cout << "调用构造函数:\n";
re = r;
im = i;
}
string text(){
stringstream ss;
ss << re << " + " << im << "j";
return ss.str();
}
};
int main(){
Complex a = {1.0, 2.0}; //此处调用构造函数Complex
cout << "a = " << a.text();
Complex b = {4.0, 5.0}; //再次调用构造函数Complex
cout << "b = " << b.text();
}
程序输出:
上述函数中,并没有调用Complex但复数a和b的实部和虚部却被自动分配好了,这就是构造函数,在类被实例化(即分配对象时)被自动调用。如果一个类中没有构造函数,则编译系统会默认补上一个空的构造函数。但是如果自行在类中添加了构造函数,系统将不会自动再补一个构造函数。因此,如果有构造函数存在,那么对类实例化时必须与构造函数的格式相同。例如:
#include
using namespace std;
class Complex{
double re;
double im;
public:
Complex(const double r, const double i){
cout << "调用构造函数\n";
re = r;
im = i;
}
double getReal() const{
return re;
}
double getImag() const{
return im;
}
Complex mul(const Complex& z){
Complex s; //此处将产生编译错误,因为这里实例化类Complex时和构造函数的格式不一样
Complex s = {0.0, 0.0}; //这种格式正确
s.re = re * z.getReal() - im * z.getImag();
s.im = re * z.getImag() + im * z.getReal();
}
int main(){
}
一个类中可以拥有多个构造函数。例如下面的程序,用以求一个复数的模以及与另一个复数相乘的结果:
#include
#include
#include
#include
using namespace std;
class Complex{
double re;
double im;
public:
Complex(){
cout << "调用默认构造函数\n";
}
Complex(const double r, const double i){ //带参数的构造函数
cout << "调用构造函数:\n";
re = r;
im = i;
}
void setValue(const double r, const double i){
re = r;
im = i;
}
double getReal()const{
return re;
}
double getImag()const{
return im;
}
double amount(){ //计算模
return sqrt(re * re + im * im);
}
Complex mul(const Complex& z){
Complex s; //这里将调用默认构造函数
s.setValue((re * z.getReal() - im * z.getImag()), (re * z.getImag() + im * z.getReal()));
return s;
}
string text(){
stringstream ss;
ss << re << " + " << im << "j" << endl;
return ss.str();
}
};
int main(){
Complex a = {1.0, 2.0}; //此处调用构造函数Complex
cout << "a = " << a.text();
Complex b = {4.0, 5.0}; //再次调用构造函数Complex
cout << "b = " << b.text();
Complex c; //这里将调用默认构造函数
c = a.mul(b);
cout << "c = " << c.text();
}
和构造函数相似,析构函数也是一个特殊的类方法,它是用来自动释放对象的。析构函数的表示形式为:在类名前面加上“~”即表示析构函数。与构造函数不同的是,一个类中只允许一个析构函数存在。
如果类中无析构函数,则编译系统会默认补上一个空的析构函数。当程序结束时,会自动调用析构函数。例如下面的程序:
#include
using namespace std;
class A {
public:
A() {
cout << "调用构造函数\n";
}
~A() { //析构函数
cout << "调用析构函数\n";
}
};
int main() {
A a; //实例化类时自动调用构造函数
cout << "对象a被实例化\n";
} //程序结束后自动调用析构函数
程序结果:
如果该类是通过new申请的新的动态空间,则在使用delete时会自动调用析构函数,同样地在使用new时也会自动调用构造函数,而对于另一个申请动态空间malloc和free则不会自动对对象实例化,这也就是为什么C++更偏向于使用new和delete来申请动态空间的原因。
可以使用 .get_id() 的方法来获取线程的id。
比如这个程序
#include
#include
using namespace std;
void show(int n){
cout<<"show1:get_id() is "<
程序结果:
举个例子,hello world的多线程版是这样的
这个创建的方式就是以函数作为一个入口,创建了一个子线程,那么创建的语句就是第11行代码所示,所传入的参数就是入口的函数名。在创建了这个子线程之后,这个子线程就开始运行了,同时主线程也不停的往下运行,当碰到t.join()这句代码的时候,就表示主线程需要等待子线程运行结束回收掉子线程的资源后,再往下运行,否则就会产生一种情况:当子线程还没有运行完主线程先运行完了,那么就会结束这个进程,从而中断了子线程的运行。因此join()函数的作用就是使主线程在此阻塞,等待子线程运行结束并回收其资源,再往下运行。
当然我们可以用this_thread::get_id()这个函数来验证这个子线程和主线程是不同的两个线程,结果如下图所示
看这张图
从这个图中我们可以发现fun和main是交叉着输出的,并不是先输出fun中的内容,那么detach的作用就是将主线程与子线程分离,主线程将不再等待子线程的运行,也就是说两个线程同时运行,当主线程结束的时候,进程结束。
那么可能就会产生一些疑问,那这样不就中断了子线程的运行吗?
其实不是,在detach的时候,这个子线程将脱离主线程的控制,子线程独立分离出去并在后台运行,相当于linux中的守护进程,那么该子线程会由运行时库托管。当主线程结束的时候,进程也就结束,那么该进程的所有线程也会结束,被分离出去的子线程会由运行时库回收其资源。
joinable()函数是一个布尔类型的函数,他会返回一个布尔值来表示当前的线程是否是可执行线程(能被join或者detach),因为相同的线程不能join两次,也不能join完再detach,同理也不能detach,所以joinable函数就是用来判断当前这个线程是否可以joinable的。通常不能被joinable有以下几种情况:
1)由thread的缺省构造函数而造成的(thread()没有参数)。
2)该thread被move过(包括move构造和move赋值)。
3)该线程被join或者detach过。
以前我都会把最后demo的代码直接放出了,但是因为这个我懒得敲代码,所以这个任务你们可以试试
效果:
这里我是分了两个程序
一个程序负责刷新密码,然后把密码存入password.txt文件里
另一个程序负责读取密码,如果password.txt文件存在,将输入的密码和文件里的密码比较。
你们可以尝试用多线程的方法把两个程序合体
代码:
password.cpp
#include
#include
#include
#include
using namespace std;
int rd(int a,int b){
srand((unsigned)time(NULL));
return (rand()%(b-a+1)+a);
}
int main()
{
system("color F0");
srand((unsigned)time(NULL));
int rand_num=rd(100000,999999);
bool flag=false;
int i=1;
int password=0;
while(true)
{
cout<<"您的密码是:"<=10)
{
Sleep(500);
i=1;
rand_num=rd(100000,999999);
system("cls");
break;
}
}
}
return 0;
}
a.cpp
#include
#include
#include
using namespace std;
int main()
{
system("color F0");
ifstream in("password.txt",ios::in);
if (!in)
{
cout<<"请先运行 password.cpp 文件!";
return 0;
}
else
{
cout<<"请输入你的密码:";
int password=0;
int right_password=0;
in>>right_password;
in.close();
cin>>password;
if(password==right_password)
{
cout<
好了,关于C++多线程的知识就到这里了,
路过的大佬可以顺手来个三连,作者会回访的
这次的博客写到了6000多字,不知道能不能上热榜呢?
拜拜