初谈Linux多线程--线程控制

news2024/11/16 9:30:04

在这里插入图片描述

文章目录

  • 线程的概述
    • 理解线程
    • Linux中的线程
    • 重新理解的进程
    • Windows的线程
    • 线程的优点
    • 线程的缺点
      • 理解线程调度成本低
    • 进程VS线程
  • 线程控制
    • 创建线程
    • 等待线程
    • 线程函数传参
    • 线程的返回值
      • 新线程的返回值
      • 新线程返回值错误
      • 返回值为类对象
    • 创建多线程
    • 线程的终止
    • 线程的分离pthread_detach

线程的概述

理解线程

线程:线程是在进程内部(PCB)运行,是CPU内部调度的基本单位。

在这里插入图片描述


Linux中的线程

在Linux中,线程执行的是进程代码的一部分,也就是说,线程是进程的实体,可以看作是进程内的一个执行单元,我们将这些不同的执行单元称之为轻量级进程,不同线程之间可以通过共享内存来进行通信。
在这里插入图片描述

这里可以举一个例子,要想有一个幸福的家庭,家庭成员就要执行好自己的事情,这样才能成就一个幸福的家庭。

PCB大部分属性都包含了线程的属性,复用PCB,用PCB统一表示执行流,这样线程就不需要给线程单独设计数据结构和调度算法。这样维护的数据结构和调度算法的代码就少。

在CPU看来,不需要区分task_struct是进程还是线程,都叫做执行流。Linux执行流都是轻量级进程。Linux使用进程模拟线程。


重新理解的进程

以前我们学习的进程=内核数据结构+进程的数据代码,这是我们之前理解的。以前我们强调的是代码的执行流,内部只有一个执行流,而现在我们的内部有多个执行流。

因此以内核观点,给进程重新下一个定义:承担分配系统资源的基本实体。


Windows的线程

操作系统设计一个线程,需要新建、创建、销毁、管理等,线程要不要和进程产生关系呢?
操作系统需要对线程进行管理,先描述(struct tcb),再组织。

struct tcb
{
	//线程的id,优先级,状态,上下文,连接属性...
}

Windows提供了真实的线程控制块,不是复用PCB属性,这样维护的数据结构和调度算法的代码就高。


线程的优点

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

线程的缺点

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
    线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
    同步和调度开销,而可用的资源不变。

  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
    不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高
    编写与调试一个多线程程序比单线程程序困难得多


理解线程调度成本低

线程在同一个进程内部共享相同的地址空间和大部分资源,因此在创建、销毁或者切换线程时,无需像进程那样复制和维护额外的资源。这减少了资源管理的开销。

硬件角度:线程在同一核心上连续执行时,由于其数据可能保持在该核心的缓存中,可以更高效地利用缓存,从而提高数据访问的速度。这可以减少因缓存未命中而引起的额外延迟,进而降低线程切换的成本。


进程VS线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:
    线程ID
    一组寄存器:硬件上下文数据,体现出线程可以动态运行
    :线程在运行的时候,本质是运行函数,会形成临时变量,临时变量会被保存在每个线程的栈区
    errno
    信号屏蔽字
    调度优先级

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程
中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id。

进程和线程的关系:

在这里插入图片描述

线程控制

在Linux系统中没有线程,只有轻量级进程,这个轻量级进程实际上就是线程,因为没有单独设计TCB。因此Linux操作系统不会直接给我们提供线程的系统调用,只会提供创建轻量级进程的系统调用接口。Linux系统存在一个中间软件层,有一个pthred库,是自带的原生线程库,对该轻量级进程接口进行封装,按照线程的接口提供给用户。所以说,Linux是用户级线程,Windows是内核级线程。

创建线程

创建一个进程的函数接口:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);


Compile and link with -pthread.

在这里插入图片描述

参数:
thread:返回线程ID,是一个输出型参数
attr:设置线程的属性,attrNULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码,内容未定义。

等待线程

等待线程的函数接口:

#include <pthread.h>

int pthread_join(pthread_t thread, void**retval);

Compile and link with -pthread.

参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

代码示例:

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

void *threadRun(void *args)
{
    int cnt=10;
    while(cnt)
    {
        std::cout<<"new thread run ...,cnt:"<<cnt--<<std::endl;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,threadRun,(void*)"thread 1");
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::cout<<"main thread join begin..."<<std::endl;

    n=pthread_join(tid,nullptr); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess"<<std::endl;
    }

    return 0;
}

