C++多线程6000字用法总结(附源码实例解析)构造函数(constructor)用法

目录

哈喽

多线程是什么

了解进程和线程

多线程头文件的用途

构造函数(constructor)用法

析构函数(destructor)

get_id用法:获取线程id

join的用法

detach的用法

joinable的用法

试一下

最后


哈喽

hello!

我是YR,

今天,我来总结一下C++中多线程的用法。

C++里的多线程还是非常重要的。

多线程是什么

了解进程和线程

如果要认识线程,首先要认识进程。

进程是什么?

指系统中能独立执行的程序称之为进程。是操作系统资源分配的基本单位。双cpu才能实现多进程,每个cpu执行一个进程。单cpu只是不断的切换执行程序,所以看起来像是同时执行多个进程,当实际上切换中有较大开销。
线程是什么?

线程可以看做是轻量级的进程,是任务调度和执行的基本单位。一个进程中至少包括一个线程或者多个线程。一个线程就是一个程序中的一条执行线索,如果要一进程中实现多段代码同时交替运行,就需要产生多个线程,并指定每个线程上面需要运行的代码段,这就是多线程。
举个例子,你需要写程序和吃饭。

如果你选择先写程序再吃饭或者先吃饭再写程序,这就是单线程。

如果你比较聪明,选择了一边写程序一边吃饭,这就是多线程。

C++11 新标准中引入了5个头文件来支持多线程编程,

它们分别是<atomic>  <thread> <mutex> <condition_variable> <future>

多线程头文件的用途

<atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
<thread>:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
<mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
<condition_variable>:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
<future>:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。

构造函数(constructor)用法

  • 构造函数(constructor)是一个特殊的类方法,它是在初始化对象时调用的类中的方法。有以下几个关键点:

    1. 构造函数调用时可以对类中的属性赋值以及输出相应的提示语句
    2. 构造函数无返回类型和返回值
    3. 一个类可以拥有多个构造函数
      例如下面的程序:
#include <iostream>
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 <iostream>
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 <iostream>
#include <string>
#include <sstream>
#include <cmath>
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();
}

析构函数(destructor)

和构造函数相似,析构函数也是一个特殊的类方法,它是用来自动释放对象的。析构函数的表示形式为:在类名前面加上“~”即表示析构函数。与构造函数不同的是,一个类中只允许一个析构函数存在。
如果类中无析构函数,则编译系统会默认补上一个空的析构函数。当程序结束时,会自动调用析构函数。例如下面的程序:
 

#include <iostream>
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

可以使用 .get_id() 的方法来获取线程的id。

比如这个程序

#include <iostream>
#include <thread>
using namespace std;
void show(int n){
		cout<<"show1:get_id() is "<<this_thread::get_id()<<",Now n is "<<n<<endl;
}
void show2(int n){
	
		cout<<"show2:get_id() is "<<this_thread::get_id()<<",Now n is "<<n<<endl;
}
int main()
{
	cout<<"main starts"<<endl;
	cout<<"mainThread is "<<this_thread::get_id()<<endl;
	thread t2(show,10);
	cout<<"t2 is "<<t2.get_id()<<endl;
	t2.join();
	cout<<"after t2.join(),t2 is "<<t2.get_id()<<endl;
	
	thread t3(show2,99);
	cout<<"t3 is "<<t3.get_id()<<endl;
	t3.detach();
	cout<<"after t3.detach(),t3 is "<<t3.get_id()<<endl;
	
	cout<<"main complete!"<<endl;
		
	
}

程序结果:

join的用法

举个例子,hello world的多线程版是这样的

这个创建的方式就是以函数作为一个入口,创建了一个子线程,那么创建的语句就是第11行代码所示,所传入的参数就是入口的函数名。在创建了这个子线程之后,这个子线程就开始运行了,同时主线程也不停的往下运行,当碰到t.join()这句代码的时候,就表示主线程需要等待子线程运行结束回收掉子线程的资源后,再往下运行,否则就会产生一种情况:当子线程还没有运行完主线程先运行完了,那么就会结束这个进程,从而中断了子线程的运行。因此join()函数的作用就是使主线程在此阻塞,等待子线程运行结束并回收其资源,再往下运行。
当然我们可以用this_thread::get_id()这个函数来验证这个子线程和主线程是不同的两个线程,结果如下图所示

detach的用法

看这张图

从这个图中我们可以发现fun和main是交叉着输出的,并不是先输出fun中的内容,那么detach的作用就是将主线程与子线程分离,主线程将不再等待子线程的运行,也就是说两个线程同时运行,当主线程结束的时候,进程结束。

       那么可能就会产生一些疑问,那这样不就中断了子线程的运行吗?

       其实不是,在detach的时候,这个子线程将脱离主线程的控制,子线程独立分离出去并在后台运行,相当于linux中的守护进程,那么该子线程会由运行时库托管。当主线程结束的时候,进程也就结束,那么该进程的所有线程也会结束,被分离出去的子线程会由运行时库回收其资源。
 

joinable的用法

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 <iostream>
#include <time.h>
#include <windows.h>
#include <fstream>
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<<"您的密码是:"<<rand_num<<endl;
		fstream out("password.txt",ios::out);
		out<<rand_num;
		out.close(); 
		cout<<"刷新时间:";
		while(true)
		{
			Sleep(1000);
			i++;
			cout<<"#";
			if(i>=10)
			{
				Sleep(500);
				i=1;
				rand_num=rd(100000,999999);
				system("cls");
				break;
			}
		}
	}
    return 0;
}

 a.cpp

#include<iostream>
#include<fstream>
#include<windows.h>
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<<endl<<"密码正确!";
        	Sleep(1000);
        	system("cls");
        	cout<<"结束";
        	return 0;
		}
		else{
			cout<<"密码错误!";
			Sleep(1000);
        	system("cls");
        	cout<<"结束";
        	return 0;
		}
    }
	return 0;
}

最后

好了,关于C++多线程的知识就到这里了,

路过的大佬可以顺手来个三连,作者会回访的

这次的博客写到了6000多字,不知道能不能上热榜呢?

拜拜

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>