Linux 多线程(四)线程等待,线程分离,线程管理,C++多线程,pthread库

目录

一、线程等待

pthread_join

代码demo:

退出信息的理解

如何理解pthread_join中的参数是二级指针retval

二、分离线程

可连接状态joinable和分离状态detached

pthread_detach

Demo1:子线程被主线程分离

Demo2:子线程自己分离自己

分离线程本质

三、线程ID与进程地址空间布局

pthread库

[pthread 库的加载与共享原理](#pthread 库的加载与共享原理)

[线程 ID](#线程 ID)

四、线程的管理

[pthread 线程库(用户态)的核心内存布局图](#pthread 线程库(用户态)的核心内存布局图)

线程的独立栈

[struct pthread_attr_t](#struct pthread_attr_t)

[五、Linux NPTL 线程模型的本质](#五、Linux NPTL 线程模型的本质)

[Linux NPTL 线程模型的本质](#Linux NPTL 线程模型的本质)

线程完整的生命周期

六、线程局部存储

[__thread原理 :](#__thread原理 :)

__thread的特性和限制:

七、C++的多线程

[C++ 多线程与 pthread 库的核心关联](#C++ 多线程与 pthread 库的核心关联)

[C++ 多线程的优势与使用场景](#C++ 多线程的优势与使用场景)

C++跨平台性:

[C++多线程代码demo :](#C++多线程代码demo :)

八、总结


我们在上一篇文章中详细的讲述了线程的控制和终止问题,本篇文章我们继续讲解关于线程的剩余内容。

一、线程等待

一个默认创建出来的线程,必须被主线程等待,如果主线程不等待,就会导致:

  1. 线程终止后,其内核资源不会自动释放,必须通过 pthread_join 回收,否则会造成线程资源泄漏(类比进程的僵尸进程,但 Linux 无 "僵尸线程" 的正式定义,本质是资源未回收),长期运行会耗尽系统资源。
  2. 若主线程需要子线程的计算结果、执行状态,可通过 retval 参数获取子线程的退出状态,实现线程间的结果同步。
  3. 可通过 pthread_join 控制主线程与子线程的执行顺序,确保主线程在子线程完成任务后再继续执行,避免主线程先退出导致整个进程终止、子线程被强制杀死。

**如何等待?**使用 pthread_join 接口

pthread_join

pthread_join 是线程等待与资源回收的核心接口阻塞调用线程 (通常是主线程),等待指定的 thread 线程终止,并回收其资源,同时获取该线程的退出状态。

  • 第一个参数 thread 是要等待的目标线程的 pthread_t ID。
  • 第二个参数 retval 是输出型参数,并且是个二级指针,用于接收目标线程的退出信息( 线程入口函数 return 的值,或 pthread_exit 传入的参数**)** ,因为前两者的返回值是一级指针 void*,**因此 pthread_join 第二个参数用二级指针 void** 接收,本质是通过指针传递,将线程的返回值写入调用者的变量中。**若不需要返回值可传 NULL。

返回值:成功返回 0,失败返回错误码(如目标线程不存在、线程已被分离等)。

注意事项 :

  1. pthread_join 是阻塞等待,调用线程会一直挂起,直到目标线程终止,才会继续执行后续代码。
  2. retval 类型说明 : 线程入口函数的返回值是 void*,因此 pthread_join 用二级指针 void** 接收,本质是通过指针传递,将线程的返回值写入调用者的变量中。
  3. 一个线程只能被 join 一次,重复 join 会导致未定义行为。

代码demo:

线程函数的返回值类型是 void*(一级指针),这是线程退出时的退出信息载体。代码中的 return (void*)10;,就是把整数10强转成void*,作为线程的退出返回值。

线程等待函数 pthread_join 的第二个参数是 void** (二级指针),是输出型参数 **,作用就是接收线程函数的void*返回值。

运行结果:

退出信息(退出码)被拿到。

退出信息的理解

退出信息是线程通过 return / pthread_exit 返回的 void* 类型数据的统称,是一个通用载体,它可以承载任意类型的数据:整数,字符串地址,类对象 / 结构体指针,自定义状态标识。

退出码则是是把这个 void* 载体,强转为整数使用时的特定语义,是退出信息的一种具体用法,属于退出信息的子集。

void* 是线程退出信息的通用载体,退出码是这个载体承载整数状态时的特定用法,因此退出码属于退出信息的一种;我们也可以用它传整数当退出码,也可以用它传任意其他类型的数据,此时它就是广义的退出信息。

那这个退出信息在哪?

线程的返回值(void*)是由 pthread线程库在用户态维护的,线程调用return/pthread_exit返回void* 时,线程库会把这个返回值,暂存在线程的控制块(TCB)中。当主线程调 pthread_join时,线程库会从 TCB 中取出这个返回值,通过二级指针写入主线程的retval变量。pthread_join 执行完毕后,线程的 TCB、栈等资源会被回收,返回值的生命周期也就结束了。

为什么 pthread_join 只拿到退出码,没有异常退出信号?

pthread_join 只能拿到线程的正常退出码 ,**因为线程一旦发生异常,整个进程会直接崩溃,pthread_join 根本没有执行的机会,自然永远拿不到线程的异常退出信号。**因此多线程编程中,pthread_join 只需要处理正常退出的场景,无需关心异常,这是由 Linux 多线程的共享地址空间模型决定的。

我们上面说过退出信息不仅可以是数字(退出码),还可以是其他类型,理由就是线程入口函数的返回类型是 void*,它可以是任意一个类型,只是我们在上面的代码中把它强转为整形当作整形退出码来看,那我们下来把它转成类类型:

注意事项:

  1. 如果我们在栈上定义 Res res,函数返回后栈内存就立即会被销毁。虽然地址还在,但里面的数据已经变成垃圾了,主线程访问时就是野指针,程序就会崩溃。所以线程在返回复杂对象时,必须用 new 在堆上分配,或者用全局/静态变量(不推荐全局,多线程冲突)。
  2. 类型强转必须严格匹配,在线程里是 Res*,主线程里就必须强转回 Res*。如果强转成其他类型,程序会直接崩溃。

运行结果:

输出符合预期,证明了 void* 不仅能承载整数(退出码),还能承载结构体 / 对象指针,传递任意复杂的退出信息。

如何理解pthread_join中的参数是二级指针retval

本质原因就是 pthread_join 需要修改调用者的变量,而 C/C++ 中要修改函数外的变量,必须传该变量的地址(也就是指针的指针)。线程入口函数的返回值是 void*,pthread_exit 的参数也是 void*。也就是说线程的退出信息本质是一个 void* 类型的值 。我们在主线程定义一个变量 retval 用来存这个返回值,**我们要在主线程拿到这个值,就要传 retval 这个变量本身的地址,也就是 void** 类型。**函数内部可以通过这个地址,直接修改主线程 retval 的值,把线程的返回值写进去。这就是 pthread_join中的参数是二级指针的原因。

二、分离线程

线程能不能执行线程替换?

线程本身不能直接执行程序替换(exec 系列函数),因为 exec 系列函数的本质是**替换当前进程的地址空间,用新程序的代码、数据、栈、堆完全覆盖原进程的所有内容。**线程是进程内的执行流,同一进程内的所有线程共享同一个地址空间。如果在线程中直接调用 exec,会直接替换掉整个进程的地址空间,导致进程内所有线程(包括主线程)瞬间被销毁,原进程的所有逻辑彻底终止,因此线程绝对不能直接执行 exec。

但我们可以通过 fork() 在当前线程中创建一个全新的子进程,在子进程中执行 exec 完成程序替换,这是 Linux 多线程环境下执行外部程序的标准方案。

可连接状态joinable和分离状态detached

我们知道 pthread_join 在等待线程时是阻塞等待的,因此就会导致主线程在阻塞等待期间做不了任何事。只有目标线程执行完毕、资源回收,主线程才能继续执行后续逻辑。

所以我们把子线程必须让主线程阻塞等待的状态叫做 joinable (可连接状态)。我们创建的线程默认都是可连接状态。

那我们如果不想让主线程再等待我们创建的线程,让创建好的线程做完事后自动结束掉,自己释放自己的资源,不要主线程再管了,此时我们就要把线程的 joinable (可连接状态) 设置为另一种状态---detached(分离状态)。

线程被分离,即取消 joinable(可连接状态),设置为 detached(分离状态)。

如何分离? 就引出了下一个线程分离接口函数 pthread_detach :

pthread_detach

pthread_detach 用于将指定线程设置为分离态(detached)。**取消线程的 joinable 属性,使其退出时自动释放内核资源(栈、TCB),无需其他线程调用 pthread_join 回收。**一旦分离,线程无法再被 join,也无法恢复成 joinable 状态。成功返回 0;失败返回错误码(如线程不存在)。

Demo1:子线程被主线程分离

逻辑:主线程创建子线程后,立刻调用 pthread_detach 将其分离,然后主线程继续执行其他任务,无需等待子线程。

主线程分离后子线程继续打印,子线程独立运行。主线程退出后,进程结束,子线程被强制终止 (输出可能不完整)。

Demo2:子线程自己分离自己

逻辑:子线程启动后,内部调用 pthread_detach(pthread_self()) 主动设置为分离态,这种写法更灵活,常用于线程库内部设计。

主线程尝试 join 分离态线程,会输出错误信息 Invalid argument,证明分离态确实无法 join。

分离线程本质

线程分离态仅改变线程的资源回收方式,使其退出时自动释放资源、无需主线程阻塞等待,但完全不改变线程属于进程的本质属性 :同一进程内的所有线程共享虚拟地址空间与生命周期,无论是否分离,主线程一旦退出,整个进程终止,所有子线程都会被强制销毁; 同时,任何线程(包括分离态线程)触发严重异常时,都会导致整个进程崩溃,无法实现异常隔离。因此,分离态线程的最佳实践是主线程必须保持存活(通常以死循环、事件循环等方式),绝不能提前退出,以此保障分离态后台线程的正常运行。


三、线程ID与进程地址空间布局

pthread库

pthread库是什么?pthread库在哪?

pthread(POSIX Thread)是 POSIX 标准定义的线程库,也是 Linux 系统下实现多线程编程的标准用户级线程库本质是一个动态链接库(.so 文件,如 libpthread.so.0,软链接指向具体版本如 libpthread-2.17.so),存储在磁盘上,程序运行时加载到内存。

POSIX(Portable Operating System Interface)是一系列由 IEEE 制定的标准,旨在确保操作系统之间的兼容性,尤其针对类 Unix 系统。它定义了应用程序与操作系统之间的接口规范,包括系统调用、命令行工具和线程管理等。
pthread库封装的不是内核提供的系统调用,而是用户态封装层:基于 Linux 内核的轻量级进程(LWP)机制封装出 pthread_create、pthread_join、pthread_detach 等接口,向上给用户提供统一的线程操作接口函数,向下调用内核系统调用创建线程,实现了用户态与内核态的协同。
这和我们在 Linux 多线程(一)这篇文章中讲的相对应 : 内核层面不存在真正的线程实体与专属 TCB ,仅通过task_struct管理共享进程资源的轻量级进程,提供clone等系统调用完成创建;而 pthread 用户态线程库作为中间层,向上为用户封装pthread_create等标准线程接口,屏蔽内核 LWP 的底层细节,让用户以线程视角开发。向下调用内核clone接口创建共享地址空间、页表、文件描述符的轻量级进程,最终实现用户感知的 "线程" 本质就是 pthread 库封装后的 LWP。该设计完美衔接了内核的轻量级进程模型与用户的线程使用需求,同时也决定了线程的生命周期完全绑定进程、线程异常会导致整个进程崩溃等核心特性,且使用该库编译时需链接-pthread参数。

pthread 库的加载与共享原理

pthread本身就是个在磁盘上的库文件,程序启动时,操作系统将磁盘上的 pthread.so 库文件加载到物理内存进行进程地址空间映射,每个进程(图中左右两个进程)在自己的虚拟地址空间中,通过 mmap 将物理内存中的 pthread 库映射到动态映射区域 / 共享区。由于是动态共享库,物理内存中只保留一份 pthread 库的代码段,所有进程通过页表映射共享这份代码,极大节省了物理内存。

进程的代码区可以直接调用 pthread 库的函数(如 pthread_create、pthread_join),并操作进程内的线程。

线程 ID

pthread_create 函数会产生一个线程ID,存放在第一个参数指向的地址中,**这个 线程 ID 是pthread 库在用户态为新线程分配一个唯一的标识符(pthread_t)。**ID 仅在该进程的 pthread 库内部有效。

  1. 左侧打印出的线程 ID 是 140697675123284,这是 pthread_t 类型的用户态线程 ID(通常是一个很大的整数,对应库内部的线程控制块地址)。
  2. 右侧使用 ps -aL 命令查看进程,显示的 LWP ID 是 1690918 和 1690919,这是内核态的轻量级进程 ID。
  1. 我们所看到的线程的tid是一个地址

四、线程的管理

线程可以有多个,所以线程本身要被管理吗?

这里的线程,指的是用户层面上的线程 ,用户使用库中的函数(pthread_create)会创建多个线程,所以这些线程必须被管理!依旧是先描述再组织

先描述 :

一定会有一个结构体来管理线程,这个结构体就叫 TCB(Thread Control Block)。 用户态里的线程是真实存在的,并且需要被管理。**这个管理者,就是 pthread 库。管理每个线程信息的结构体,就是 TCB。**这里一定不能和内核态里的轻量级进程(LWP)搞混了,内核层面上不存在真正意义上的线程,所以也没有专门的TCB(线程控制块)结构,内核用 task_struct(PCB) 管理。但是我们现在讨论的是用户层面上的线程。注意区分

再组织 :

pthread 库会用全局链表来组织进程内所有线程的 TCB。

这里可能我们会好奇,我们之前学习的结构体大都是在内核中,内核中用先描述再组织的方法我们很熟悉,为什么库中也可以用结构体来进行先描述再组织?

Linux 下用户态线程的 TCB(线程控制块)完全由 pthread 库在用户态实现和管理,内核中不存在这个结构;库中不仅可以定义 TCB 结构体,还会用链表等数据结构组织所有线程的 TCB,实现线程的全生命周期管理,这和 STL 容器用数据结构管理元素的逻辑完全一致。

C++ STL 容器中用数据结构(链表 / 数组)管理多个元素(如 std::list<int> 管理多个整数),提供插入、删除、查找接口。pthread 库用链表数据结构管理多个 TCB 元素,提供 pthread_create、pthread_join、pthread_detach等接口。库本身就是用户态的软件层,完全可以自由定义结构体、组织数据结构,不受内核限制,这是用户态库的基本特性。

pthread 线程库(用户态)的核心内存布局图

这个红色方框整体就是 TCB。它是 pthread 库内部为了管理每一个用户态线程而定义的核心数据结构体。

TCB里的内容:

  1. 线程 ID (pthread_t tid) : 就是 TCB 自己的起始地址 ,这个地址就是我们上面打印出来的很大的一个数字,因为它本质就是一个虚拟地址。
  2. 线程栈信息 : 线程栈的起始地址,线程栈大小,栈顶、栈边界、栈溢出保护页。
  3. 线程退出信息 : 线程返回值 void *retval ,就是我们用 return 或 pthread_exit 传出来的那个值。它会被保存在 TCB 中,所以 retval 就是从 TCB 中取出的。
  4. 线程局部存储 TLS : Thread Local Storage 每个线程独有的全局变量,不共享。和下文要讲的 __thread 关键字有关。
  5. 线程运行状态 : 线程是否已经退出,是 joinable 还是 detached(分离态), 线程取消状态(能否被 pthread_cancel), 线程优先级、调度策略。
  6. 线程入口信息 : 线程函数指针 start_routine 传给线程的参数 arg。
  7. 库内部管理用的结构 : 链表指针 prev /next,pthread 库用链表把所有 TCB 串起来管理。
  8. 同步相关信息(锁、条件变量等),线程持有的锁信息,条件变量等待队列节点,信号掩码等。

TCB 在哪?

TCB 位于用户态空间(0~3GB)的动态库区域(mmap 映射区)。**pthread 库在调用 pthread_create 创建线程时,会在自己的库内存空间内部,通过 malloc 或 mmap 申请一块内存,用来存放这个红色方框。**这也解释了为什么 TCB 是由 pthread 库创建管理的。

线程的独立栈

线程要运行函数,必须有栈来存:局部变量,函数调用关系,临时数据,寄存器上下文,每个线程必须有自己的栈,否则会和其他线程冲突。栈的位置不在进程栈,不在内核栈,不在堆。它在进程地址空间的 mmap 区域(动态库/共享区)。

线程栈是 pthread 库在用户态创建的,调用 pthread_create 时,库申请一块内存(默认 8MB),这块内存就是线程独立栈,把栈的地址、大小存进 TCB(struct pthread),整个过程和内核都无关。

struct pthread_attr_t

struct pthread_attr_t 是什么?

struct pthread_attr_t 是 pthread 库提供的、用户态可见的线程属性配置结构体是创建线程前用来定制线程参数的配置模板 ,它不在 TCB 里。它和 pthread_create 函数的第二个参数有关,

它的核心作用是在调用 pthread_create 创建线程前,通过设置这个结构体的字段,自定义新线程的各项特性(如栈大小、分离态、调度策略、CPU 亲和性等),再将属性结构体作为参数传入 pthread_create,让新线程按配置创建。

这个属性结构体的所有配置最终会被写入 TCB 中,作为线程运行时的参数,属性结构体本身不参与线程的运行时管理,仅负责初始化阶段的参数传递。


五、Linux NPTL 线程模型的本质

Linux NPTL 线程模型的本质

Linux NPTL 线程模型的本质:struct pthread(用户态 TCB)与 struct task_struct(内核轻量级进程 LWP)是 1:1 一一对应的关系,这是 Linux 线程实现的核心基石。

补充 : NPTL(Native POSIX Thread Library)是 Linux 系统上的一个线程实现库,旨在提供高性能、可扩展的 POSIX 线程支持。它取代了早期的 LinuxThreads 实现,成为现代 Linux 系统中默认的线程库。

  • 内核是底层执行者:task_struct 负责完成最底层的 CPU 调度、上下文切换、资源分配等核心工作,是线程能被 CPU 执行的根本保障。
  • 用户态是上层封装:基于内核 LWP 封装出用户熟悉的 "线程" 接口,屏蔽内核细节,让用户以线程视角开发。
  • 内核完成底层操作后,会将状态同步给用户态 TCB,最终形成完整的线程执行流。

有个问题 : 线程退出后,我们用 ps -aL 查看什么都没有了,那为什么还要调用 pthread_join 进行线程等待回收?

这个问题还上面的知识息息相关,分别对应线程退出时的两层状态变化,当线程执行完毕退出时(return 或 pthread_exit):

  • **内核层对应的 task_struct(LWP)会被内核立即销毁、释放资源,**因此用 ps -aL 等工具查看时,该 LWP 已经消失,查不到任何信息。
  • 用户态层线程的 struct pthread(TCB)不会自动释放,因为它还存储着线程的退出值、栈资源等关键信息,需要等待 pthread_join 读取后才能回收。

pthread_join 的核心作用是用户态资源回收的唯一入口 ,它不但阻塞等待线程退出,还会读取并释放 TCB 资源 :从 TCB 中取出线程退出值,然后释放 TCB、线程栈等用户态资源,彻底完成线程的全生命周期回收。

如果线程被设置为分离态(detached),线程退出,内核销毁 LWP 的同时,pthread 库会自动释放 TCB 资源,无需手动调用 pthread_join。这就是分离态线程 "无需等待、自动回收" 的底层原理,本质是把 TCB 的回收权交给了库,而非用户。

线程完整的生命周期


六、线程局部存储

我们来看两段代码 :

我们定义了一个普通全局变量 pid_t id = 0,主线程和新线程同时对其进行读写操作:新线程循环打印 id 的值和地址,id 自增,id 自增主线程循环打印 id 的值和地址

运行结果 :

  • 地址完全相同:主线程和新线程打印的 &id 都是 0x55e16353d154,说明两个线程访问的是同一块内存地址。
  • 值同步递增:id 的值在两个线程中交替递增(0→1→2→3...),证明全局变量被所有线程共享,是典型的线程间共享资源。

底层原理 :

Linux 线程是进程内的轻量级进程(LWP),所有线程共享同一个进程的虚拟地址空间,因此普通全局变量位于进程的全局数据段,被所有线程共享访问,这也是多线程数据竞争的根源。

那如果在全局变量前加上一个编译选项 __thread 呢?

仅在全局变量前添加 __thread 关键字,其余逻辑完全不变。

运行结果 :

  • 地址完全不同:主线程的 &id 为 0x7fad1b3083bc,新线程的 &id 为 0x7fad1b30463c,说明两个线程访问的是不同的内存块
  • 值完全独立:主线程的 id 始终为 0(仅主线程自增),新线程的 id 独立递增(0→1→2→3...),两个线程的变量互不干扰

__thread原理 :

__thread 是 GCC 提供的线程局部存储(Thread Local Storage, TLS)关键字,作用是为每个线程创建该变量的独立副本,每个线程仅能访问自己的副本。 变量的存储位置从全局数据段转移到线程的 TLS 区域(属于线程私有内存,由 TCB 管理)。线程间的变量完全隔离,不存在数据竞争,无需加锁即可安全访问。

__thread的特性和限制:

七、C++的多线程

C++ 多线程的本质和跨平台性有关。

C++ 标准库中的 <thread> 模块(C++11 引入),本质是跨平台的线程抽象接口,其核心逻辑是:

  • 在 Linux 下:底层直接封装 POSIX 线程库(pthread),所有线程操作最终都会调用 pthread_create、pthread_join 等底层接口。
  • 在 Windows 下:底层封装 Windows 原生的 CreateThread 等 API。

C++ 多线程与 pthread 库的核心关联

C++ 多线程的本质是在C++层面上对 Linux 中 pthread 库的封装

C++ 多线程的优势与使用场景

C++跨平台性:

C++ 多线程的跨平台性,核心是通过 C++11 及后续标准引入的<thread>等标准线程库,构建了一套统一、抽象、与操作系统解耦的线程编程接口,彻底屏蔽了不同平台底层线程实现的差异,实现了 "一套代码、多平台编译运行" 的核心目标。

  1. 在 Linux 系统下,C++ 标准线程库本质是对 POSIX pthread 库的封装,std::thread的创建、join()等待、detach()分离等操作,底层会直接调用pthread_create、pthread_join、pthread_detach等原生接口,复用了 Linux 下 "用户态 TCB 管理 + 内核 LWP 调度" 的 1:1 线程模型;
  2. 在 Windows 系统下,标准库则会自动封装 Windows 原生的CreateThread等内核线程 API,适配 Windows"进程 PCB + 线程 TCB" 的原生线程模型;
  3. 在 macOS 等其他类 Unix 系统中,也会对应调用系统原生的线程实现。

开发者仅需使用 C++ 标准统一的语法(如std::thread、std::mutex、thread_local等)编写线程逻辑,无需针对不同平台修改代码、适配不同的原生线程接口,标准库会在编译阶段根据目标平台自动完成底层适配,既保留了原生线程的高性能,又通过抽象封装消除了平台差异,大幅降低了跨平台多线程开发的复杂度,同时保证了代码的可移植性与一致性。

C++多线程代码demo :

C++ std::thread 常用接口 & 作用 :

  • std::thread 构造函数创建线程,并立即开始运行。
  • join()主线程阻塞,等待子线程结束,并回收线程资源(对应 pthread_join)。
  • detach()将线程分离,主线程不再管它,线程退出后系统自动回收资源(对应 pthread_detach)。
  • joinable()判断这个线程对象是否还 "可被 join",没 join、没 detach 就返回 true。
  • get_id() / std::this_thread::get_id()获取线程 ID,用于标识不同线程。
  • std::this_thread::sleep_for()让当前线程休眠一段时间。
  • thread_local线程局部变量,每个线程一份,互不干扰。

完整 Demo:演示线程完整生命周期 : 创建 → 运行 → 休眠 → 私有变量 → 结束 → 回收走一遍。

因为是C++下的多线程,所以这段代码我们是在 VS 2022 下运行的,我们也能看出运行正常,这也说明了C++这门语言跨平台性的强大。

八、总结

本文深入讲解了Linux线程管理的核心内容,包括线程等待、分离、ID管理及线程局部存储等关键机制。重点分析了pthread_join的线程资源回收原理,解释了分离线程(detached)的自动释放特性。详细阐述了用户态线程控制块(TCB)与内核轻量级进程(LWP)的1:1对应关系,揭示了Linux线程模型的本质。同时介绍了__thread关键字实现的线程局部存储(TLS)机制,以及C++标准线程库对pthread的跨平台封装。通过底层原理分析与代码示例,全面展示了Linux线程从创建到回收的完整生命周期管理。

谢谢大家的观看!

相关推荐
麦德泽特2 小时前
基于 Go 语言的 Modbus 项目实战:构建高性能、可扩展的工业通信服务器
服务器·开发语言·golang·modbus·rtu
倔强的胖蚂蚁2 小时前
云原生服务器存储规划与磁盘选型实施
运维·服务器·云原生
ZGUIZ2 小时前
Ubuntu 25.10 无法外接显示器解决方案
linux·运维·ubuntu
一条闲鱼_mytube2 小时前
TCP流量控制与拥塞控制
服务器·网络·tcp/ip
H_BB2 小时前
DFS实现回溯算法
数据结构·c++·算法·深度优先
还是大剑师兰特2 小时前
pnpm format 什么作用
开发语言·javascript·ecmascript
汀、人工智能2 小时前
[特殊字符] 第17课:滑动窗口最大值
数据结构·算法·数据库架构·图论·bfs·滑动窗口最大值
楼田莉子2 小时前
设计模式:设计模式的相关概念与原则
c++·学习·设计模式
yang)2 小时前
JESD 204b
运维·服务器·网络