一文搞懂Linux多线程【下】

目录

🚩多线程代码的健壮性

 🚩多线程控制

🚩线程返回值问题

🚩关于Linux线程库

 🚩对Linux线程简单的封装

 


 在观看本博客之前,建议大家先看一文搞懂Linux多线程【上】由于上一篇博客篇幅太长,为了更好的阅读体验,我拆成了两篇博客。那么接下来,在上一篇的基础上,我们继续学习Linux信号部分。今天,我们主要学习Linux多线程控制问题。

🚩多线程代码的健壮性

一份代码写的如何,一项重要的指标就是这份代码的健壮性

我们来写一份代码:

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;

int g_val=0;
void* pthread_routine(void *args)
{
    while(1)
    {
         cout<<"我是新进程,我正在运行,g_val:"<< g_val++<<"  &g_val: "<<&g_val<<endl;
         int n=0;
         n=n/0;
        //  发生除零错误,
         sleep(1);
    }
   

}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,pthread_routine,(void *)"thread one");
    assert(n==0);
    (void)n;

    while(1)
    {
        cout<<"我是主进程,我正在运行,g_val:"<<g_val<<"   &g_val:"<<&g_val<<endl;
        sleep(1);
    }
    return 0;

}

在新线程中,出现了除零错误。

我们发现:新线程和主线程一起被干掉了。为什么?

这是因为代码中出现了错误,操作系统堆整个进程发送信号,操作系统要回收这个进程的资源,而线程是从进程那里得到的字眼,进程都没了,线程也就没法存在了。这个进程中的所有的线程都会被终止。

举个例子:在某互联网大厂,张三是一名程序员,他所在项目组敷个某个程序的开发。有一天,张三在写代码之前干了一瓶二锅头,代码中出现了很严重的bug,张三写代码出现了问题,就是这个项目组出现了问题。公司要对这个事情做出惩罚。所以就对张三的项目组长进行了谈话和警告。这个项目组就是一个进程,项目组的每一个成员就是一个线程(轻量级进程)。成员出现了错误,自然要惩罚这个项目组。

所以:多线程的代码的健壮性非常不好,这也是线程很大的一个缺点。 

 🚩多线程控制

Linux多线程控制是一个很重要的部分,这其中包括线程创建,线程等待,线程替换等等话题。

🚀Linux线程创建

我们在之前讲过如何创建线程,今天,我想一次性创建多个线程。让一个进程中包括若干个执行流。并同时运行。

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;
void *start_routine(void *args)
{
    char *buf=static_cast<char*>(args);
    int cnt=10;
    while(cnt)
    {
        cout<<"我是新线程, "<<buf<<" cnt:"<<cnt<<endl;
        cnt--;
        sleep(1);
    }
}
int main()
{
    for(int i=0;i<10;i++)
    {
        pthread_t id;
        char buffer[64];
        snprintf(buffer,sizeof buffer,"%s:%d","thread",i);
        pthread_create(&id,nullptr,start_routine,buffer);

    }
    while(1)
    {
        cout<<"我是新线程,我一直在运行"<<endl;
        sleep(1);
    }
}

我们发现:我们创建的新线程的打印信息都是thread:9 。但是我们通过命令查询到我们的确创建了多个线程。但为什么都是9号呢?

我们知道,如上的两点代码是两个不同的执行流,究竟是哪个执行流先被执行呢?不确定,这是由编译器的调度顺序决定的。但是我们的运行的结果证明是主线程先被执行。为什么?

我们在for循环中定义了一个字符串,在pthread_create循环中传入了字符串的起始地址。有没有可能这块空间里的内容在被打印之前就被修改了呢?事实证明就是如此。主线程在新线程读取字符串内容之前,先将字符串的内容进行了修改。

这种现象的背后的根本原因是:字符串是一个共享资源,被所有线程所共享。由此造成这一系列问题。

如何更改这种现象呢?

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;


class Threads
{
public:
    pthread_t id;
    char buffer[64];
};

void* pthread_routine(void *args)
{
    int cnt=5; 
    Threads *newThreads=static_cast<Threads *>(args); 
    while(cnt--)
    {  
         cout<<"new thread make success,name:  "<<newThreads->buffer<<" cnt:"<<cnt<<endl;    
         sleep(1);

    }
    // delete newThreads;
    // return nullptr;

}
int main()
{
   for(int i=0;i<10;i++)
   {
        Threads *id=new Threads();
        snprintf(id->buffer,sizeof(id->buffer),"%s:%d","thread",  i);
        pthread_create(&id->id,nullptr,pthread_routine,id);
     
   }
    while(1)
    {
        cout<<"我是主进程,我正在运行"<<endl;
        sleep(1);
    }
    return 0;
}

