Linux操作系统——多线程

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.

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

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

相关文章

先进的人工智能促进更好的业务沟通

提升商务沟通效率&#xff1a;了解SaneBox智能电子邮件管理工具 在现代商业环境中&#xff0c;有效的沟通至关重要。 先进的人工智能技术&#xff0c;特别是在电子邮件管理方面&#xff0c;正在改变企业处理沟通的方式&#xff0c;提高效率和个性化。 下面&#xff0c;我们深入…

【1】THIS IS NOT PROLIFIC PL2303. PLEASE CPMTACT YOUR SUPPLIER

0x01 问题描述 连接COM口连接不上&#xff0c;出现THIS IS NOT PROLIFIC PL2303. PLEASE CPMTACT YOUR SUPPLIER.如下图 0x02 问题解决 1、分析后&#xff0c;因为是windows 11 系统&#xff0c;就装一下驱动。右键单击--》属性 2、更新驱动程序--》浏览我的电脑以查找驱动程序…

电脑中了.[nicetomeetyou@onionmail.org].faust勒索病毒,关于数据恢复

文件后缀变成.[nicetomeetyouonionmail.org].faust了怎么办&#xff1f; 当文件后缀变成.[nicetomeetyouonionmail.org].faust时&#xff0c;通常意味着你的计算机系统已经受到了Faust勒索病毒的攻击。这种病毒会利用先进的加密算法对你的文件进行加密&#xff0c;并将文件后缀…

整合qq邮箱发送

目录 &#x1f32e;1.获取qq授权码 &#x1fad3;2.引入依赖 &#x1f9c8;3.配置mail信息 &#x1f95e;4.创建实现类 &#x1f956;5.测试 1.获取qq授权码 点击开启服务&#xff0c;发送信息获取授权码 2.引入依赖 <!--邮件--><dependency><groupId&…

my2sql —— go语言版binlog解析及闪回工具

之前学习过python语言版binlog解析及闪回工具 MySQL闪回工具简介 及 binlog2sql工具用法 最近听同事介绍有了新的go语言版的my2sql。优点是不需要安装一大堆依赖包&#xff0c;直接可以安装使用&#xff0c;并且解析更高效&#xff0c;试用一下。 一、 下载安装 1. 软件下载 …

学习笔记-华为IPD转型2020:3,IPD的实施

3. IPD的实施 1999 年开始的 IPD 转型是计划中的多个转型项目中的第一个&#xff08;Liu&#xff0c;2015&#xff09;。华为为此次转型成立了一个专门的团队&#xff0c;从大约20人开始&#xff0c;他们是华为第一产业的高层领导。董事会主席孙雅芳是这个团队的负责人。该团…

minio 文件分片上传和下载

文件下载 文件分片下载&#xff1a; 计算文件开始字节位置&#xff0c;计算文件结束字节位置添加头部信息: Range:bytes0-2000表示开始字节位置&#xff0c;200表示结束字节位置 文件上传 生成

1058:求一元二次方程

【题目描述】 利用公式 求一元二次方程axbxc0的根&#xff0c;其中a不等于0。结果要求精确到小数点后5位。 【输入】 输入一行&#xff0c;包含三个浮点数a,b,c&#xff08;它们之间以一个空格分开&#xff09;&#xff0c;分别表示方程axbxc0的系数。 【输出】 输出一行&…

三款.NET代码混淆工具比较分析:ConfuserEx、Obfuscar和Ipa Guard

随着.NET应用程序的广泛应用&#xff0c;保护知识产权和防止逆向工程的需求逐渐增长。本文将详细介绍三款知名的.NET代码混淆工具&#xff1a;ConfuserEx、Obfuscar和Ipa Guard&#xff0c;帮助读者全面了解其功能特点和应用场景。 一、ConfuserEx ConfuserEx是一个.NET代码混…

SpringCloud-Nacos配置管理

在nacos中添加配置文件 如何在nacos中管理配置呢&#xff1f; 然后在弹出的表单中&#xff0c;填写配置信息&#xff1a;如&#xff1a;userservice-dev.yaml 注意&#xff1a;项目的核心配置&#xff0c;需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置…

【解决】Github Pages搭建的网页访问加载缓慢

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、CDN技术简介二、基于Cloudflare平台使用CDN服务&#xff08;一&#xff09;添加网站&#xff08;二&#xff09…

基于springboot小区物业管理系统

摘 要 在网络信息的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;小区物业只能以客户为导向&#xff0c;以产品的持续创新作为小区物业最重要的竞争手段。 采取MyS…

蓝桥杯之简单数论冲刺

文章目录 取模快速幂 取模 这道题目有两个注意点&#xff1a; 1.当你的取模之后刚好等于0的话&#xff0c;后面就不用进行后面的计算 2.if sum detail[i] > q: 这个语句的等号也很重要 import os import sys# 请在此输入您的代码a,b,n map(int,input().split())week a*5 …

[linux]--关于进程概念(上)

目录 冯诺依曼体系结构 操作系统 概念 设计os的目的 定位 如何理解管理 总结 系统调用和库函数概念 进程 描述进程-pcb 组织进程 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程-fork初识 进程状态 阻塞和挂起 Z(zombie)-僵尸进程 冯诺依曼体系结…

spring整合Sentinel

安装sentinel&#xff1a; 执行命令; java -jar sentinel-dashboard-1.8.6.jar 注:sentinel的默认端口为8080&#xff0c;容易出现tomcat的冲突。 当端口冲突&#xff0c;可以使用该指令修改sentinel的端口 默认账号和密码都为sentinel Springcloud整合sentinel&#xff1a;…

IDA反汇编工具详解之菜单栏和基本操作

文章目录 IDA 菜单栏FileEditJumpSearchViewDebuggerOptionsWindows 反汇编操作名称和命名IDA 中的注释基本代码转换修改exe文件并保存 IDA 菜单栏 File File菜单负责项目工程的管理&#xff0c;操作包括:打开项目、关闭项目、保存项目 Edit Edit菜单负责编辑和管理该项目中的…

matlab 混沌系统李雅普洛夫指数谱相图分岔图和庞加莱界面

1、内容简介 略 65-可以交流、咨询、答疑 2、内容说明 matlab 混沌系统李雅普洛夫指数谱相图分岔图和庞加莱界面 混沌系统李雅普洛夫指数谱相图分岔图和庞加莱界面 李雅普洛夫指数谱、相图、分岔图、庞加莱界面 3、仿真分析 略 4、参考论文 略

队列的算法

数组队列 数组的子集 主要方法addLast( )和removeFirst( ) public interface IQueueDesc<E>{void enqueue(E e);E dequeue();E getFront();int getSize();boolean isEmpty(); }public class QueueMyList<E> implements IQueueDesc<E{MyArray<E> a…

深度学习500问——Chapter03:深度学习基础(4)

文章目录 3.7 预训练与微调&#xff08;fine tuning&#xff09; 3.7.1 为什么无监督预训练可以帮助深度学习 3.7.2 什么是模型微调 fine tuning 3.7.3 微调时候网络参数是否更新 3.7.4 fine-tuning模型的三种状态 3.8 权重偏差和初始化 3.8.1 全都初始化为0 3.8.2 全都初始化为…

无需PS技能!2024年在线UI设计工具推荐,让你快速上手!

随着UI设计行业的蓬勃发展&#xff0c;越来越多的设计师进入UI设计&#xff0c;选择一个方便的UI设计工具尤为重要&#xff01;除了传统的UI设计工具外&#xff0c;在线UI设计工具也受到越来越多设计师的青睐。这种不受时间、地点和计算机配置限制的工作模式真的很令人兴奋。在…