【Linux学习】线程详解

news2024/11/23 18:43:15

目录

十八.多线程

        18.1 线程与进程

        18.2 内核视角看待创建线程与进程

        18.3 线程优缺点总结

        线程的优点:

        线程的缺点:

        线程的用途:

        18.4 线程与进程的联系

十九.线程控制

        19.1 POSIX线程库

        19.2 线程创建

        19.3 线程等待

        19.4 线程终止

        19.5 线程分离

        19.6 线程ID及进程地址空间布局


十八.多线程

        18.1 线程与进程

在Linux系统中,线程(Thread)和进程(Process)是两种不同的执行单元,它们之间有几个重要的区别:

  1. 资源共享:线程是属于同一进程的执行单元,它们共享同一进程的资源,如内存空间、文件描述符等。而进程是独立的执行单元,拥有独立的内存空间和资源,进程之间的通信通常需要通过特定的IPC(进程间通信)机制。

  2. 调度线程是由内核进行调度的最小执行单元,因此线程的切换开销通常比进程小。进程的调度由操作系统负责,而线程的调度可以在用户空间内完成,因此线程的切换速度通常更快。

  3. 创建和销毁开销:创建线程比创建进程要快,因为线程共享了父进程的地址空间和其他资源。销毁线程的开销也比销毁进程小。

  4. 独立性:进程是独立的执行环境,一个进程的崩溃通常不会影响其他进程;而线程是共享相同地址空间的执行单元,一个线程的崩溃可能会影响同一进程内的其他线程。

  5. 通信:进程之间的通信需要使用IPC机制,如管道、消息队列、共享内存等;而线程之间可以直接共享全局变量等数据,也可以使用线程间同步机制来进行通信和协调。

总的来说,线程是轻量级的执行单元,更适合用于并发编程和任务并行,可以更高效地利用系统资源;而进程是独立的执行环境,更适合用于资源隔离和单独执行任务。

        18.2 内核视角看待创建线程与进程

之前表示进程所用的结构图:

一个进程的创建实际上伴随着其进程控制块(task_struct)、进程地址空间(mm_struct)以及页表的创建,虚拟地址和物理地址就是通过页表建立映射的

但如果我们在创建“进程”时,只创建task_struct,并要求创建出来的task_struct和父task_struct共享进程地址空间和页表,那么创建的结果就是下面这样的:

此时我们创建的实际上就是四个线程:

  • 其中每一个线程都是当前进程里面的一个执行流,也就是我们常说的“线程是进程内部的一个执行分支”。
  • 同时我们也可以看出,线程在进程内部运行,本质就是线程在进程地址空间内运行,也就是说曾经这个进程申请的所有资源,几乎都是被所有线程共享的。

  如何理解之前的进程?

从内核角度来看:

  • 进程:它是承担分配系统资源的基本实体。
  • 线程:它是CPU调度的基本单位,承担进程资源的一部分的基本实体。

         因此,我们从现在理解进程就不能说一个task_struct结构了,一个进程它包含了进程地址空间、文件相关的属性、各种信号、页表等

        换言之,当我们创建进程时是创建一个task_struct、创建地址空间、维护页表,然后在物理内存当中开辟空间、构建映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等。而我们之前接触到的进程都只有一个task_struct,也就是该进程内部只有一个执行流,即单执行流进程,反之,内部有多个执行流的进程叫做多执行流进程。

CPU如何看待task_struct?

CPU不管有多少条执行流,只看task_struct,你task_struct有1条执行流就是单执行流的task_struct,有多执行流,你就是多执行流的task_struct。如下图: 

单执行流进程被调度:

多执行流进程被调度:

因此,CPU看到的虽说还是task_struct,但已经比传统的进程要更轻量化了。 

Linux下并不存在真正的线程,而是用进程模拟的?

确实,在 Linux 系统中,并没有严格意义上的线程实体,而是使用进程模拟了线程的行为。在 Linux 内核中,线程被实现为轻量级进程(Lightweight Process,LWP),这些轻量级进程与父进程共享了相同的地址空间和其他资源,因此在用户空间看起来就像是在同一个进程中创建了多个线程一样。