我们顶定义一个类,每个线程被创建之初都会创建一个独立的类对象,这个类归线程所属,是私有的,所以不会出现上面的情况,关于这个类的相关数据储存在哪里,我们后面再谈。

 接下来,我们就改过的代码来回答几个问题:

在这份代码中,start_routine函数被几个执行流执行呢?10个

start_routine函数处于什么状态?可重入状态。

那这个函数是可重入函数吗?是的。


这个函数内的变量是被所有的线程共享的吗?我们实验一下:

我们对打印内容做稍稍改动:

我们发现:不同的执行流的cnt地址都不同,说明每一个线程都有一个独立的栈结构。 至于在什么位置,我们后面再说。

线程创建我们掌握了,那如何让线程终止呢?


🚀Linux线程终止

 方法一

 在线程对应的代码中,return返回,这个线程就终止了。

但是不能调用exit函数,我们说过exit是用来终止进程的,这时所有的线程都会跟着终止。

 方法二

调用pthread_exit函数

该函数的作用是终止调用这个函数的线程。

参数我们默认设为nullptr。 

方法三

一个跑起来的线程是可以被取消的。取消线程的函数为pthread_cancel。下面,我们简单介绍一下:

#include <pthread.h>
 int pthread_cancel(pthread_t thread);

 参数很简单,一看就明白

返回值:成功时返回0,失败错误码被设置。

下面,我们感受一下:

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<vector>
#include<string>
using namespace std;

class Threads
{
public:
    pthread_t id;
    char buffer[64];
};
class ThreadReturn
{
public:
     int return_result;
     int return_code;
};
void* pthread_routine(void *args)
{
    int cnt=5; 
    Threads *newThreads=static_cast<Threads *>(args); 
    while(cnt--)
    {  
         cout<<"new thread make success,name:  "<<newThreads->buffer<<endl;

         sleep(1);
     } 
     
     return (void*)123;
}
int main()
{
   vector<Threads *> iter;
   for(int i=0;i<10;i++)
   {
        Threads *id=new Threads();
        snprintf(id->buffer,sizeof(id->buffer),"%s:%d","thread",  i);
        pthread_create(&id->id,nullptr,pthread_routine,id);
        iter.push_back(id);
   }
   sleep(5);
//    将创建的线程先取消一半
   for(int i=0;i<iter.size()/2;i++)
   {
          pthread_cancel(iter[i]->id);
          cout<<"pthread_cancel:"<<iter[i]->buffer<<" successful"<<endl;
   }
   for(auto &it:iter)
   {

        void *ret;
        int n = pthread_join(it->id, (void**)&ret); 
        assert(n == 0);
        cout << "join " << it->id << " successful" <<(long long)ret<< endl;
   }
   cout<<"main thread quit"<<endl;

}

如果一个线程被pthread_cancel取消,这个线程的返回值会被设为-1。 

🚀Linux线程等待

线程也是需要等待的,如果不等待,会造成类似僵尸进程问题,否则会造成内存泄漏。线程必须要被等待的。等待工作需要:

  1. 获取新线程的退出信息。
  2. 回收新线程对应的PCB资源,防止内存泄漏。

 所以,我们有必要且必须对线程进行等待,线程等待函数是pthread_join。下面,我们简单介绍一下这个函数。

参数:

第一个参数为:要等待的线程的thread,第二个我们先不需要了解,默认为nullptr。

返回值:

等待成功,返回0;等待失败,返回相应的错误码。

接下来,我们来试一下:

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<vector>
using namespace std;

class Threads
{
public:
    pthread_t id;
    char buffer[64];
};

void* pthread_routine(void *args)
{
    int cnt=5; 
    Threads *newThreads=static_cast<Threads *>(args); 
    while(cnt--)
    {  
         cout<<"new thread make success,name:  "<<newThreads->buffer<<" cnt:"<<cnt<<" &cnt"<<&cnt<<endl;    
         sleep(1);
     } 
     pthread_exit(nullptr);
}
int main()
{
   vector<Threads *> iter;
   for(int i=0;i<10;i++)
   {
        Threads *id=new Threads();
        snprintf(id->buffer,sizeof(id->buffer),"%s:%d","thread",  i);
        pthread_create(&id->id,nullptr,pthread_routine,id);
        iter.push_back(id);
   }
   for(auto &it:iter)
   {
        int n=pthread_join(it->id,nullptr);
        assert(n==0);
        cout<<"join "<<it->id<<" successful"<<endl;
   }
   cout<<"main thread quit"<<endl;

}

 运行一下:

如我们所见:所有的线程同一时间全部退出,接着主线程退出。

🚀Linux线程分离

  • ‘’默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
  • 一个线程被分离,就不能调用pthread_join()进行线程等待。

接下来,我们简单介绍一下几个函数。

pthread_self()

       #include <pthread.h>

       pthread_t pthread_self(void);

使用起来很简单,作用就是返回调用该线程的Id。由于太简单,这里我们就不再演示了。

pthread_detech()

       #include <pthread.h>

       int pthread_detach(pthread_t thread);

参数很简单,要对哪个线程进行分离,就传入哪个线程的Id。

返回值;成功的话,返回0;失败时,错误码被设置。

接下来,我们尝试使用一下:

 

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<vector>
#include<string>
#include<cstring>
using namespace std;

string changeId(const pthread_t  &id)
{
     char buffer[128];
     snprintf(buffer,sizeof buffer,"0x%x",id);
     return buffer;
}
void *start_routine(void *args)
{
     pthread_detach(pthread_self());
     int cnt=5;
     while(cnt)
     {
          cout<<"new spthread running..... Id:"<< changeId(pthread_self())<<endl;
          sleep(1);
          cnt--;
     }
}
int main()
{
     pthread_t id;
     int n=pthread_create(&id,nullptr,start_routine,(void*)"thread noe");
     assert(n==0);
     (void)n;
     pthread_detach(id);
    
     sleep(5);

    
     return 0;
}

这个函数的调用原则为:一般让主线程对新线程进行分离,在主线程中进行调用。 

🚩线程返回值问题

我们刚刚在讲解pthread_join()和pthread_exit()函数时,我们一般将参数设为nullptr。至于原因,我们当时没说,现在我们就来分析一下这些参数。这些参数都是输出

一切的一切都在告诉我们这个参数不简单,我们先写个代码试一下这个参数的作用。

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<vector>
using namespace std;

class Threads
{
public:
    pthread_t id;
    char buffer[64];
};

void* pthread_routine(void *args)
{
    int cnt=5; 
    Threads *newThreads=static_cast<Threads *>(args); 
    while(cnt--)
    {  
         cout<<"new thread make success,name:  "<<newThreads->buffer<<endl;

         sleep(1);
     } 
     
     return (void*)123;
}
int main()
{
   vector<Threads *> iter;
   for(int i=0;i<10;i++)
   {
        Threads *id=new Threads();
        snprintf(id->buffer,sizeof(id->buffer),"%s:%d","thread",  i);
        pthread_create(&id->id,nullptr,pthread_routine,id);
        iter.push_back(id);
   }
   for(auto &it:iter)
   {
     
          void *ret;
        int n=pthread_join(it->id,&ret);

        assert(n==0);
        cout<<"join "<<it->id<<" successful"<<" ret:"<<(long long)ret<<endl;
   }
   cout<<"main thread quit"<<endl;

}

我们在线程调用的函数中返回了 return (void*)123; 

运行一下:

看,我们输入的123成功读取到了 。我们是怎么做到的?为了方便叙述,我将相关的代码块拎出来。

 请看如下图:pthread_join的第二个参数是输出型参数

在pthread库中,有一个变量存储的数据为void* 。我们将在线程函数的返回值强转成void*,存储在这个变量中,我们暂且将这个存储的数据叫做X数据。

现在,我们在代码层面同样定义了一个void*的变量ret。如何将X数据转到我们用户层面的ret中呢?办法有两种:

方案1:直接把x的值赋值给ret。因为他们都是一级指针,解引用就可以得到数据(123)。

方案2:对ret取地址,对数据X取地址,都得到二级指针,然进行赋值,再解引用得到数据(123)。

在这里,OS采用的是第二种方式。因为我们调用的是函数来获取,无法实现简单的赋值。

接下来,我们看图理解一下

大家懂的话,可以返回任意类型的数据,但是:必须是new出来的类型,因为new出来的类型不会因为出了函数空间被释放。接下来,我给大家示范一下:

来看:

成功打出来。


进程退出时,都会设置对应的退出码,线程这里为什么没有退出码呢? 

线程异常,收到信号,整个进程都会退出。 

pthread_join默认就认为函数可以调用成功,不考虑异常,异常是进程应该来考虑的事情。 

🚩关于Linux线程库

其实,C++也可以写多线程代码:

#include<iostream>
#include<thread>
#include<unistd.h>
using namespace  std;

void start_routine()
{
     cout<<"我是新线程"<<endl;
     sleep(1);
}
int main()
{
     std::thread t1(start_routine);
     while(1)
     {
          cout<<"我是主线程"<<endl;
          sleep(1);
     }
     t1.join();
     return 0;

}

但是,在编译时必须指明要链接pthread库。

任何语言在Linux下使用多线程,必须使用pthread库。

c++的多线程,在Linux下,本质是对pthread库的封装。 


我们遗留的问题,现在有必要搞清楚了。

我们已经确认每一个线程都有自己的独立栈结构,这个栈结构在哪里?

 我们每创建一个线程,都会有一个线程的Id,这个Id看起来是个地址,究竟是什么地址?

原生线程库中有可能会存在很多的线程,要不要对这些进行有效的管理?要,管理的方式就是先描述,再组织。相对于进程的属性,线程的属性就显得非常少,因为在进程的PCB中,有一部分属性就是线程的属性。

如图,用户每在用户层面创建一个线程,就会在pthread库中创建一个属性集数据结构,该属性集指向操作系统内的一个用户级进程。

 在该属性集中一定包括线程独立栈的地址,LWP值等等。

Linux方案:用户级进程,用户关心的线程属性在库中,内核(操作系统)负责提供操作系统执行流的调度。

Linux用户级线程:轻量级进程=1:1;

用户级线程库可以当作磁盘中的一个文件(也就是一个动态库)。该线程库经过映射,可以通过mmap区域找到这个动态库。一个进程的所有线程的属性集(就是TCB结构体)都会被保存在这里。 我们可以想象成一个数组。但是我们不是通过下标查找。我们创建线程返回的地址,就是对应线程的TCP在此处的起始地址,通过这个地址就可以找到这个线程私有的栈结构。

 那创建一个线程,pthread库应该帮我们做哪个工作呢?

帮我们在pthread库中创建线程控制块(TCB)。然后返回TCB的起始地址,便于用户对线程进行操作。属于新线程私有的数据都会存储在线程私有栈中,主线程的数据则存储在共享区中。


🚀线程的局部存储。

废话不说,上码:

#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <cassert>
#include <vector>
#include <string>
#include <cstring>
using namespace std;

int g_val = 100;
void *start_routine(void *args)
{
     while (1)
     {
          cout << "new  pthread running..... g_val: " << g_val << " &g_val " << &g_val << endl;
          sleep(1);
          g_val++;
     }
}
int main()
{
     pthread_t id;
     int n = pthread_create(&id, nullptr, start_routine, (void *)"thread noe");
     assert(n == 0);
     (void)n;
     while (1)
     {
          cout << "main pthread running..... g_val: " << g_val << " &g_val " << &g_val << endl;
          sleep(1);
     }
     pthread_join(id,nullptr);
     return 0;
}

全局变量被所有进程所共享,所以新线程对数据进行修改后,主线程立刻就可以读取。

接着,我们做部分代码修改

__thread int g_val = 100;

我们发现,新线程对数据进行修改,但主线程读取的数据并未改变,并且地址读取的地址也不同。

 

对比,我们发现,前后地址差别好大。这就是__thread的作用了。

 __thread的作用是什么?

