【Linux学习】多线程——线程控制 | 线程TCB

🐱作者:一只大喵咪1201
🐱专栏:《Linux学习》
🔥格言:你只管努力,剩下的交给时间!
图

线程控制 | 线程TCB

  • 🧰线程控制
    • 🎴线程创建
    • 🎴线程结束
    • 🎴线程等待
      • 线程返回值
      • 线程取消(线程结束的一种方式)
    • 🎴线程分离
  • 🧰C++多线程
  • 🧰线程库中的TCB
    • 🎴线程tid
    • 🎴线程局部存储(__thread)
  • 🧰总结

🧰线程控制

Linux内核中并不存在线程的概念,我们程序员是通过库来使用线程的,这个库是POSIX线程库,是由原生线程库提供的,它遵守POSIX标准,就像之前学过的System V标准一样。POSIX线程库有以下几个特点:

  1. 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
  2. 要使用这些函数库,要通过引入头文<pthread.h>。
  3. 链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

🎴线程创建

系统调用接口:

图

  • pthread_t* thread:输出型参数,将线程的tid值放入到我们外部创建好的pthread_t 类型的变量中。
  • 第二个参数:用来设置线程属性,一般情况下设置成nullptr,等用到的时候再详细讲解。
  • void* (*start_routine)(void *):函数指针,这是一个回调函数,该函数的内容就是新线程要执行的。
  • void* arg:回到函数的参数。
  • 返回值: 线程创建成功返回0,不成功返回错误码。

一般情况下,新线程的创建是不会失败的,万一失败了,也不会设置errno,因为errno是一个全局变量,某个线程改变了这个变量会对其他线程造成影响,所以直接将错误码返回即可。

  • 在编译的时候,必须指定线程库,使用-l pthread选项。

接下来用这个接口创建一批线程:

#define NUM 10

void* start_routine(void* args)
{
    sleep(1);
    string name = (char*)(args);
    while(1)
    {
        cout<<"new thread name: "<<name<<endl;
        sleep(1);
    }
}

int main()
{
    //创建一批线程
    for(size_t i = 0; i < NUM; ++i)
    {
        pthread_t tid;
        char buffer[64];
        snprintf(buffer,sizeof buffer,"thread %d",i+1);
        pthread_create(&tid,nullptr,start_routine,(void*)buffer);
    }

    while(1)
    {
        cout<<"----create success----"<<endl;
        sleep(1);
    }
    return 0;
}

创建10个线程,让它们同时运行,并且给每个线程编号,新线程死循环打印各自的线程名字,新线程在延时1秒后开始执行。

图
将上诉代码运行起来后,查看线程,可以看到一共有11个线程,其中1个主线程,10个新线程。

图
但是运行结果中,10个线程都是线程10,其他9个线程并没有出现,这是什么原因呢?

图

  • 新线程中首先要延时1秒钟,然后才开始执行代码,在它延时的过程中,主线程一直在跑。
  • 主线程中的名字缓冲区会被覆盖,最终只有"thread 10"。
  • 当10个新线程开始执行时,需要去缓冲区中拿数据(缓冲区所有线程共享),所以拿到的都是"thread 10"。

上面代码中,本喵故意给新线程先延时了一秒钟,让主线程先跑,去覆盖缓冲区,如果不延时也有可能会出现上诉情况。

  • 主线程和新线程到底谁先执行是不确定的,是由操作系统的调度器决定的。

即使不给新线程延时,也有可能是主线程先运行,在时间片结束之前,同样会完成数据覆盖,导致新线程从缓冲区中只能读到最终的数据。

所以说,上面的代码是有问题的,我们需要保证每个线程都有自己独一无二的缓冲区。

class ThreadData
{
public:
    pthread_t _tid;
    char _name[64];
};

创建一个类,这个类中包括线程的tid以及名字的缓冲区。

图

  • 每个线程都在堆区new一个对象,来存放该线程的tid以及名字,然后将这个对象的地址传给新线程。
  • 新线程通过主线程传过来的指针找到属于它的结构体对象,然后使用里面的数据。

图
此时10个线程就都能正常运行了,不存在缓冲区的覆盖问题了,因为一个线程有一个缓冲区。

🎴线程结束

  1. return nullptr结束线程

图
当新线程执行到return的时候,就会结束。

  • 在线程中加了计数值,5秒后跳出循环,执行return,结束线程。

图
当计数值到了以后,新线程全部结束,只剩下主线程在执行。

  1. pthread_exit()结束线程

