Linux操作系统——多线程

news2025/2/25 0:30:37

1.线程特性

1.1线程优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

1.2线程缺点

  • 性能损失
    •  一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低
    • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制
    • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高
    • 编写与调试一个多线程程序比单线程程序困难得多。

1.3线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。

1.4线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

2.编写代码理解多线程

首先我们创建一个Makefile编写如下代码:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf mythread

然后创建一个mythread.cc的c++源文件编写如下代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h>


void * ThreadRoutine(void * arg)
{
    while(true)
    {
        std::cout<<"I am a new pthread"<<std::endl;
        sleep(1);
    }
}


int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,ThreadRoutine,nullptr);
    

      while(true)
    {
        std::cout<<"I am a main pthread"<<std::endl;
        sleep(1);
    }
    return 0;
}

然后编译运行发现报错了:

g++说不认识这个pthread_create这个接口,那么我们就需要来谈一个话题了,Linux有没有真正的线程呢?没有,内核中只有轻量级进程的概念,所以Linux操作系统只会提供轻量级进程创建的系统调用,不会直接提供线程创建的接口。

因为可能用户学习的操作系统中是有线程这个概念的,但是Linux内核只认轻量级进程LWP所以两者就无法达成共识,为了让用户认为自己创建的是线程,然后Linux操作系统认为创建的是轻量级进程,所以就有了中间的软件层,pthread原生线程库,这个库一般都是跟linux配套在一起的,所以不用担心用户因为没有这个pthread原生线程库而调用创建线程的接口而失败。但是又有人说了,为什么Linux非得用轻量级进程而不去实现线程,因为轻量级进程又不得不实现一个像pthread原生线程库这样的库,这不是多此一举吗?其实并不是这样的,用轻量级进程LWP模拟线程本就是Linux操作系统的一大亮点,而且中间有一层pthread原生线程库反而可以让接口层与实现层进行解耦,未来如果pthread原生线程库要是更新了也不会影响到Linux内核,比如说无论上层怎么更新同样可以是用同一版本的Linux内核,这样维护性的效率就大大提高了,这样的实现更符合软件工程。这个库我们是可以通过搜索该路径看到的:

所以上述代码报错的原因是找不到我们对应的库,所以我们将Makefile中的代码改为:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -rf mythread

运行结果:

我们可以查看一下链接的库:

此时两个线程就开始运行了。

下面我们对线程创建再进行扩展:

比如说我们如何给线程传参呢?如何传递线程创建的时间啊,执行的任务,线程名称

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>


using func_t = std::function<void()>;

class ThreadData
{
public:
    ThreadData(const std::string name,const uint64_t ctime,func_t f)
        :threadname(name),createtime(ctime),func(f)
        {}
public:
    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout<<"我是线程执行的大任务的一部分"<<std::endl;
}

void * ThreadRoutine(void * args)
{

   ThreadData *td = static_cast<ThreadData*>(args);
    while(true)
    {
        std::cout<<"I am a new pthread"<<"threadname: "<<td->threadname<<"create time: "<<td->createtime<<std::endl;
        td->func();
        sleep(1);
    }
}


int main()
{
    pthread_t tid;
    ThreadData * td = new ThreadData("thread 1",(uint64_t)time(nullptr),Print);
    pthread_create(&tid,nullptr,ThreadRoutine,td);
    
      while(true)
    {
        std::cout<<"I am a main pthread"<<std::endl;
        sleep(1);
    }
    return 0;
}

运行结果:

下面如何修改代码变成创建多线程呢?

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

const int threadnum = 5;
using func_t = std::function<void()>;

class ThreadData
{
public:
    ThreadData(const std::string name,const uint64_t ctime,func_t f)
        :threadname(name),createtime(ctime),func(f)
        {}
public:
    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout<<"我是线程执行的大任务的一部分"<<std::endl;
}

void * ThreadRoutine(void * args)
{

   ThreadData *td = static_cast<ThreadData*>(args);
    while(true)
    {
        std::cout<<"I am a new pthread"<<"threadname: "<<td->threadname<<"create time: "<<td->createtime<<std::endl;
        td->func();
        sleep(1);
    }
}


int main()
{
    std::vector<pthread_t> pthreads;
    for(int i = 0;i<threadnum;i++)
    {
        pthread_t tid;
        char threadname[64];
        snprintf(threadname,sizeof(threadname),"%s - %lu","thread",i+1);
        ThreadData * td = new ThreadData(threadname,(uint64_t)time(nullptr),Print);
        pthread_create(&tid,nullptr,ThreadRoutine,td);
        pthreads.push_back(tid);
        sleep(1);
        
    }
    
      while(true)
    {
        std::cout<<"I am a main pthread"<<std::endl;
        sleep(1);
    }
    return 0;
}