Linux 内核提供了一些系统调用(如 clone()),允许创建这样的轻量级进程。这些轻量级进程可以与父进程共享资源,包括地址空间、文件描述符等。由于轻量级进程的实现方式与进程类似,因此内核中并不需要专门的线程管理模块,所有的线程操作都可以通过进程相关的系统调用来完成。

总的来说,Linux 中的线程实际上就是轻量级进程,是通过进程模拟实现的。尽管在用户空间中看起来像是在操作线程,但在内核层面实际上是在操作轻量级进程。因此,将 Linux 中的线程概念加上引号以示区分,是合适的。

        18.3 线程优缺点总结

线程的优点:

  1. 低开销创建和切换: 创建一个新线程的开销比创建一个新进程小得多,线程之间的切换也相对较快。

  2. 资源消耗较少: 线程相比进程占用的资源要少很多,因为线程共享了相同进程的资源,如地址空间、文件等。

  3. 并发性能提升: 能够充分利用多处理器系统的并行性,提高程序的并发性能。

  4. IO操作重叠: 在IO密集型应用中,线程可以同时等待不同的IO操作,从而提高了IO操作的效率。

  5. 任务并行处理: 能够将任务分解成多个线程来同时执行,提高了计算密集型应用的执行效率。

线程的缺点:

  1. 性能损失: 当计算密集型线程的数量超过可用处理器的数量时,可能会造成性能损失,因为额外的同步和调度开销会增加。

  2. 健壮性降低: 编写多线程程序需要更加全面和深入的考虑,线程之间的同步和数据共享容易引发健壮性问题。

  3. 缺乏访问控制: 线程是进程内的执行分支,因此在多线程程序中,访问控制变得更加困难,可能会影响进程内部的资源访问。

  4. 编程难度提高: 编写和调试多线程程序比单线程程序更加困难,因为需要考虑线程同步、数据共享等问题。

线程的用途:

  1. 提高CPU密集型程序的执行效率: 合理利用多线程技术,将任务分解成多个线程来并行执行,提高计算密集型应用的执行效率。

  2. 提高IO密集型程序的用户体验: 在IO密集型应用中,使用多线程可以同时进行多个IO操作,提高了程序的响应速度和用户体验。

总的来说,线程是一种强大的并发编程工具,能够提高程序的并发性能和响应速度。但是在使用线程时,需要注意合理设计线程数量和任务分配,避免因过多线程导致的性能损失和健壮性问题。

        18.4 线程与进程的联系

进程是资源分配的基本单位;线程是调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器(存储自己的上下文信息)
  • 栈(每个线程都有临时数据,都需要压栈出栈,各自独立)
  • errno
  • 信号屏蔽字
  • 调度优先级

多线程共享

共享同一地址空间,因此代码段(Text Segment)、数据段(Data Segment)都是共享的:

  • 如果定义一个函数,在各线程中都可以调用;
  • 如果定义一个全局变量,在各线程中都可以访问到;

除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

十九.线程控制

        19.1 POSIX线程库

应用层的原生线程库:

  • pthread 线程库是应用层的原生线程库,意味着它并非由操作系统内核直接提供,而是由第三方实现并被大部分操作系统默认包含。

函数系列:

  • 与线程相关的函数构成了一个完整的系列,大部分函数的名称都以 "pthread_" 打头,例如 pthread_create、pthread_join、pthread_mutex_init 等。

使用方法:

  • 要使用 pthread 线程库,需要包含头文件 <pthread.h>
  • 在链接时,需要使用编译器的 -lpthread 选项来链接 pthread 库。

错误检查:

  • 传统的一些函数在出错时会设置全局变量 errno,并返回 -1 来表示错误,例如 read、write 等。
  • pthread 函数在出错时不会设置全局变量 errno,而是通过返回值来表示错误。通常,成功返回 0,失败返回非零值。
  • pthread 同样提供了线程内的 errno 变量,以支持其他使用 errno 的代码,但是建议优先使用返回值来判断错误,因为读取返回值的开销更小。

        19.2 线程创建