POSIX线程库专门提供了一个接口来结束线程:

图

  • 参数:返回线程结束信息,当前阶段设置成nullptr即可。

调用该接口的线程会结束。

图
同样,当计数值到了以后,新线程会调用该接口,然后就只剩下主线程了,新线程全部结束了。

注意:

不能使用exit()来结束线程,因为exit系统调用是争对进程的,调用该接口会让整个进程都结束掉。

🎴线程等待

和进程一样,线程也是需要等待的,如果不等待会造成内存泄漏,也就是结束掉的线程PCB不会被回收(类似僵尸进程),但是我们看不到没有回收的现象。

系统调用:

图

  • pthread_t thread:要等待的线程tid。
  • void** retval:线程结束信息返回,这是一个输出型参数。
  • 返回值:等待成功返回0,等待失败返回错误码。

图
主线程中并没有延时,它执行的速度是很快的。在新线程中需要进行计数,所以执行速度会慢很多。

图

可以看到,主线程在执行到线程等待的时候,会阻塞等待,不再往下执行,直到所有线程都等待成功才会继续向下执行。

所以说,线程等待是阻塞式等待

线程返回值

线程等待和进程等待一样,主要有两个作用:

  • 获取线程退出信息。
  • 回收线程PCB资源,防止内存泄漏。

上面线程等待的代码中并没有获取线程退出的相关信息,那么该如何获取线程退出的相关信息呢?

图

  • 新线程在结束的时候会返回一个void*类型的指针。
  • 在pthread线程库中,有一个void类型的指针变量来接收从线程中返回的void指针。
  • 指针变量和指针是有区别的,指针变量会开辟空间,里面存放的是指针。
  • 指针就是地址,是数字,不会开辟空间。

如上图中代码所示,将整形数字10强转成void*类型,然后返回。

  • 现在面临的问题就是怎么从pthread线程库中拿到从线程中返回的void*指针。

图
在主线程的栈区中有一个void类型的指针变量,新线程中返回的void类型指针最终会放到这个ret中。

  • pthread线程库中有一个void** 类型的二级指针变量retval。
  • pthread_join()系统调用将主线程中void*类型的指针变量的地址传给了pthread线程库中的二级指针变量,此时主线程就和线程库建立了联系。
  • 将新线程中返回到线程库中的void*指针变量中的返回值,通过这种联系放到主线程中指针变量中----也就是 *retval = ret。

这样,我们就可以成功的获取到新线程退出时的返回信息了,桥梁就是pthread_join()系统调用。

pthread_join()系统调用中,之所以传的是二级指针,是为了在pthread库中能够找到主线程中一级指针变量 void * ret。

图
在线程等待时,传入ret的二级指针获取线程退出信息。

  • 由于Linux中void* ret是8个字节,接收到的线程退出信息10也是一个void*类型的。
  • 我们要想看到这个值,需要将它转换成整数,所以必须转成longlong类型,也是8个字节,如果转成int的话会有精度损失从而会报错。

图
可以看到,每个线程在退出时的退出信息都被主线程接收到了,由于所有线程的退出信息都是10,所以接收到的也都是10。

图
通过pthread_exit()同样可以将线程的退出信息返回到pthread的线程库中,然后再通过线程等待接口拿走这个退出信息。

  • 在结构体中增加一个线程编号信息,每创建成功一个线程都给它一个编号。
  • 新线程在退出的时候返回各自的编号。
  • 线程等待代码不变,和上面一样。

图

此时我们就成功获得了各个线程在退出时候返回的编号,也就是获得了线程的退出信息。

整数都可以返回,更别说一个真正的地址了,可以将要返回的信息放在数组中,然后返回数组地址

  • 在学习进程等待的时候,我们不仅可以获得进程的退出信息,还能获得进程的退出信号,但是在线程退出时就没有获得线程退出信号,这是为什么呢?
  • 因为信号是发给进程的,整个进程都会被退出,线程要退出信号也没有意义了。
  • 而且pthread_join默认是能够等待成功的,并不考虑异常的问题,异常是进程要考虑的事,线程不用考虑。

线程取消(线程结束的一种方式)

线程取消的接口:

图

  • 参数:要取消的线程tid。
  • 返回值:取消成功返回0,失败返回错误码。
  • 只有运行起来的线程才能被取消。

图
在主线程中,新线程被创建后,取消一半的线程,然后继续进行线程等待。