在一个全局变量前加__thread,可以将一个内置类型设置为线程局部存储(就本例子而言,未添加前,数据存储在已初始化数据段;添加后,在运行时,会将数据给每个线程都拷贝一份,存储在自己的栈中,线程之间数据修改彼此互不影响。

由于存储的地址空间发生改变,空间中越往上地址越大,所以我们读取的地址理所当然的变大了。

 🚩对Linux线程简单的封装

代码如下:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include  <pthread.h>
// header only 开源代码

class Thread;

//上下文,当成一个大号的结构体
class Context
{
public:
    Thread *this_;
    void *args_;
public:
    Context():this_(nullptr), args_(nullptr)
    {}
    ~Context()
    {}
};

class Thread
{
public:
    // using func_t = std::function<void*(void*)>;
    typedef std::function<void*(void*)> func_t;
    const int num = 1024;
public:
    Thread(func_t func, void *args = nullptr, int number = 0): func_(func), args_(args)
    {
        // name_ = "thread-";
        // name_ += std::to_string(number);

        char buffer[num];
        snprintf(buffer, sizeof buffer, "thread-%d", number);
        name_ = buffer;

         // 异常 == if: 意料之外用异常或者if判断
        // assert: 意料之中用assert
        Context *ctx = new Context();
        ctx->this_ = this;
        ctx->args_ = args_;
        int n = pthread_create(&tid_, nullptr, start_routine, ctx); //TODO
        assert(n == 0); //编译debug的方式发布的时候存在,release方式发布,assert就不存在了,n就是一个定义了,但是没有被使用的变量
        // 在有些编译器下会有warning
        (void)n;
    }
    // 在类内创建线程,想让线程执行对应的方法,需要将方法设置成为static
    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;
    }

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

    ~Thread()
    {
        //do nothing
    }
private:
    std::string name_;
    func_t func_;
    void *args_;

    pthread_t tid_;
};

到这里,本篇内容就结束了,我们下期内容,再见!!。 

 

 

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

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

相关文章

任务5.1 初识Spark Streaming

实战概述&#xff1a;使用Spark Streaming进行词频统计 1. 项目背景与目标 背景: Spark Streaming是Apache Spark的流处理框架&#xff0c;用于构建可伸缩、高吞吐量的实时数据处理应用。目标: 实现一个实时词频统计系统&#xff0c;能够处理流式数据并统计文本中的单词出现频…

网易严选礼品卡有什么用?

网易严选的礼品卡可以在网易商城里买东西 但是现在好多人买东西基本上都用的是淘宝京东之类的 很少会有人用网易吧 但是最近我朋友送了我几张网易的卡&#xff0c;我自己也用积分兑换一张&#xff0c;一直不知道怎么用 最后还是在收卡云上转让出去了&#xff0c;价格高不说…

yolo-world使用自己数据集训练

YOLO-World下载&#xff1a; https://github.com/AILab-CVC/YOLO-World/tree/master 1.数据准备 数据格式COCO格式即可 2.配置文件修改 configs/finetune_coco/yolo_world_v2_l_vlpan_bn_sgd_1e-3_40e_8gpus_finetune_coco.py &#xff08;1&#xff09; 模型下载路径&#xf…

vue3-openlayers 要素聚合(cluster)、icon聚合

本篇介绍一下使用vue3-openlayers 要素聚合&#xff08;cluster&#xff09;&#xff0c;icon聚合 1 需求 要素聚合&#xff08;cluster&#xff09;&#xff0c;icon聚合 2 分析 使用ol-source-cluster 4 实现 <template><ol-map:loadTilesWhileAnimating"…

gin数据解析和绑定

一. Json数据解析和绑定 html文件&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <meta htt…

Java数据脱敏

数据脱敏 敏感数据在存储过程中为是否为明文, 分为两种 落地脱敏: 存储的都是明文, 返回之前做脱敏处理不落地脱敏: 存储前就脱敏, 使用时解密, 即用户数据进入系统, 脱敏存储到数据库中, 查询时反向解密 落地脱敏 这里指的是数据库中存储的是明文数据, 返回给前端的时候脱…

带货直播部门的薪酬提成还有绩效考核怎么做!

直播带货公司一大片&#xff0c;老板一定要控制好自己利润很好的时候分钱的这个欲望&#xff0c;因为不怕分钱&#xff0c;就怕分错了之后收不回来。举例&#xff1a;你今年赚了 1, 000 万&#xff0c;然后你的运营或者你的投手是不是你感觉他的贡献很大&#xff0c;这时候你就…

时延降低 50%,小红书图数据库如何实现多跳查询性能大幅提升

多跳查询为企业提供了深入的数据洞察和分析能力&#xff0c;它在小红书众多在线业务中扮演重要的角色。然而&#xff0c;这类查询往往很难满足稳定的 P99 时延要求。小红书基础架构存储团队针对这一挑战&#xff0c;基于大规模并行处理&#xff08;MPP&#xff09;的理念&#…

【已解决】Pycharm:卡顿解决方案汇总

可能原因&#xff1a; 1、内存少 2、加载慢 3、文件多 4、硬件老 解决方案&#xff1a; 本机测试在 MAC&#xff0c;Windows、Linux也有相应的设置&#xff0c;请自行查询。 一、调整Pycharm使用内存 Help - Change Memory Settings 二、取消勾选 重复打开上次项目 Pych…

