【Linux学习】线程详解

目录

十八.多线程

        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) { //新线程打印自己的IDprintf("我是新线程[%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) { //主线程先是创建新线程并打印新线程的IDprintf("我是主线程,我创建的线程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.mzph.cn/news/679848.shtml

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

相关文章

橘子学linux调优之工具包的安装

今天在公司无聊的弄服务器&#xff0c;想着有些常用的工具包安装一下&#xff0c;这里就简单记录一下。 一、sysstat的安装和使用 1、安装 我是通过源码的方式安装的&#xff0c;这样的好处在于可以自由选择你的版本&#xff0c;很直观。 直接去github上找到sysstat的地址&a…

网络安全工程师技能手册(附学习路线图)

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 安全是互联网公司的生命&#xff0c;也是每位网民的基本需求。现在越来越多的人对网络安全感兴趣&#xff0c;愿意投奔到网络安全事业之中&#xff0c;这是一个很好的现象。 很多对网络安全感…

LeetCode 0094.二叉树的中序遍历:递归/迭代(栈模拟递归)

【LetMeFly】94.二叉树的中序遍历&#xff1a;递归/迭代(栈模拟递归) 力扣题目链接&#xff1a;https://leetcode.cn/problems/binary-tree-inorder-traversal/ 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root […

操作系统——内存管理(附带Leetcode算法题LRU)

目录 1.内存管理主要用来干什么&#xff1f; 2.什么是内存碎片&#xff1f; 3.虚拟内存 3.1传统存储管理方式的缺点&#xff1f; 3.2局部性原理 3.3什么是虚拟内存&#xff1f;有什么用&#xff1f; 3.3.1段式分配 3.3.2页式分配 3.3.2.1换页机制 3.3.2.2页面置换算法…

SSM实现支付宝沙盒支付

文章目录 沙盒支付准备配置测试 沙盒支付 这里用的支付宝的一个沙盒环境&#xff0c;是支付宝提供给开发者测试用的。 下面主要梳理一下&#xff0c;支付功能的实现&#xff0c;其实还是很简单的&#xff0c;因为支付宝都提供好了&#xff0c;我们只要调用接口去传入参数即可…

C# EventHandler<T> 示例

新建一个form程序&#xff0c;在调试窗口输出执行过程&#xff1b; 为了使用Debug.WriteLine&#xff0c;添加 using System.Diagnostics; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using S…

写的太通透了!大模型自省式 RAG 与 LangGraph 的实践!

本文讲解了自省式 RAG 的基础原理以及基于 LangGraph 的实践演示 自省式 RAG 与 LangGraph 重要链接 关于 Self-RAG 和 CRAG 的教程手册 演示视频 研究背景 由于大多数大型语言模型&#xff08;LLMs&#xff09;通常只针对大量公共数据进行周期性训练&#xff0c;它们往往…

使用python绘制无边框ECG信号-可用于论文插图-小白版

用python绘制一个心电信号 最近在写大论文&#xff0c;由于自己做的是心电信号难免要做一些心电信号的插图&#xff0c;然后在写论文的时候有的时候要用真实信号有的时候需要用到示意图&#xff0c;这里面的其他部分使用Visio绘制的&#xff0c;但是前面的心电信号部分&#x…

视觉slam十四讲学习笔记(三)李群与李代数

1. 理解李群与李代数的概念&#xff0c;掌握 SO(3), SE(3) 与对应李代数的表示方式。 2. 理解 BCH 近似的意义。 3. 学会在李代数上的扰动模型。 4. 使用 Sophus 对李代数进行运算。 目录 前言 一、李群李代数基础 1 群 2 李代数的引出 3 李代数的定义 4 李代数 so(3…

HAproxy+Mycat集群+MySQL主从组成高可用性方案架构图

如果还担心 HAproxy 的稳定性和单点问题&#xff0c;则可以用 keepalived 的 VIP 的浮动功能&#xff0c;加以强化&#xff1a;https://blog.csdn.net/gaofenglxx/article/details/118883060

推荐在线图像处理程序源码

对于喜爱图像编辑的朋友们来说&#xff0c;Photoshop无疑是处理照片的利器。然而&#xff0c;传统的Photoshop软件不仅需要下载安装&#xff0c;还对电脑配置有一定的要求&#xff0c;这无疑增加了使用的门槛。 现在&#xff0c;我们为您带来一款革命性的在线PS修图工具——基…

泽攸科技ZEM系列台扫助力环境科研创新:可见光催化抗生素降解的探索

环境污染和能源短缺是当今人类社会面临的最严重威胁之一。为了克服这些问题&#xff0c;特别是在污水处理过程中&#xff0c;寻找新的技术来实现清洁、高效、经济的发展显得尤为重要。在各种工业废水中&#xff0c;抗生素的过量排放引起了广泛关注。抗生素的残留会污染土壤、水…

深入探索Flex布局:从基础到实战,附带抖音解决方案案例分析

文章目录 简介盒子模型概念版心居中 Flex布局组成主轴对齐方式侧轴对齐方式修改主轴方向弹性伸缩比弹性盒子换行行对齐方式案例-抖音解决方案效果图案例分析参考代码 领取完整源码 简介 在阅读本文之前&#xff0c;请确保你已经掌握CSS基本语法、常用属性以及CSS选择器。 盒模…

计算机视觉讲座PPT分享

最近在电子工业出版社做的《计算机视觉入门路线图》讲座的部分PPT。 主要介绍了计算机视觉的学习基本路线。

推荐系统|行为序列_用户行为序列建模、Din模型和SIM模型

文章目录 用户行为序列建模Din模型Din模型的缺点 用户行为序列建模 物品ID通过Embedding将会得到一个向量&#xff0c;性质差不多的向量在空间中也会处于差不多的位置&#xff0c;可以用取平均方式得到一个综合所有向量的向量。 取平均后可以作为代表用户的一个特征。 以上的…

数据分析基础之《pandas(8)—综合案例》

一、需求 1、现在我们有一组从2006年到2016年1000部最流行的电影数据 数据来源&#xff1a;https://www.kaggle.com/damianpanek/sunday-eda/data 2、问题1 想知道这些电影数据中评分的平均分&#xff0c;导演的人数等信息&#xff0c;我们应该怎么获取&#xff1f; 3、问题…

SpringCloud-高级篇(二十二)

前面解决了消息的可靠性、消息的延迟问题&#xff0c;消息的堆积的问题&#xff0c;下面研究mq可用性、并发能力问题&#xff0c;这就需要mq集群来实现了 一&#xff1a;集群分类 &#xff08;1&#xff09;普通集群 创建一个节点&#xff1a; 8082、8083也可以看到这个队列&…

红队打靶练习:DEVGURU: 1

目录 信息收集 1、arp 2、nmap 3、dirsearch WEB web信息收集 8585端口 漏洞利用 提权 系统信息收集 横向渗透 get flag 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:50:56:20:80:1b, IPv4: 192.168.10…

14.盔甲?装甲?装饰者模式!

人类的军工发展史就是一场矛与盾的追逐&#xff0c;矛利则盾坚&#xff0c;盾愈坚则矛愈利。在传统的冶金工艺下&#xff0c;更坚固的盾牌和盔甲往往意味着更迟缓笨重的运动能力和更高昂的移动成本。从战国末期的魏武卒、秦锐士&#xff0c;到两宋之交的铁浮图、重步兵&#xf…