图
10个线程被创建后就会跑起来。

  • 前五个线程被取消了,线程等待直接成功,不用再阻塞,被取消的线程等待成功后的返回值是-1,并不是我们设定的线程编号。
  • 未被取消的后五个线程,仍然阻塞等待,等待成功后返回的是我们设定的线程编号。

所以说,如果一个线程是被取消结束的,它的退出码就是-1。它其实是一个宏定义:PTHREAD_CANCELED。

线程取消也是一种线程结束的方式,放在这里是为了能够通过线程等待看线程退出的退出码。

🎴线程分离

线程的tid也可以通过接口获得,就像获得pid一样,获取tid的接口:

图
这个接口也是POSIX线程库提供的,哪个线程调用该接口就会返回哪个线程的tid。

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

但是这样主线程就需要阻塞式等待线程的释放,主线程什么都干不了。能不能像进程那样不需要阻塞式等待(将SIGCHID信号设置为忽略),等新线程结束以后自动释放呢?

  • 尤其是不需要关心线程返回值的时候,join是一种负担。

当然可以,将需要自动释放的线程设置成分离状态,将线程设置成分离状态意味着不需要主线程再关心该线程的状态,它会自动释放。

  • joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

线程分离的接口:

图

  • 参数:要分离的线程tid。
  • 返回值:成功返回0,不成功返回错误码。
  • 可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。自己分离自己就需要使用接口获取到自己的tid。

线程分离后,如果主线程仍然等待该线程,就会等待失败,返回错误码

新线程中分离自己:

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    size_t cnt = 5;
    pthread_detach(pthread_self());//线程分离
    while(cnt--)
    {
        cout<<"new thread name: "<<name<<", cnt: "<<cnt<<endl;
        sleep(1);
    }
    pthread_exit(nullptr);
}

int main()
{
    //创建新线程
    pthread_t tid;
    pthread_create(&tid,nullptr,start_routine,(void*)"thread one");
    cout<<"main thread tid: 0x"<<(void*)pthread_self()<<endl;

    //线程等待
    int n = pthread_join(tid,nullptr);
    cout<<"error: "<<n<<"->"<<strerror(n)<<endl;

    return 0;
}

在新线程中分离线程。
图
不是说线程分离了再进行线程等待就会失败吗?怎么上面的运行结果仍然是等待成功呢?

  • 因为主线程先被调度,在新线程被创建但是没有执行的时候主线程就开始等待新线程了。

所以当新线程将自己分离以后,主线程已经处于等待状态了,它不认为新线程被分离,还会继续等待,而且可以等待成功。

图
可以让主线程延时一段时间,保证新线程先执行,也就是保证线程分离发生在线程等待之前。

图
可以看到,此时主线程在进行线程等待的时候就会失败,而且返回错误码。

在主线程中分离新线程:

最为稳妥的办法就是在主线程中分离新线程:

图
在主线程中分离新线程,任何进行线程等待,并且主线程在一直运行。

图

  • 主线程等待新线程失败后直接返回错误码,然后接着向下运行,并不会阻塞。

在新线程运行结束以后,自动回收其PCB资源,只剩下主线程在运行。

  • 一个线程一旦被分离就不用再管这个线程了,在它运行结束的时候系统会自动回收,不会造成内存泄漏。

🧰C++多线程

我们知道,C++也是可以多线程编程的,而且提供了多线程的库,而无论什么编程语言,什么库,在Linux系统上的多线程本质上都是对pthread原生线程库的封装

接下面本喵就模拟一下C++对线程库的封装,写一个小组件,同时也方便我们后面直接使用:

#define NUM 1024

class Thread;//前置声明

class Context//线程上下文
{
public:
    Context()
    :_this(nullptr)
    ,_args(nullptr)
    {}

    //成员变量
    Thread* _this;
    void* _args;
};

class Thread
{
public:
    //重命名函数对象
    typedef std::function<void*(void*)> func_t;
    
    //构造函数
    //传入新线程执行的函数,参数,以及新线程编号
    Thread(func_t func, void* args = nullptr, int number = 0)
        :_func(func)
        ,_args(args)
    {
        //格式化线程名
        char buffer[NUM];
        snprintf(buffer,sizeof (buffer),"thread-%d",number);
        _name = buffer;
        //创建线程
        Context* ctx = new Context();
        ctx->_this = this;
        ctx->_args = _args;
        int n = pthread_create(&_tid,nullptr,start_routine,ctx);
    }

    void* run(void* args)
    {
        return _func(args);
    }