什么是Arkose Labs挑战及其解决方法

Arkose Labs挑战是一种复杂的机制&#xff0c;旨在验证用户是真正的人类&#xff0c;而不是自动化的机器人或脚本。这一挑战在维护在线服务的安全性和完整性方面发挥着关键作用&#xff0c;通过防止欺诈活动并确保只有真实用户才能访问某些功能。 目录 什么是Arkose Labs挑战&a…

地理空间数据格式GeoJSON扫盲,在CesiumJS中如何加载。

Hi&#xff0c;我是贝格前端工场&#xff0c;GIS已经越来越多的应用在可视化大屏中了&#xff0c;开发GIS类应用就少不了地理空间数据&#xff0c;本文介绍一下数据GeoJSON数据格式。 一、什么是GeoJSON数据格式&#xff0c;在GIS开发中有什么作用 GeoJSON是一种基于JSON&…

T100M2S2 M.2高清2路SDI采集卡

产品简介&#xff1a; 同三维T100M2S2一款支持全高清1080P 60HZ高清M2型两路SDI采集卡&#xff0c;板卡采用了高速的M.2-PCI-E接口&#xff0c;可实现1080P全实时不丢帧60帧传输。支持高清SDI输入&#xff0c;满足各种用户的需求&#xff0c;其最高分辨率可以实现1920&time…

文生视频模型Sora刷屏的背后的数据支持

前言&#xff1a;近日&#xff0c;OpenAI的首个文生视频模型Sora横空出世&#xff0c;引发了一波Sora热潮。与其相关的概念股连续多日涨停&#xff0c;多家媒体持续跟踪报道&#xff0c;央视也针对Sora进行了报道&#xff0c;称这是第一个真正意义上的视频生成大模型。 01 …

Java医院绩效考核系统源码:考核目标、考核指标、考核方法、考核结果与奖惩措施

Java医院绩效考核系统源码&#xff1a;考核目标、考核指标、考核方法、考核结果与奖惩措施 随着我国医疗体制的改革广大人民群的看病难&#xff0c;看病贵的问题一直没有得到有效地解决医疗费用的上涨&#xff0c;远远大于大多数家庭收入的增长速度。医院的改革已经势在必行&am…

早餐店小程序开发

在快节奏的城市生活中&#xff0c;早餐对于许多人来说是一天中最重要的一餐。然而&#xff0c;传统的早餐店在经营过程中常常面临客流量不稳定、服务效率低下等问题。为了解决这些问题&#xff0c;越来越多的早餐店老板开始寻求利用科技手段提升经营效率。早餐店小程序作为一种…

项目验收测试有必要找第三方软件测试机构吗?

在当今信息技术飞速发展的时代&#xff0c;软件测试成为了确保软件质量的重要环节。而在项目的验收测试中&#xff0c;很多企业都面临一个问题&#xff0c;那就是是否有必要找第三方软件测试机构进行验收测试?今天&#xff0c;我们就来探讨一下这个问题。 第三方软件测试机构…

python中的nan是什么意思

NaN&#xff08;not a number&#xff09;&#xff0c;在数学表示上表示一个无法表示的数&#xff0c;这里一般还会有另一个表述inf&#xff0c;inf和nan的不同在于&#xff0c;inf是一个超过浮点表示范围的浮点数&#xff08;其本质仍然是一个数&#xff0c;只是他无穷大&…

如何制作自己的网站

制作自己的网站可以帮助个人或组织在互联网上展示自己的品牌、作品、产品或服务。随着技术的发展&#xff0c;现在制作网站变得越来越简单。下面是一个简单的步骤指南&#xff0c;帮助你制作自己的网站。 1. 确定你的网站需求和目标 在开始之前&#xff0c;你需要明确你的网站的…

左右旋分辨

从端头看&#xff0c;切削路径顺时针是右旋&#xff0c;反时针左旋。

【JVM-1】JVM内存结构

目录 什么是JVMJava源码执行机制class文件的组成部分 JVM跨平台原理JVM的组成堆年轻代与老年代对象分配过程GC类型Full GC触发条件&#xff1a;对象进入老年代的触发条件 对象分配过程&#xff1a; 字符串常量池静态变量线程本地分配缓冲区&#xff08;TLAB&#xff09;TLAB相关…