创建线程的函数叫做pthread_create,函数原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 参数说明:

    • thread:获取创建成功的线程ID,是一个输出型参数。
    • attr:用户设置线程的属性,传入 NULL 表示默认属性。
    • start_routine:表示线程的入口函数,即线程启动后要执行的函数。
    • arg:传给线程入口函数的参数。
  • 返回值说明:

    • 线程创建成功返回 0,失败返回相应的错误码。 

让主线程创建一个新线程 

  • 当一个程序启动时,操作系统会创建一个进程,同时也会创建一个线程,这个线程称为主线程。

  • 主线程通常是产生其他子线程的线程,在启动其他线程后,主线程可以继续执行其他操作,也可以等待子线程执行完毕后再继续执行。

  • 主线程通常负责完成一些必要的执行动作,比如初始化操作、资源的释放等。

下面是一个示例代码,展示了如何在主线程中创建一个新线程:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread_run(void* args)                                                                                                                             
{      
    const char* id = (const char*)args;      
    while(1){      
        printf("I am %s thread, %d\n", id, getpid());      
        sleep(1);      
    }      
}      
      
int main()      
{      
    pthread_t tid; 
    pthread_create(&tid, NULL, thread_run, (void*)"thread 1");      
    while(1){      
        printf("I am mian thread, %d\n",getpid());      
        sleep(1);      
    }      
    return 0;      
}

注意:你需要在编译时添加 -pthread 选项来链接 pthread 库

运行结果:

此时使用ps axj的命令查看进程信息: 虽然此时该进程中有两个线程,但是我们看到的进程只有一个,因为这两个线程都是属于同一个进程的。 

使用ps -aL命令,可以显示当前的轻量级进程。

  • 不带-L,看到是就一个个的进程
  • 带-L,看到的是每个进程内的多个轻量级进程

其中,LWP(light weight process)就是轻量级进程的ID,可以看到显示的两个轻量级进程的PID是相同的,因为它们属于同一个进程。

注意:在Linux中,应用层的线程与内核的LWP是一一对应的,实际上操作系统调度的时候采用的是LWP,而并非PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的,所以对于单线程进程来说,调度时采用PID和LWP是一样的。

获取线程ID

在提取线程 ID 的过程中,常见的两种方式如下:

  • 创建线程时通过输出型参数获得。
  • 通过调用pthread_self函数获得。
pthread_t pthread_self(void);

调用pthread_self函数即可获得当前线程的ID,类似于调用getpid函数获取当前进程的ID。

例如下面的代码,我们通过主线程创建一个新线程,主线程不断的打印新线程的ID,新线程去执行回调函数,打印出自己的ID:

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

void* thread_run(void* args) {
    while(1) { //新线程打印自己的ID
        printf("我是新线程[%s],我线程ID是:%lu\n", (const char*)args, pthread_self());
        sleep(1);
    }
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_run, (void*)"new thread");
    while(1) { //主线程先是创建新线程并打印新线程的ID
        printf("我是主线程,我创建的线程ID是:%lu, 我的线程ID是:%lu\n", tid, pthread_self());
        sleep(1);
    }
}

运行代码,可以看到这两种方式获取到的线程的ID是一样的。

        19.3 线程等待

线程等待是指一个线程等待另一个线程执行完毕后再继续执行的过程。在 POSIX 线程中,常用的线程等待函数是 pthread_join。一个线程被创建出来,就如同进程一般,也是需要被等待的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。线程需要被等待,如果不等待会产生类似于“僵尸进程”的问题,也就是内存泄漏。

线程等待的函数: pthread_join()

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

参数说明:

  • thread:被等待线程的ID
  • retval:它是一个输出型参数,用来获取新线程退出的时候,函数的返回值;新线程函数的返回值是void*,所以要获取一级指针的值,就需要二级指针,也就是void**;

返回值

  • 成功返回0
  • 失败返回错误码