把创建线程的代码放入for循环,然后将threaname都有不同的线程名称,而且将tid保存起来我们用到了vector.

下面我们运行一下:

所以这里我们就创建5个新线程。

下面我们来研究两个问题:

1.线程的健壮性问题:当一个进程有多个线程时,只要有一个线程触发了异常,整个进程也会受到相应的影响。

比如说我们修改一个函数中的代码故意制造除零错误触发段错误来进行验证:

void * ThreadRoutine(void * args)
{
    int a = 10;
    ThreadData *td = static_cast<ThreadData*>(args);
    while(true)
    {
        std::cout<<"I am a new pthread"<<"threadname: "<<td->threadname<<"create time: "<<td->createtime<<std::endl;
        td->func();
        if(td->threadname=="thread - 4")
        {
            std::cout<<td->threadname<<" 触发了异常"<<std::endl;
            a/=0;
        }
        sleep(1);
    }
}

当线程名称为第四个的时候让其触发段错误,促使让操作系统发送8号信号让进程终止。

我们通过监视窗口可以看到,进程直接被终止了,说明一旦线程出现了异常,那么操作系统会给进程发信号让进程退出,那么进程都退了,线程自然也就退了。

2.观察一下thread id

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

const int threadnum = 5;
using func_t = std::function<void()>;

class ThreadData
{
public:
    ThreadData(const std::string name,const uint64_t ctime,func_t f)
        :threadname(name),createtime(ctime),func(f)
        {}
public:
    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout<<"我是线程执行的大任务的一部分"<<std::endl;
}

void * ThreadRoutine(void * args)
{
    int a = 10;
    ThreadData *td = static_cast<ThreadData*>(args);
    while(true)
    {
        std::cout<<"I am a new pthread"<<"threadname: "<<td->threadname<<"create time: "<<td->createtime<<std::endl;
        td->func();
        // if(td->threadname=="thread - 4")
        // {
        //     std::cout<<td->threadname<<" 触发了异常"<<std::endl;
        //     a/=0;
        // }
        sleep(1);
    }
}


int main()
{
    std::vector<pthread_t> pthreads;
    for(int i = 0;i<threadnum;i++)
    {
        pthread_t tid;
        char threadname[64];
        snprintf(threadname,sizeof(threadname),"%s - %lu","thread",i+1);
        ThreadData * td = new ThreadData(threadname,(uint64_t)time(nullptr),Print);
        pthread_create(&tid,nullptr,ThreadRoutine,td);
        pthreads.push_back(tid);
        sleep(1);
        
    }

    for(const auto &tid : pthreads)
    {
        std::cout<<"thread id : "<<tid<<std::endl;
    }
    
    while(true)
    {
        std::cout<<"I am a main pthread"<<std::endl;
        sleep(1);
    }
    return 0;
}

在上述代码基础上添加一段打印tid的代码,运行结果:

那么这些thread id这么长的一段数字到底是什么意思呢,为了更清晰的理解这串数字,我们可以将其16进制打印出来。

我们再来认识一个线程获取自身的id的一个接口:

然后再写一段代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