在这里插入图片描述

上述代码中,子线程完成之后,主线程才退出。

这里的tid是什么?
tid是线程库中的一个地址,是虚拟地址。
在这里插入图片描述

线程函数传参

主线程数据可传递给新线程:
代码演示:

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

void *threadRun(void *args)
{
    //std::string name=(const char*)args;
    int a=*(int*)args;
    int cnt=10;
    while(cnt)
    {
        std::cout<<a<<" run ...,cnt:"<<cnt--<<std::endl;
        sleep(1);
    }

    return nullptr;
}

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

int main()
{
    pthread_t tid;

    int a=100;

    int n=pthread_create(&tid,nullptr,threadRun,(void*)&a);
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::string tid_str=PrintToHex(tid);
    std::cout<<"tid:"<<tid_str<<std::endl;

    std::cout<<"main thread join begin..."<<std::endl;

    n=pthread_join(tid,nullptr); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess"<<std::endl;
    }

    return 0;      
}

在这里插入图片描述


这里传递参数可以是任意类型,可以传递类对象,在传递类对象时,传递的是地址。也就是说,我们可以传递多个参数。

代码示例:

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

class ThreadData
{
public:
    std::string name;
    int num;
};

void *threadRun(void *args)
{
    //std::string name=(const char*)args;
    //int a=*(int*)args;
    ThreadData *td=static_cast<ThreadData*>(args);//(ThreadData*)args  
    int cnt=10;
    while(cnt)
    {
        std::cout<<td->name<<" run ...,num is:"<<td->num<<",cnt:"<<cnt--<<std::endl;
        sleep(1);
    }

    return nullptr;
}

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

int main()
{
    pthread_t tid;

    ThreadData td;
    td.name="thread-1";
    td.num=1;

    int n=pthread_create(&tid,nullptr,threadRun,(void*)&td);
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::string tid_str=PrintToHex(tid);
    std::cout<<"tid:"<<tid_str<<std::endl;

    std::cout<<"main thread join begin..."<<std::endl;

    n=pthread_join(tid,nullptr); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess"<<std::endl;
    }

    return 0;      
}

在这里插入图片描述

上述做法不推荐,td在主线程中,破坏了主线程的完整性和独立性。td 是一个局部变量,其生命周期仅限于 main 函数。一旦 main 函数结束,td 将会被销毁,此时新线程仍然可能在尝试访问已经无效的内存,从而导致未定义行为。另外,如果此时还有一个线程,使用的也是td访问的也是主函数中td变量,那这两个线程中如果其中一个线程对td进行修改,那么就会影响另一个线程。

换一种做法:

在主线程中申请一个堆上的空间,把堆上的地址拷贝给线程,此时对空间就交给了新线程。如果还有第二个线程,那就重新new一个堆上的空间,交给新的线程。这样每一个线程都有属于自己的堆空间。另外,在线程内部处理完后需要delete

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

class ThreadData
{
public:
    std::string name;
    int num;
};

void *threadRun(void *args)
{
    //std::string name=(const char*)args;
    //int a=*(int*)args;
    ThreadData *td=static_cast<ThreadData*>(args);//(ThreadData*)args  
    int cnt=10;
    while(cnt)
    {
        std::cout<<td->name<<" run ...,num is:"<<td->num<<",cnt:"<<cnt--<<std::endl;
        sleep(1);
    }

    delete td;
    return nullptr;
}

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

int main()
{
    pthread_t tid;

    ThreadData *td=new ThreadData(); //在堆上申请空间
    td->name="thread-1";
    td->num=1;

    int n=pthread_create(&tid,nullptr,threadRun,td);
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::string tid_str=PrintToHex(tid);
    std::cout<<"tid:"<<tid_str<<std::endl;

    std::cout<<"main thread join begin..."<<std::endl;

    n=pthread_join(tid,nullptr); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess"<<std::endl;
    }

    return 0;      
}

在这里插入图片描述

线程的返回值

新线程的返回值

线程的返回值是void*,那么主线程的函数就必须使用void **的参数返回,int pthread_join(pthread_t thread, void**retval)void**retval是一个输出型参数,需要一个void*类型的变量即void* retvoid* ret是一个指针类型的变量,有对应的空间,在Linux中占8个字节,在传递的时候是传递&ret ,是将地址传过去。

主线程拿到新线程的退出信息:
在这里插入图片描述

在这里插入图片描述