使用说明:

  • 调用该函数的线程将挂起等待,直到被指定的线程结束。
  • 被等待的线程以不同的方式终止,根据终止方式,retval 所指向的单元里存放的值也会不同:
    • 如果被等待的线程通过 return 返回,retval 所指向的单元里存放的是被等待线程函数的返回值。
    • 如果被等待的线程被其他线程调用 pthread_cancel 异常终止,retval 所指向的单元里存放的是常数 PTHREAD_CANCELED
    • 如果被等待的线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。
    • 如果对被等待线程的终止状态不感兴趣,可以传入 NULLretval 参数。

 为什么线程异常终止会导致整个进程崩溃?

  1. 进程异常处理: 当一个进程中的某个子进程异常终止时,父进程可以通过 wait()waitpid() 函数获取到子进程的退出状态,包括退出码、终止信号等信息。这是因为子进程和父进程是相互独立的,一个子进程的异常终止不会影响到父进程的执行。

  2. 线程异常处理: 在一个进程中,所有线程共享同一地址空间和资源,它们是相互依赖的。如果一个线程异常终止,整个进程都会受到影响,因为线程之间是共享资源的。当一个线程异常终止时,整个进程的执行状态就会变得不确定,甚至可能导致进程崩溃。因此,pthread_join() 函数只能获取到线程正常退出时的退出码,而不能获取到线程异常退出的信息。

  • 一个线程的错误可能会影响到其他线程以及整个进程的执行状态。如果一个线程访问了无效的内存地址,或者发生了其他类似的错误,可能会导致整个进程崩溃。
  • 因此,当一个线程异常终止时,整个进程的执行状态就变得不确定。此时,父线程(或者主线程)可能无法正常地调用 pthread_join() 函数来等待子线程的结束,因为整个进程可能已经处于异常状态,无法继续执行。

模拟野指针问题 

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

void* thread_run(void* args) {
    int num = *(int*)args;
    while(1) {
        printf("I am new thread [%d], my thread ID is: %lu\n", num, pthread_self());
        sleep(1);
        // 模拟异常情况
        if(num == 3) {
            printf("Thread number: %d quit due to an error.\n", num);
            int* p = NULL;
            *p = 100;
        }
    }
}

int main() {
    pthread_t tid[5];
    for(int i = 0; i < 5; i++) {
        pthread_create(tid + i, NULL, thread_run, (void*)&i);
    }

    // 等待线程结束
    for(int i = 0; i < 5; i++) {
        void* status = NULL;
        pthread_join(tid[i], &status);
        printf("Thread [%d] exit with status: %d\n", i, (int)status);
    }

    return 0;
}

        19.4 线程终止

线程的终止是线程生命周期的一个重要部分。在多线程编程中,正确地管理线程的终止是确保程序稳定性和可靠性的关键。本节将介绍线程终止的几种常见方式以及如何安全地终止线程。

1. 正常退出

线程可以通过以下方式正常退出:

  • 返回语句: 线程函数可以通过简单地返回来正常退出。在这种情况下,线程函数中的 return 语句将会返回一个值,并且该值将会成为线程的退出状态,可以由其他线程通过 pthread_join 获取到。
void* thread_function(void* arg) {
    // 线程执行的代码
    return (void*)42; // 返回退出状态
}
  •  调用 pthread_exit 函数: 可以在线程函数内部显式地调用 pthread_exit 函数来退出线程。与返回语句相比,使用 pthread_exit 可以更明确地指定线程的退出状态。
void pthread_exit(void *retval);
void* thread_function(void* arg) {
    // 线程执行的代码
    pthread_exit((void*)42); // 退出线程并指定退出状态
}

2. 异常退出

线程可能会因为各种异常情况而提前退出,这时候需要考虑如何优雅地处理线程的终止。

  • 取消线程: 可以使用 pthread_cancel 函数取消线程的执行。被取消的线程会在接收到取消请求后立即退出,但是需要确保线程的资源得到正确释放,避免资源泄漏。
pthread_cancel(thread_id); // 取消线程的执行