    //由于调用成员函数有隐藏的this指针,所以使用static修饰
    //void* start_routine(this, void* args)
    static void* start_routine(void* args)
    {
        Context* ctx = static_cast<Context*>(args);//安全的类型转换
        void* ret = ctx->_this->run(ctx->_args);
        delete ctx;
        return ret;
    }

    //线程等待
    void join()
    {
        int n = pthread_join(_tid,nullptr);
        assert(n==0);
        (void)n;
    }
private:
    std::string _name;
    func_t _func;
    void* _args;
    pthread_t _tid;
};

tu

  • 在调用start_routine成员函数的时候,会隐藏一个this指针。
  • 而pthread_create中的函数指针只有一个形参,为了消除这个指针,用static修饰新线程调用的函数。

此时就面临一个新的问题,在static函数内,需要调用类内的成员函数run(),但是没有this指针无法调用。

  • 创建一个上下文类,里面放线程类的this指针,在static函数内通过这个指针来调用类内的成员函数run()。

测试代码:

void* thread_run(void* args)
{
    string work_type=static_cast<const char*>(args);
    while(1)
    {
        cout<<"新线程:"<<work_type<<endl;
        sleep(1);
    }
}

int main()
{
    unique_ptr<Thread> thread1(new Thread(thread_run,(void*)"thread1",1));
    unique_ptr<Thread> thread2(new Thread(thread_run,(void*)"thread2",2));
    unique_ptr<Thread> thread3(new Thread(thread_run,(void*)"thread3",3));

    thread1->join();
    thread2->join();
    thread3->join();


    return 0;
}

tu
可以看到,成功创建3个新线程,并且在不停运行。

这里仅是语言层面对线程库的封装。

🧰线程库中的TCB

🎴线程tid

前面多次见过线程的tid值,但是一直不知道它是什么,现在来揭开它的神秘面纱。

图
新线程和主线成都打印新线程的tid,并且主线程也打印自己的tid。

图

  • 主线程和新线程打印的新线程tid的值都是一样的。
  • 而且tid的值是一个地址。

图
我们知道,Linux内核中是没有线程概念的,也没有对应的TCB结构。

  • 用户创建线程时使用的是POSIX线程库提供的接口。
  • 线程库中会调用clone()系统调用接口,在内核中创建线程复用的PCB结构。
  • 这些轻量级进程共用一个进程地址空间。

系统中肯定不只一个线程存在,大量的线程势必要管理起来,管理的方式同样是先描述再组织。既然Linux内核中只有轻量级进程的PCB,那么描述线程的TCB结构就只能存在于线程库中

所以pthread线程库中就会维护很多TCB结构:

//伪代码
struct pthread
{
	//线程局部存储
	//线程栈
	//....
}

线程库中的TCB里,存放着线程的属性,这里的TCB被叫做用户级线程

  • Linux线程方案:用户级线程以及用户关心的线程属性在线程库中,内核提供线程执行流的调度。
  • Linux 用户级线程 : 内核轻量级进程= 1 :1

一个线程的所有属性描述是由两部组成的,一部分就是在pthread线程库中的用户级线程,另一部分就是Linux中的轻量级进程,它们俩的比例大约是1比1。

图

  • pthread线程库从磁盘上加载到内存中后,通过页表再将虚拟地址空间和物理地址映射起来。
  • 线程库最终是映射在虚拟地址空间中的共享区中的mmap区域。

既然线程库是映射在共享区的,那么线程库所维护的TCB结构也就一定在共享区。

图

如上图所示,将映射到共享区的动态线程库放大。

  • 线程库中存在多个TCB结构来描述线程。
  • 每个TCB的地址就是线程id。

线程tid的本质就是虚拟地址共享区中TCB结构体的地址

  • 线程的栈也在共享区中,而不在栈中。
  • 虚拟地址空间中的栈是主线程的栈,共享区中动态库中的栈是新线程的栈。

所以说,线程的栈结构是相互独立的,因为存在于不同的TCB中(主线程除外)

🎴线程局部存储(__thread)

在共享区线程库中的TCB里,有一个线程的局部存储属性,它是一个介于全局变量和局部变量之间线程特有的属性。

图
在主线程和新现在中同时打印全局变量g_val以及它的地址。

图
主线程和新线程打印的值都是一样的。

  • 说明主线程和新线程共用一个全局变量。

那如果此时新线程仍然想用这个变量名,但是又不想影响其他线程,也就是让这个全局变量独立出来,该怎么办呢?此时就可以使用线程的局部存储属性了。