代码:

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

class ThreadData
{
public:
    std::string name;
    int num;
};

void *threadRun(void *args)
{
    //std::string name=(const char*)args;
    //int a=*(int*)args;
    ThreadData *td=static_cast<ThreadData*>(args);//(ThreadData*)args  
    int cnt=10;
    while(cnt)
    {
        std::cout<<td->name<<" run ...,num is:"<<td->num<<",cnt:"<<cnt--<<std::endl;
        sleep(1);
    }

    delete td;
    return (void*)111;
}

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

int main()
{
    pthread_t tid;

    ThreadData *td=new ThreadData(); //在堆上申请空间
    td->name="thread-1";
    td->num=1;

    int n=pthread_create(&tid,nullptr,threadRun,td);
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::string tid_str=PrintToHex(tid);
    std::cout<<"tid:"<<tid_str<<std::endl;

    std::cout<<"main thread join begin..."<<std::endl;

    void* code=nullptr;//开辟了空间
    n=pthread_join(tid,&code); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess,new thread exit code:"<<(uint64_t)code<<std::endl;
    }

    return 0;      
}

在这里插入图片描述

可以根据退出信息,就可以判断新线程是否完成对应的任务。

新线程返回值错误

在这里插入图片描述

上述代码故意让新线程出现野指针,是的新线程出现错误。

在这里插入图片描述
上述代码时主线程,新线程出错后让主线程等100s后再退出。

代码演示结果:

在这里插入图片描述

主线程没有等100s后退出,而是在新的进程异常后直接退出。

线程的返回值只有正确时的返回值,一旦出现异常,线程就会崩溃,线程出现异常就会发信号给进程,进程就会被杀掉,即使进程里面有多个线程,里面有一个线程出现错误,整个进程都会被杀掉。
因此线程的在退出的时候只需要考虑正确的返回,不考虑异常,一旦异常,整个进程都会崩溃,包括主线程。

返回值为类对象

主线程创建并启动了一个新的线程,通过 pthread_create 和 pthread_join 实现了线程的创建和等待。
在新线程中,通过 ThreadData 类传递了操作数,并在 threadRun 函数中计算了加法操作的结果。
最后,主线程将计算结果打印出来,展示了线程之间数据的传递和简单的同步操作。

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

class ThreadData
{
public:
    int Excute()
    {
        return x+y;
    }
public:
    std::string name;
    int x;
    int y;
};

class ThreadResult
{
public:
    std::string print()
    {
        return std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(result);
    }
public:
    int x;
    int y;
    int result;
};

void *threadRun(void *args)
{
    //std::string name=(const char*)args;
    //int a=*(int*)args;
    ThreadData *td=static_cast<ThreadData*>(args);//(ThreadData*)args  
    ThreadResult *result=new ThreadResult();
    int cnt=10;
    while(cnt)
    {
        sleep(1);

        std::cout<<td->name<<" run ..."<<",cnt:"<<cnt--<<std::endl;
        result->result=td->Excute();
        result->x=td->x;
        result->y=td->y;
        break;

    }
    delete td;
    return (void*)result;
}

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

int main()
{
    pthread_t tid;

    ThreadData *td=new ThreadData(); //在堆上申请空间
    td->name="thread-1";
    td->x=10;
    td->y=20;

    int n=pthread_create(&tid,nullptr,threadRun,td);
    if(n!=0)
    {
        std::cerr<<"create thread error"<<std::endl;
        return 1;
    }

    std::string tid_str=PrintToHex(tid);
    std::cout<<"tid:"<<tid_str<<std::endl;

    std::cout<<"main thread join begin..."<<std::endl;

    ThreadResult* result=nullptr;//开辟了空间
    n=pthread_join(tid,(void**)&result); //保证main线程最后退出
    if(n==0)
    {
        std::cout<<"main thread wait sucess,new thread exit code:"<<result->print()<<std::endl;
    }



    return 0;      
}

在这里插入图片描述

创建多线程

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

using namespace std;

const int num=10;

void *threadrun(void* args)
{
    string name=static_cast<const char*>(args);
    while(true)
    {
        cout<<name<<"is running"<<endl;
        sleep(1);
        break;
    }
    return args;
}