异常处理: 在线程函数内部进行异常处理,确保线程在出现异常时能够安全地退出,释放资源。

        19.5 线程分离

线程分离是多线程编程中的重要概念,它涉及到线程的生命周期管理和资源释放。本节将介绍线程分离的概念、作用以及如何在编程中正确地使用线程分离。

1. 线程分离的概念

线程分离是指将一个线程从其创建者(通常是主线程)中分离出来,使得该线程在终止时能够自行释放资源,而无需其他线程显式地调用 pthread_join 函数等待其结束。分离线程后,不需要调用 pthread_join 函数来等待线程的结束,线程结束时系统会自动释放其资源。

2. 线程分离的作用

  • 资源释放: 分离线程可以确保线程在结束时自动释放其占用的资源,避免资源泄漏。
  • 避免僵尸线程: 分离线程可以避免产生僵尸线程,提高程序的稳定性和可靠性。
  • 简化代码: 分离线程可以简化代码逻辑,不需要显式地等待线程结束。

3. 使用 pthread_detach 函数分离线程

可以使用 pthread_detach 函数将线程分离,使得该线程在结束时自动释放资源。

pthread_detach(thread_id); // 将线程分离

4. 示例代码

下面是一个示例代码,演示了如何创建线程并将其分离:

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

void* thread_function(void* arg) {
    // 线程执行的代码
    printf("Thread is running...\n");
    sleep(3); // 模拟线程执行一段时间
    printf("Thread finished.\n");
    pthread_exit(NULL); // 结束线程
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_function, NULL); // 创建线程
    pthread_detach(tid); // 分离线程
    printf("Main thread finished.\n");
    return 0;
}

在这个示例中,主线程创建了一个新线程,并立即将其分离。因此,主线程不需要显式地等待新线程结束,而是可以立即继续执行后续代码。当新线程执行完毕后,系统会自动回收其资源。

5. 注意事项

  • 分离已结束的线程: 要确保在调用 pthread_detach 函数之前,线程尚未结束。如果尝试分离已经结束的线程,可能会导致不确定的行为。
  • 资源释放: 分离线程仅仅负责释放线程占用的资源,而不负责资源的释放。因此,在线程中分配的内存等资源需要在线程结束时手动释放,以避免资源泄漏。

线程分离是多线程编程中的一项重要技术,能够简化代码逻辑并提高程序的稳定性和可维护性。正确地使用线程分离可以有效地管理线程的生命周期,避免资源泄漏和僵尸线程的产生。

        19.6 线程ID及进程地址空间布局

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和内核中的LWP不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供的pthread_self函数,获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。

pthread_t到底是什么类型呢?

  • 在Linux系统中,pthread_t 是一个不透明的数据类型,用于表示线程的标识符。它实际上是一个指向线程控制块(TCB)的指针,TCB 包含了线程的各种属性和状态信息,如线程ID、栈信息、调度优先级等。
  • 当调用 pthread_create 函数创建新线程时,它会返回一个 pthread_t 类型的变量,这个变量实际上是一个对应于新创建线程的唯一标识符。该标识符由 NPTL 线程库使用,用于在用户空间管理线程的状态和属性,并与内核级的 LWP(轻量级进程)关联起来。
  • 线程库通过 pthread_t 变量来管理每个线程的状态和属性。线程库会根据 pthread_t 变量找到相应的线程控制块,然后根据需要对线程进行操作.pthread_t 类型取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
  • 总的来说,pthread_t 是线程库提供的一种抽象类型,用于标识和管理线程,由线程库内部使用,开发者只能通过 pthread_create 等接口来创建和操作线程。