std::string toHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void * threadRoutine(void * arg)
{
    usleep(1000);
    std::string name = static_cast<const char *>(arg);
    while(true)
    {
        std::cout<<"new thread is running, thread name: "<<name<<" thread id : "<<toHex(pthread_self())<<std::endl;
        sleep(1); 
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");

    while(true)
    {
        std::cout<<"main thread, sub thread: "<<tid<<" main thread id : "<<toHex(pthread_self())<<std::endl;
        sleep(1);
    }
    return 0;
}

运行结果:

数字有这么长一串是因为我用的是64位系统的,然后这更像是一个地址,其实thread id的本质就是一个地址。

线程既然可以创建那么我们如何把它终止呢?我们继续来认识一个线程终止的接口:

需要通过传一个指针让线程终止,是不是呢?我们通过将新线程运行的函数修改成如下:

void * threadRoutine(void * arg)
{
    usleep(1000);
    std::string name = static_cast<const char *>(arg);
    int cnt = 5;
    while(cnt--)
    {
        std::cout<<"new thread is running, thread name: "<<name<<" thread id : "<<toHex(pthread_self())<<std::endl;
        sleep(1); 
    }
    //return nullptr;
    pthread_exit(nullptr);
}

运行结果:

我们发现运行到后面就剩一个主线程了,说明新线程退出了。

下面我们来谈谈关于线程返回值的问题:

1.我们要获取返回值该如何获取呢?

2.线程在本质上其实就是Linux操作系统下的轻量级进程,那么当轻量级进程终止了,它的PCB会不会立即释放呢?

3.线程默认要被等待吗?是的,线程退出,没有等待会导致类似进程的僵尸问题。线程退出时如何获取新线程的返回值呢?

首先我们先认识一个接口:

参数是thread id 和 一个二级指针,只要等待成功了,返回值就是0.

下面我们用这个线程等待的接口来进行测试:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

std::string toHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void * threadRoutine(void * arg)
{
    usleep(1000);
    std::string name = static_cast<const char *>(arg);
    int cnt = 5;
    while(cnt--)
    {
        std::cout<<"new thread is running, thread name: "<<name<<" thread id : "<<toHex(pthread_self())<<std::endl;
        sleep(1); 
    }
    return nullptr;
    // pthread_exit(nullptr);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");

    std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    sleep(10);

    int n = pthread_join(tid,nullptr);
    std::cout<<"main thread done"<<"n : "<<n<<std::endl;

    sleep(5);
    
    return 0;
}

运行结果:

我们看到最后的返回值是0,所以表示等待成功了。

如果我们要得到新线程的返回值,那么我们得到的也应该是void *,所以为了得到一个void*就需要传入一个void * * 。

那么下面我们来修改一下代码来证明一下:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

std::string toHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void * threadRoutine(void * arg)
{
    usleep(1000);
    std::string name = static_cast<const char *>(arg);
    int cnt = 5;
    while(cnt--)
    {
        std::cout<<"new thread is running, thread name: "<<name<<" thread id : "<<toHex(pthread_self())<<std::endl;
        sleep(1); 
    }

    return (void*)"thread-1 done";
    // return nullptr;
    // pthread_exit(nullptr);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");

    std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    void * ret = nullptr;
    int n = pthread_join(tid,&ret);
    std::cout<<"main thread done "<<"n : "<<n<<std::endl;
    
    std::cout<<"main get new thread return : "<< (const char *)ret<<std::endl;

    return 0;
}

运行结果:

果然获取到了对应的返回值。

线程是可以被设置为分离状态的(可以理解为 该线程不受主线程的管控了),线程默认情况下是joinable的.

下面我们用代码来实现线程设置为分离状态,出现的现象也就是主线程等待新线程的返回值不成功:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

std::string toHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void * threadRoutine(void * arg)
{
    pthread_detach(pthread_self());
    // usleep(1000);
    // std::string name = static_cast<const char *>(arg);
    int cnt = 5;
    while(cnt--)
    {
        std::cout<<"thread is running..."<<std::endl;
        sleep(1); 
    }

    // return (void*)"thread-1 done";
    return nullptr;
    // pthread_exit(nullptr);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");
     sleep(1);
    // std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    int n = pthread_join(tid,nullptr);
    std::cout<<"main thread done "<<"n : "<<n<<std::endl;

    return 0;
}

运行结果:

我们发现返回值n不在是0了,说明等待失败了。

当然也可以这样分离:

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");
    sleep(1);

    pthread_detach(tid);
    // std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    int n = pthread_join(tid,nullptr);
    std::cout<<"main thread done "<<"n : "<<n<<std::endl;

    return 0;
}

把pthread_detach()这段代码放到这个位置,用主线程与新线程分离。

如果不分离,运行结果是这样的:

其实我们还有一种可以让线程退出的方式,那就是线程取消掉,用到接口pthread_cancel

下面我们运用这个接口来进行测试:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<functional>
#include<time.h>
#include<vector>