string PrintToHex(pthread_t &tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

int main()
{
    vector<pthread_t> tids;

    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[128];
        snprintf(name,128,"thread-%d",i+1);//名字
        pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字

        //保存所有线程的id信息
        tids.push_back(tid);
    }

    //等待
    for(auto tid:tids)
    {
        void *name=nullptr;
        pthread_join(tid,&name);
        //cout<<PrintToHex(tid)<<" quit..."<<endl;
        cout<<(const char*)name<<" quit..."<<endl;
        delete (const char*)name; 
    }

    sleep(1);    
    return 0;
}

在主函数中,首先创建一个 vector 容器 tids,用于存储所有线程的 ID。使用for循环创建num个线程。在第一个for循环中,配一个新的字符数组name来存储线程名字,使用 snprintf 将线程名字格式化为 thread-i 的形式,调用 pthread_create 函数创建线程,传递线程名字作为参数,将每个线程的 ID 存入 tids 向量。第二个for循环中,等待所有进程结束,使用 pthread_join 函数等待线程结束,获取线程返回的 name,并输出线程名字加上 “quit…”,删除线程名字的内存,以防止内存泄漏。

运行结果:
在这里插入图片描述

线程的终止

新的线程如何终止?函数return

主线程如何终止?
主线程对应的main函数结束,那么主线程结束,表示整个进程结束。此时如果还有新线程还没有结束,那么新线程就会被提前终止,因此有线程等待,是的主线程最后结束。

使用线程终止函数:

#include <pthread.h>

void pthread_exit(void *retval);

Compile and link with -pthread.

代码测试:

#include<iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

using namespace std;

const int num=10;

void *threadrun(void* args)
{
    string name=static_cast<const char*>(args);
    while(true)
    {
        cout<<name<<"is running"<<endl;
        sleep(3);
        break;
    }
    //exit(1);//exit是进程退出,不能终止线程
    //return args;
    pthread_exit(args);
}

string PrintToHex(pthread_t &tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

int main()
{
    vector<pthread_t> tids;

    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[128];
        snprintf(name,128,"thread-%d",i+1);//名字
        pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字

        //保存所有线程的id信息
        //tids.push_back(tid);
        tids.emplace_back(tid);
    }

    //等待
    for(auto tid:tids)
    {
        void *name=nullptr;
        pthread_join(tid,&name);
        //cout<<PrintToHex(tid)<<" quit..."<<endl;
        cout<<(const char*)name<<" quit..."<<endl;
        delete (const char*)name; 
    }

    sleep(1);    
    return 0;
}

在这里插入图片描述


还有一个终止线程函数:

#include <pthread.h>

int pthread_cancel(pthread_t thread);

Compile and link with -pthread.

这个实际上是取消一个线程,发送一个取消请求给线程,一般都是使用主线程取消其他线程。

代码示例:

#include<iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

using namespace std;

const int num=10;

void *threadrun(void* args)
{
    string name=static_cast<const char*>(args);
    while(true)
    {
        cout<<name<<"is running"<<endl;
        sleep(1);
        //break;
    }
    //exit(1);//exit是进程退出,不能终止线程
    //return args;
    pthread_exit(args);
}

string PrintToHex(pthread_t &tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

int main()
{
    vector<pthread_t> tids;

    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[128];
        snprintf(name,128,"thread-%d",i+1);//名字
        pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字

        //保存所有线程的id信息
        //tids.push_back(tid);
        tids.emplace_back(tid);
    }

    sleep(5);
    //等待
    for(auto tid:tids)
    {
        pthread_cancel(tid);//取消
        cout<<"cancel:"<<PrintToHex(tid)<<endl;
        void *result=nullptr;  //现成被取消,被取消的线程的返回结果是-1
        pthread_join(tid,&result);
        //cout<<PrintToHex(tid)<<" quit..."<<endl;
        cout<<(long long int)result<<" quit..."<<endl;
        //delete (const char*)name; 
    }

    sleep(1);    
    return 0;
}

在这里插入图片描述

线程的分离pthread_detach

线程的分离函数接口:

#include <pthread.h>

int pthread_detach(pthread_t thread);

Compile and link with -pthread.

如果一个线程被创建,默认是需要joinable的,必须被join;如果一个线程被分离,线程的工作状态是分离状态,不能被join

进程分离:一个线程依旧属于线程,但是不需要被主线程等待。

#include<iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

using namespace std;

const int num=10;

void *threadrun(void* args)
{
    pthread_detach(pthread_self());
    string name=static_cast<const char*>(args);
    while(true)
    {
        cout<<name<<"is running"<<endl;
        sleep(3);
        break;
    }
    //exit(1);//exit是进程退出,不能终止线程
    //return args;
    pthread_exit(args);
}