图

  • 在全局变量g_val前面加__thread(两个下划线),此时这个全局变量就具有了局部存储的属性。

主线程和新线程同样打印这个全局变量,并且新线程将这个具有局部存储属性的全局变量不断加一。

图

  • 主线程和新线程打印出来的全局变量的地址不相同了,说明此时用的并不是同一个全局变量。
  • 新线程修改这个值,主线程不受影响。
  • 可以将全局变量或者static变量添加 __thread,设置位线程局部存储。
  • 此时每个线程的TCB中都会有一份该变量,相互独立,并不会互相影响。

🧰总结

有了进程的基础,线程有些地方可以进行类比,还是比较容易理解的。线程控制非常重要,而且在编程中经常使用到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/17749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

写作业用白光还是暖光?盘点色温4000K的护眼台灯

台灯的白光或者暖光指的是台灯的色温&#xff0c;低色温的光线看起来发黄发红&#xff0c;高色温的光线发白发蓝。 如果灯光的光源是高品质光源&#xff0c;本身没有蓝光问题&#xff0c;那么色温的选择对护眼的影响是比较少的&#xff0c;更多的是对人学习工作状态&#xff0c…

Linux 之 vi 文本编辑器(二)

1、文本编辑器简介 Linux 中最常用的文本编辑器&#xff1a; vi&#xff1a;类 Unix 系统中默认的文本编辑器 vim&#xff1a;vi 编辑器的增强版本&#xff0c;习惯上也称 vi vi 文本编辑器的作用和特性&#xff1a; vi 可以执行插入、删除、查找、替换等众多文本操作&…

Leetcode268. 丢失的数字

Every day a leetcode 题目来源&#xff1a;268. 丢失的数字 解法1&#xff1a;排序 代码&#xff1a; /** lc appleetcode.cn id268 langcpp** [268] 丢失的数字*/// lc codestart class Solution { public:int missingNumber(vector<int> &nums){int n nums.s…

ESP32设备驱动-Si1145红外接近-紫外 (UV) 指数和环境光传感器驱动

Si1145红外接近-紫外 (UV) 指数和环境光传感器驱动 文章目录 Si1145红外接近-紫外 (UV) 指数和环境光传感器驱动1、Si1145介绍2、硬件准备3、软件准备4、驱动实现1、Si1145介绍 Si1145/46/47 是一款低功耗、基于反射的红外接近、紫外 (UV) 指数和环境光传感器,具有 I2C 数字接…

【一起撸个DL框架】4 反向传播求梯度

CSDN个人主页&#xff1a;清风莫追 欢迎关注本专栏&#xff1a;《一起撸个DL框架》 文章目录 4 反向传播求梯度&#x1f965;4.1 简介4.2 导数与梯度4.3 链式法则4.4 示例&#xff1a;y2x1的梯度 4 反向传播求梯度&#x1f965; 4.1 简介 上一篇&#xff1a;【一起撸个DL框架】…

【OpenCV】 2D-2D:对极几何算法原理

2D-2D匹配: 对极几何 SLAM十四讲笔记1 1.1 对极几何數學模型 考虑从两张图像上观测到了同一个3D点&#xff0c;如图所示**。**我们希望可以求解相机两个时刻的运动 R , t R,t R,t。 假设我们要求取两帧图像 I 1 , I 2 I_1,I_2 I1​,I2​之间的运动,设第一帧到第二帧的运动为…

全国快递物流 API 实现快递单号自动识别的原理解析

概述 全国快递物流 API 是一种提供快递物流单号查询的接口&#xff0c;涵盖了包括申通、顺丰、圆通、韵达、中通、汇通等600快递公司的数据。该 API 的目标是为快递公司、电商、物流平台等提供便捷、快速、准确的快递物流信息查询服务。 数据采集和处理 全国快递物流 API 的…

自定义控件 (?/N) - 颜料 Paint

参考来源 一、颜色 1.1 直接设置颜色 1.1.1 setColor( ) public void setColor(ColorInt int color) paint.setColor(Color.RED) paint.setColor(Color.parseColor("#009688")) 1.1.2 setARGB( ) public void setARGB(int a, int r, int g, int b) paint.se…

Packet Tracer – 研究 VLAN 实施

Packet Tracer – 研究 VLAN 实施 地址分配表 设备 接口 IP 地址 子网掩码 默认网关 S1 VLAN 99 172.17.99.31 255.255.255.0 不适用 S2 VLAN 99 172.17.99.32 255.255.255.0 不适用 S3 VLAN 99 172.17.99.33 255.255.255.0 不适用 PC1 NIC 172.17.10.2…