通过ldd命令可以看到,我们采用的线程库实际上是一个动态库。

  • 在Linux系统中,动态库加载后需要将动态库本身的全部信息映射到主线程的堆、栈之间的共享区域。动态库除了包含代码之外,还需要维护线程创建的数据结构,这些数据结构通常存储在共享区域中,供所有线程访问和修改。
  • 每个线程运行时都需要有自己的临时数据,因此需要有私有的栈结构。在地址空间中,通常只有一个栈,这个栈是用来给主线程使用的,而其他线程会使用动态库中维护的栈结构。这样就可以确保每个线程都有自己的私有栈,不会与其他线程共享栈空间。
  • 动态库本身还负责线程的组织和管理工作,每个线程在地址空间中都会有一个 struct pthread(线程结构体)以及线程局部存储和线程栈。这些信息由动态库来维护,实现了“先描述、再组织”的方式。每个新线程在共享区域都有一块描述其信息的区域,通过这个区域的起始地址可以获取到线程的各种信息。
  • 在内核中,LWP(轻量级进程)和 struct pthread(线程结构体)是一一对应的关系。在用户层,如果存在多个线程结构体,为了和内核的 LWP 一一对应,这些线程结构体中一定会包含对应的 LWP。这样就可以确保在用户空间中的线程结构体和内核中的 LWP 是一一对应的关系,方便线程的管理和调度。

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

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

相关文章

【后端高频面试题--Nginx篇】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;后端高频面试题 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 后端高频面试题--Nginx篇 什么是Nginx&#xff1f;为什么要用Nginx&#xff1f;为什么Nginx性能…

揭秘:15条黄金法则,让你的GPT聊天提示效率翻倍!(一)

你的 ChatGPT 响应的好坏完全取决于你使用的ChatGPT 提示。 事实是&#xff0c;ChatgPT对于潜在客户开发、内容创建甚至外展都非常有效。 但大多数人只是使用人工智能来创建内容。 当然&#xff0c;它有时可以产生一些纯文本。也就是说&#xff0c;如果你只使用正确的提示。…

jsp课程教学管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 课程教学管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0…

FPGA_工程_基于rom的vga显示

一 框图 二 代码修改 module Display #(parameter H_DISP 1280,parameter V_DISP 1024,parameter H_lcd 12d150,parameter V_lcd 12d150,parameter LCD_SIZE 15d10_000 ) ( input wire clk, input wire rst_n, input wire [11:0] lcd_xpos, //lcd horizontal coo…

Days 29 ElfBoard LCD屏双电荷泵电路原理

7寸LCD屏幕的屏幕排线中采用的供电电压是5V供电&#xff0c;但是在屏幕工作时需要VCOM-5.3V、AVDD-12.5V、VGL--7V、VGH-17V几组电压&#xff0c;所以要对初始的5V电源进行DC-DC电压变换&#xff0c;在这里我们用到了双电荷泵电路。 再此电路中VCC_5V为电源输入&#xff0c;E…

gtkmm4 应用程序使用 CSS 样式

文章目录 前言css选择器css文件示例源代码效果动态设置css-classes 前言 程序样式和代码逻辑分离开 使代码逻辑更可观 css选择器 Cambalache提供了两种css-classes 相当于css里的类名:class“类名”css-name 相当于css里的标签名:spin div p 啥的 如上我设置了这个按钮控件的…

【ES】--ES集成热更新自定义词库(字典)

目录 一、问题描述二、具体实施1、Tomcat实现远程扩展字典2、验证生效3、ES配置远程扩展字典4、为何不重启ES能实现热更新 一、问题描述 问题现象: 前面完成了自定义分词器词库集成到ES中。在实际项目中词库是时刻在变更的&#xff0c;但又不希望重启ES&#xff0c;对此我们应…

[Python进阶] 识别验证码

11.3 识别验证码 我们再开发某些项目的时候&#xff0c;如果遇到要登录某些网页&#xff0c;那么会经常遇到输入验证码的情况&#xff0c;而每次人工输入验证码的话&#xff0c;比较浪费时间。于是&#xff0c;可以通过调用某些接口进行识别。 11.3.1 调用百度文字识别接口 …

软件22-上午题-树与二叉树2

一、平衡二叉树 平衡二叉树&#xff1a;是一棵空树或它的左右两个子树的高度差的绝对值不超过 1&#xff0c; 并且左右两个子树都是一棵平衡二叉树。 注意&#xff1a; 完全二叉树 平衡二叉树&#xff01;&#xff01;&#xff01; 二、二叉排序树&#xff08;二叉查找树、二…