string PrintToHex(pthread_t &tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

int main()
{
    vector<pthread_t> tids;

    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[128];
        snprintf(name,128,"thread-%d",i+1);//名字
        pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字

        //保存所有线程的id信息
        //tids.push_back(tid);
        tids.emplace_back(tid);
    }

    //等待
    for(auto tid:tids)
    {
        void *result=nullptr;  //现成被取消,被取消的线程的返回结果是-1
        int n=pthread_join(tid,&result);
        //cout<<PrintToHex(tid)<<" quit..."<<endl;
        cout<<(long long int)result<<" quit...,n:"<<n<<endl;
        //delete (const char*)name; 
    }

    sleep(100);    
    return 0;
}

在这里插入图片描述

上述代码表示:主线程创建一个新线程,但是新的线程被分离了,没有被等待,因此n返回为22。.

如果被分离的线程出异常依然会影响进程,会导致进程直接崩溃。

在这里插入图片描述

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

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

相关文章

AIExpo2024奖项申报正式启动,三大奖项为你闪耀

由新一代人工智能产业技术创新战略联盟、苏州市人工智能协同创新中心联合主办&#xff0c;苏州启智创新科技有限公司、苏州工业园区科技发展有限公司共同承办的第六届全球人工智能产品应用博览会&#xff08;以下简称“智博会”&#xff09;将于2024年9月11-12日在苏州国际博览…

二叉搜索树,Map,Set,LeetCode刷题

二叉搜索树&#xff0c;Map&#xff0c;Set 1. 二叉搜索树2. 二叉搜索树的模拟实现1.1 插入操作1.2 查找操作1.3 删除操作 3. 二叉搜索树时间复杂度分析4. TreeMap和TreeSet5. Map5.1 Map的常用方法5.2 Map.Entry<K,V> 6. Set6.1 Set的常用方法 LeetCode刷题1. 二叉搜索树…

Total Eclipse 挑战赛:在以太坊首个 SVM L2 上开发应用

摘要&#xff1a;Eclipse 基金会宣布了其首届黑客马拉松计划&#xff0c;即"The Total Eclipse Challenge"&#xff0c;作为一场独一无二的黑客松活动 &#xff0c;邀请了优秀的开发者们在链上开发创新的应用。 "The Total Eclipse 挑战赛" 是一项为期两周…

如何更好地做出判断?

笛卡尔有句名言&#xff1a;无法下判断的人&#xff0c;不是欲望太奢侈&#xff0c;就是觉悟还不够。 这里说的判断&#xff0c;其实也就是选择。我们人生面临着各种维度的选择&#xff0c;大到人生方向&#xff0c;小到一顿饭吃什么&#xff0c;可以说&#xff0c;选择伴随着我…

全志T113i移植LAN8720指南