数字化转型导师坚鹏:数字化转型背景下的企业人力资源管理

企业数字化转型背景下的企业人力资源管理 课程背景&#xff1a; 很多企业存在以下问题&#xff1a; 不清楚企业数字化转型目前的发展阶段与重要应用&#xff1f; 不知道企业数字化转型给企业人力资源管理带来哪些机遇与挑战&#xff1f; 不知道企业数字化转型背景下如何…

SQL注入攻防入门详解

毕业开始从事winform到今年转到 web &#xff0c;在码农届已经足足混了快接近3年了&#xff0c;但是对安全方面的知识依旧薄弱&#xff0c;事实上是没机会接触相关开发……必须的各种借口。这几天把sql注入的相关知识整理了下&#xff0c;希望大家多多提意见。 &#xff08;对于…

系统集成项目管理工程师 下午 真题 及考点(2020年下半年)

文章目录 2020年下半年试题一&#xff1a;第10章 项目质量管理&#xff0c;规划质量管理过程的输入试题二&#xff1a;第9章 项目成本管理&#xff0c;典型&#xff1a;EAC ACETC AC&#xff08;BAC-EV&#xff09;/CPI BAC/CPI试题三&#xff1a;第18章 项目风险管理&#x…

吴恩达ChatGPT网课笔记Prompt Engineering——训练ChatGPT前请先训练自己

吴恩达ChatGPT网课笔记Prompt Engineering——训练ChatGPT前请先训练自己 主要是吴恩达的网课&#xff0c;还有部分github的prompt-engineering-for-developers项目&#xff0c;以及部分自己的经验。 一、常用使用技巧 prompt最好是英文的&#xff0c;如果是中文的prompt&am…

【网站架构】Nginx 4层、7层代理配置,正向代理、反向代理详解

大家好&#xff0c;欢迎来到停止重构的频道。 本期我们讨论网络代理。 在往期《大型网站 安全性》介绍过&#xff0c;出于网络安全的考虑&#xff0c;一般大型网站都需要做网络区域隔离&#xff0c;以防止攻击者直接操控服务器。 网站系统的应用及数据库都会放在这个网络安全…

【Python习题集6】类与对象

类与对象 一、实验内容二、实验总结 一、实验内容 1.设计一个Circle类来表示圆&#xff0c;这个类包含圆的半径以及求面积和周长的函数。在使用这个类创建半径为1~10的圆&#xff0c;并计算出相应的面积和周长。 半径为1的圆&#xff0c;面积: 3.14 周长: 6.28 半径为2的圆&am…

云原生介绍

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/130540430 一、云原生的概念 云原生的整体概念思路是三统一&#xff0c;即统一基础平台、统一软件架构、统一开发流程。 基于统一的基础平台、软件架构以及开发流程&#xff0c;数字化转型和云化转型能…

详解:搭建常见问题(FAQ)的步骤?

许多的Web用户都更加偏向于可信赖的FAQ页面&#xff0c;以此作为快速查找更多信息的方法。因为用户时间的紧缺&#xff0c;并且想知道产品的功能和能够提供的服务。构造精巧的FAQ页面是提供人们寻求信息的绝妙方法&#xff0c;而且还可以提供更多的信息。这就是为什么FAQ页面对…

Chrome远程调试

最近接触到Chrome远程调试相关内容&#xff0c;记录一下。 场景&#xff1a;使用Chrome远程调试Chromium。当能够控制目标主机执行命令之后&#xff0c;可以在该主机上建立全局代理&#xff0c;然后在自己这一边开启浏览器监听&#xff0c;接着在目标机器上执行 chrome.exe --…

3.13 结构体嵌套、大小及位域

目录 结构体嵌套结构体 结构体的大小 位域 结构体嵌套结构体 含义 结构体中的成员可以是另一个结构体 语法 struct 结构体名 { struct 结构体名 成员名&#xff1b; }; 结构体中共同的变量可以单独放出来&#xff0c;单独封装一个结构体 结构体的大小 字节对齐 含义 …

Bark:基于转换器的文本到音频模型

Bark是由Suno创建的一个基于转换器的文本到音频模型。Bark可以生成高度逼真的多语言语音以及其他音频&#xff0c;包括音乐、背景噪音和简单的音效。该模型还可以产生非语言交流&#xff0c;如大笑、叹息和哭泣。为了支持研究社区&#xff0c;我们正在提供对预先训练的模型检查…
最新文章