std::string toHex(pthread_t tid)
{
    char id[64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void * threadRoutine(void * arg)
{
    //pthread_detach(pthread_self());
    // usleep(1000);
    // std::string name = static_cast<const char *>(arg);
    int cnt = 5;
    while(cnt--)
    {
        std::cout<<"thread is running..."<<std::endl;
        sleep(1); 
    }

    // return (void*)"thread-1 done";
    return nullptr;
    // pthread_exit(nullptr);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");
    sleep(5);

    //pthread_detach(tid);
    // std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    int n = pthread_cancel(tid);
    std::cout<<"main thread cancel done, "<<"n : "<<n<<std::endl;

    void * ret = nullptr;
    n = pthread_join(tid,&ret);
    std::cout<<"main thread join done,"<<" n : "<<n<<"thread return : "<<(int64_t)ret<<std::endl;
    return 0;
}

运行结果:

说明我们线程取消成功了,同时被join了,退出码是-1.

我们把线程分离加上去:


int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,(void *)"thread-1");
    sleep(5);

    pthread_detach(tid);
    // std::cout<<"main thread,  "<<" main thread id : "<<toHex(pthread_self())<<std::endl;
    int n = pthread_cancel(tid);
    std::cout<<"main thread cancel done, "<<"n : "<<n<<std::endl;

    void * ret = nullptr;
    n = pthread_join(tid,&ret);
    std::cout<<"main thread join done,"<<" n : "<<n<<"thread return : "<<(int64_t)ret<<std::endl;
    return 0;
}

运行结果就是:

线程被分离了也是可以取消的,但是不能被join.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024.3.9|第十五届蓝桥杯模拟赛(第三期)

2024.3.9|十五届蓝桥杯模拟赛&#xff08;第三期&#xff09; 第一题 第二题 第三题 第四题 第五题 第六题 第七题 第八题 第九题 第十题 心有猛虎&#xff0c;细嗅蔷薇。你好朋友&#xff0c;这里是锅巴的C\C学习笔记&#xff0c;常言道&#xff0c;不积跬步无以至千里&…

sql-bypass通关笔记(作业)

靶场环境的搭建&#xff1a; 首先安装dvwa靶场&#xff1b;因为要用到dvwa的数据库然后将该靶场放到phpstudy的www目录下创建一个网站指向该文件夹通过访问文件夹中php文件的形式进行闯关具体压缩包我放到最后的资料里了 index1.php通关笔记 知识点&#xff1a; 空格过滤 #…

51单片机—直流电机

1.元件介绍 2.驱动电路 3.电机调速 一般会保证一个周期的时间是一样的 应用&#xff1a; 1.LED呼吸灯 #include <REGX52.H>sbit LEDP2^0;void Delay(unsigned int t) {while(t--); } void main() {unsigned char Time,i;while(1){for(Time0;Time<100;Time){for(i0;…

MateBook 14s 2023款 集显 触屏(HKFG-16)原厂Win11系统

HUAWEI华为MateBook14s笔记本电脑2023款原装Windows11&#xff0c;恢复出厂开箱状态系统下载 适用型号&#xff1a;HKFG-XX、HKFG-16、HKFG-32 链接&#xff1a;https://pan.baidu.com/s/1GBPLwucRiIup539Ms2ue0w?pwdfm41 提取码&#xff1a;fm41 原厂系统自带所有驱动、…

python失物招领系统-安卓-flask-django-nodejs-php

对于本失物招领 的设计来说&#xff0c; 它是应用mysql数据库、安卓等技术动态编程以及数据库进行努力学习和大量实践&#xff0c;并运用到了 建设中在整个系统的设计当中&#xff0c;具体根据网上失物招领的现状来进行开发的&#xff0c;具体根据用户需求实现网上失物招领网络…

基于python的在线学习与推荐系统

技术&#xff1a;pythonmysqlvue 一、系统背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样的大…

【计算机视觉】三、图像处理——实验:图像去模糊和去噪、提取边缘特征

文章目录 0. 实验环境1. 理论基础1.1 滤波器&#xff08;卷积核&#xff09;1.2 PyTorch:卷积操作 2. 图像处理2.1 图像读取2.2 查看通道2.3 图像处理 3. 图像去模糊4. 图像去噪4.1 添加随机噪点4.2 图像去噪 0. 实验环境 本实验使用了PyTorch深度学习框架&#xff0c;相关操作…

STM32编写ADC功能,实现单路测量电压值(OLED显示)

先来看看本次实验的结果吧&#xff1a;stm32点电压测量范围为0-3.3V&#xff0c;数值为&#xff1a;0-4095 来看看这个工程的文件布局吧&#xff1a; 实现ADC功能总共分为六步&#xff1a; 第一步&#xff1a;开始RCC时钟&#xff0c;包括ADC和GPIO的时钟&#xff0c;ADCCLK的…

【Linux】——进程地址空间 Linux2.6内核进程调度队列

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、环境变量的补充 二、进程空间的地址 2.1、程序地址空间 2.2、研究背景 2.3、程序地址空间 来段代码感受一下 2.4、进程地址空间 2.5、如何…