Linux(Ubuntu)环境下安装卸载Python3(避免踩坑)

一、安装 第一步&#xff1a; 进入/usr/local/目录&#xff0c;下载Python3&#xff0c;这里我下载的是python 3.8.10&#xff0c;如果要下载其他版本改下链接中的版本号&#xff0c;需与官网版本号对应。 wget https://www.python.org/ftp/python/3.8.10/Python-3.8.10.tgz第…

问题:入园前需要做好的准备包括( )。 #学习方法#其他#微信

问题&#xff1a;入园前需要做好的准备包括( )。 A.室内通风 B.清洁消毒 C.保健医晨检 D.准备用品和玩具 参考答案如图所示

AI新工具(20240209) ImgGen AI-免费在线AI图像生成应用;Smoothrase - 新一代的图像擦除技术等

ImgGen AI-免费在线AI图像生成应用 使用ImgGen的AI图像生成器&#xff08;文字转图像&#xff09;免费创建令人惊叹的图像&#xff0c;无水印&#xff0c;无需注册。包括功能、优势、定价、定位等。 Anything in Any Scene - 在现有的动态视频中无缝地插入任何物体&#xff0c…

java对象内部都有哪些东西

普通对象 对象头 markword 占8字节ClassPointer 指针 :-XX userCompressedClassPointrs 为4字节&#xff0c;不开启为 8字节实例数据 引用类型: -XX userCommpressedOops 为4字节&#xff0c;不开启8字节Padding对齐&#xff0c; 8的倍数 数组对象 对象头&#xff1a;markwor…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 2月12日,星期一

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年2月12日 星期一 农历正月初三 1、 注意错峰出行&#xff01;今起全国公路网流量将处于高位运行状态。 2、 中国旅游研究院&#xff1a;预计2024年国内旅游人数或超60亿人次。 3、 阔别四年&#xff0c;北京、贵阳、张家…

Halcon 频域缺陷检测

文章目录 傅里叶变换频谱矩形圆菱形黑白相间的亮带去除图纹&#xff08;反傅里叶变换&#xff09;去除图纹滤波器处理 Halcon 频域空间域检测缺陷Halcon 频域差分空间域 缺陷检测&#xff08;lines_gauss 提取线&#xff09;Halcon 频域差分空间域&#xff08;blob特征&#xf…

leetcode 461. 汉明距离

比较简单的一题&#xff0c;先对两个整数进行异或操作&#xff0c;会将两个整数二进制形式中各个数字进行异或操作&#xff0c;不同的数字则为1&#xff0c;再通过移位操作统计得到的二进制数中为1的个数&#xff0c;即为所求。 Java代码如下&#xff1a; class Solution {pub…

例38:使用Frame(分组框)

建立一个EXE工程&#xff0c;在窗体上放两个Frame框。分别放两组单选按钮表示性别和收入&#xff0c;注意每组单选按钮的组名要一样。在按钮中输入代码&#xff1a; Sub Form1_Command1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)If Frame1.Visible ThenFrame1.Visib…

车载诊断协议DoIP系列 —— 协议中术语解释和定义

车载诊断协议DoIP系列 —— 协议中术语解释和定义 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,…

EXCEL中如何调出“数据分析”的菜单

今天发现&#xff0c;原来WPS还是和EXCEL比&#xff0c;还是少了“数据分析”这个日常基本做统计的菜单&#xff0c;只好用EXCEL了&#xff0c;但奇怪发现我的EXCEL中没发现这个菜单&#xff0c;然后查了下&#xff0c;才发现&#xff0c;要用如下的方法打开&#xff1a; 1&…

基于Java (spring-boot)的电影院管理系统

一、项目介绍 基于Java (spring-boot)的电影院管理系统功能&#xff1a;管理员登录、用户注册、用户登录、用户、影片介绍、购票、选坐、支付、我的订单、影片、榜单、关于、后台首页、影院信息管理、电影信息管理、电影类别管理、影厅信息管理、场次信息管理、订单信息管理、用…