1、根据硬件修改设备树 gmac0_pins_a: gmac0 {allwinner,pins "PG0", "PG1", "PG2", "PG3", "PG4","PG5", "PG11", "PG12", "PG13", "PG14", "PG15";allwin…

vue3+TS+nest+mysql实现网站访问统计系统

网站采用了vue3tsnestmysql的技术选型&#xff0c;目前初步实现第一版开发 访问地址&#xff1a;点我进入网站 网站访问统计 主要通过插入script来调用上传接口来实现数据统计,目前仅存储了用户的ip和访问时间&#xff0c;后期也可根据ip来获取用户的城市信息 async pageUplo…

探索TinyDB的轻量级魅力:Python中的微型数据库

文章目录 探索TinyDB的轻量级魅力&#xff1a;Python中的微型数据库背景&#xff1a;为何选择TinyDB&#xff1f;什么是TinyDB&#xff1f;如何安装TinyDB&#xff1f;5个简单的库函数使用方法3个场景下的应用实例常见问题与解决方案总结 探索TinyDB的轻量级魅力&#xff1a;Py…

社区志愿服务管理平台小程序/社区志愿者管理系统

获取源码联系方式请查看文章结尾&#x1f345; 摘 要 随着当今网络的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;于是小程序也逐步进入人们的生活&#xff0c;给我们生活或者工作提供了新的方向新的可能。 本毕业设计的内容是设计实现一个基…

芯片解密技术可以应用在哪些领域?

芯片解密技术可以在以下一些领域得到应用&#xff1a; 电子产品维修&#xff1a;对于一些老旧或难以获取原厂技术支持的设备&#xff0c;通过解密芯片获取程序代码可以更有效地进行故障诊断和维修。技术研究和学习&#xff1a;帮助科研机构和技术人员深入了解先进芯片的设计和工…

python绘图 | 横坐标是日期,纵坐标是数值

需求 profit_value [0.084, 0.225, 0.234, 0.264, 0.328] time_stamp [20221111, 20230511, 20230704, 20231212, 20240315] 横坐标是日期&#xff0c;纵坐标是数值&#xff0c;我想绘图的时候&#xff0c;横坐标是按日期格式来 代码 from matplotlib import pyplot as pl…

黄水与酿好酒有啥关系?

执笔 | 敏 敏 编辑 | 古利特 上个世纪&#xff0c;白酒泰斗周恒刚先生曾到四川考察&#xff0c;不禁感慨四川生产浓香型白酒占尽天时、地利、人和&#xff0c;其中“黄水”尤为特别。 “黄水”与酿好酒有何关系&#xff1f;,长江酒道,4分钟 近期&#xff0c;五粮液科研团队联…

word预览方式---插件,vue-office-docx、docx-preview、mammoth.js

提示&#xff1a;word预览方式—插件 文章目录 [TOC](文章目录) 前言一、vue-office-docx二、docx-preview三、mammoth.js总结 前言 word预览 一、vue-office-docx npm install vue-office-docx -S-DofficeDocx.vue <template><div class"preview_box">…

前端面试题整理-HTML

1. 从用户输入 url 到页面展示发生了什么&#xff1f; &#xff08;1&#xff09;首先进行url合法性校验&#xff1a;包括协议域名端口号 &#xff08;2&#xff09;进行强缓存判断&#xff0c;判断本地缓存是否已经存在需要的资源文件&#xff0c;存在直接使用本地缓存即可 &…

无捆绑软件!Windows7纯净版镜像等你来下载!

在电脑操作中&#xff0c;很多用户都喜欢纯净版操作系统&#xff0c;但不知道在哪里可以下载到没有捆绑软件的Windows7纯净版系统&#xff1f;接下来系统之家小编给大家带来最新的Windows7纯净版镜像&#xff0c;这些镜像文件经过优化&#xff0c;不包含捆绑软件&#xff0c;安…

零基础学习【Mybatis】这一篇就够了

Mybatis 查询resultType使用resultMap使用单条件查询多条件查询模糊查询返回主键 动态SQLifchoosesetforeachsql片段 配置文件注解增删改查结果映射 查询 resultType使用 当数据库返回的结果集中的字段和实体类中的属性名一一对应时, resultType可以自动将结果封装到实体中 r…

react高级组件ProForm实现输入框搜索

实现界面 <Col span{12}><ProForm.Itemname"name"label"推荐用户"><AutoCompleteclassName"pro-field pro-field-md"placeholder"请输入用户名"options{NameArr}onSearch{debounce(searchUser, 500)}onSelect{onSelect…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第四篇 嵌入式Linux系统移植篇-第七十三章内核添加网卡驱动

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

有趣的PHP小游戏——猜数字

猜数字 这个游戏会随机生成一个1到100之间的数字&#xff0c;然后你需要猜测这个数字是什么。每次你输入一个数字后&#xff0c;程序会告诉你这个数字是“高了”还是“低了”&#xff0c;直到你猜对为止&#xff01; 使用指南&#xff1a; 代码如下&#xff0c;保存到一个p…

大模型卷入医学图像!PFPs:使用大型视觉和语言模型的提示引导灵活病理分割,用于多样化潜在结果

PS&#xff1a;写在前面&#xff0c;近期感谢很多小伙伴关注到我写的论文解读&#xff0c;我也会持续更新吖~同时希望大家多多支持本人的公主号~ 想了解更多医学图像论文资料请移步公主&#x1f478;号哦~~~后期将持续更新&#xff01;&#xff01; 关注我&#xff0c;让我们一…

【嵌入式】STM3212864点阵屏使用SimpleGUI单色屏接口库——(2)精简字库

一 开源库简介与移植 最近一个项目需要用12864屏幕呈现一组较为复杂的菜单界面&#xff0c;本着不重复造轮子的原则找到了SimpleGUI开源库。 开源地址&#xff1a;SimpleGUI: 一个面向单色显示屏的开源GUI接口库。 SimpleGUI是一款针对单色显示屏设计的接口库。相比于传统的GUI…