【计算机网络】IP 协议

网络层IP协议 一、认识 IP 地址二、IP 协议报头格式三、网段划分1. 初识子网划分2. 理解子网划分3. 子网掩码4. 特殊的 IP 地址5. IP 地址的数量限制6. 私有 IP 地址和公网 IP 地址7. 理解全球网络&#xff08;1&#xff09;理解公网&#xff08;2&#xff09;理解私网&#xf…

一文搞懂IP

IP 1. 基本介绍2. IP地址定义3. IP地址分类4. 子网掩码5. 全局地址与私有地址 1. 基本介绍 TCP/IP 协议的心脏是网络层&#xff0c;主要“实现节点之间的通信”&#xff0c;即“点对点(end-to-end)通信”。 网络层包含IP(Internet Protocol)及DNS&#xff08;Domain Name Sys…

代码随想录算法训练营 DAY 14 | 二叉树的递归遍历和迭代遍历

二叉树基础 种类 满二叉树&#xff1a;深度为k&#xff0c;有2^k-1个节点的二叉树 完全二叉树&#xff1a;除了最底层可能没满&#xff0c;且都在靠左侧 优先级队列其实是一个堆&#xff0c;堆就是一棵完全二叉树&#xff0c;同时保证父子节点的顺序关系。 二叉搜索树&…

redis学习-String类型的命令介绍以及特殊情况分析

目录 1. set key value 2. get key 3. append key string 4. strlen key 5. incr key 和 decr key 6. incrby key num 和 decrby key num 7. getrange key start end 8. setrange key start string 9. setex key time value 10. setnx key value 11. mset key1 val…

AI论文速读 | UniST:提示赋能通用模型用于城市时空预测

本文是时空领域的统一模型——UniST&#xff0c;无独有偶&#xff0c;时序有个统一模型新工作——UniTS&#xff0c;感兴趣的读者也可以阅读今天发布的另外一条。 论文标题&#xff1a;UniST: A Prompt-Empowered Universal Model for Urban Spatio-Temporal Prediction 作者&…

Linux进程通信补充——System V通信

System V进程通信 ​ System V是一个单独设计的内核模块&#xff1b; ​ 这套标准的设计不符合Linux下一切皆文件的思想&#xff0c;尽管隶属于文件部分&#xff0c;但是已经是一个独立的模块&#xff0c;并且shmid与文件描述符之间的兼容性做的并不好&#xff0c;网络通信使…

Linux入门级别命令(下载远程连接工具)

$pwd&#xff08;当前所在位置&#xff0c;显示打印当前工作目录&#xff09;$mkdir 创建目录$cd dir 换个位置&#xff08;进入某一个目录&#xff09;$cd 什么都不加回到最开始的目录$ls当前目录位置下的文件信息&#xff08;列出当前所在位置下有哪些东西&#xff09;$mv移动…

SpringBoot与SpringCloud的版本对应详细版

在实际开发过程中&#xff0c;我们需要详细到一一对应的版本关系&#xff1a;Spring 官方对应版本地址&#xff1a; (https://start.spring.io/actuator/info)&#xff0c;建议用firefox浏览器打开&#xff0c;你会看见格式化好了json信息&#xff1a; 手动记录一些经本人实际…

导师评价网最全整合版

目录 简介 下载地址 打开index.html即可查询。 简介 下载地址 链接&#xff1a;https://pan.baidu.com/s/1QU6PgoF3Fi8NqtaGHzfAuw?pwdoc0s 提取码&#xff1a;oc0s --来自百度网盘超级会员V5的分享

【Frida】06_分析扫雷游戏的数据,显示地雷位置

&#x1f6eb; 系列文章导航 【Frida】 00_简单介绍和使用 https://blog.csdn.net/kinghzking/article/details/123225580【Frida】 01_食用指南 https://blog.csdn.net/kinghzking/article/details/126849567【Frida】 03_初识frida-node https://blog.csdn.net/kinghzking/ar…

python基于django的高校迎新系统 flask新生报到系统

系统的登录界面和业务逻辑简洁明了&#xff0c;采用一般的界面窗口来登录界面,整个系统更加人性化&#xff0c;用户操作更加简洁方便。本系统在操作和管理上比较容易&#xff0c;还具有很好的交互性等特点&#xff0c;在操作上是非常简单的。因此&#xff0c;本系统可以进行设计…