当前位置: 首页 > news >正文

Linux多线程

目录

一、认识线程

1.1 线程概念

1.2 页表

1.3 线程的优缺点

1.3.1 优点

1.3.2 缺点

1.4 线程异常

二、进程 VS 线程

三、Linux线程控制

3.1 POSIX线程库

3.1 线程创建

3.3 线程等待

3.4 线程终止

3.4.1 return退出

3.4.2 pthread_exit()

3.4.3 pthread_cancel()

3.5 线程分离

3.6 线程ID与进程地址空间布局


一、认识线程

1.1 线程概念

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

但在创建线程时,只需创建task_struct,创建出来的task_struct和主task_struct共享进程地址空间和页表等

  • 进程里的一个执行路线就是线程(thread)。即线程是"一个进程内部的控制序列(执行分支)"
  • 所有进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行,即曾经这个进程申请的所有资源,几乎都是被所有线程共享的
  • 在Linux系统中,CPU眼中看到的PCB都要比传统的进程PCB更轻量化,也称为轻量级进程
  • 通过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

重新理解进程

下面用蓝色框起来的就是进程

进程并不是通过task_struct来衡量的,除了task_struct之外,一个进程还要有进程地址空间、文件、信号集等等,合起来称之为一个进程

站在内核角度来理解进程:承担分配系统资源的基本实体,被称为进程

当创建进程时是创建一个task_struct、创建地址空间、维护页表,然后在物理内存当中开辟空间、构建映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等。

之前接触到的进程都只有一个task_struct,也就是该进程内部只有一个执行流,即单执行流进程,反之,内部有多个执行流的进程叫做多执行流进程

Linux系统中,CPU是否能区分进程和线程?

在Linux系统中,CPU并不能区分进程与线程,因为CPU只关心一个一个的独立执行流。无论进程内部只有一个执行流还是有多个执行流,CPU都是以task_struct为单位进行调度的。即线程是CPU调度的最小单位

单执行流进程被调度:

多执行流进程被调度:

Linux中并不存在真正意义上的多线程,而是进程模拟的

操作系统中存在大量的进程,一个进程中又存在一个或多个线程,因此线程的数量一定比进程的数量多,很明显线程的执行粒度要比进程更细。

若一款操作系统要真正意义上支持线程,那么就需要对线程进行管理。比如创建线程、终止线程、调度线程、切换线程、给线程分配资源、释放资源以及回收资源等等,所有的这一套相比较进程都需要另起炉灶,搭建一套线程管理模块。

因此,若要支持真的线程一定会提高设计操作系统的复杂程度。在Linux看来,描述线程的控制块和描述进程的控制块是类似的,因此Linux并没有重新为线程设计管理模块,而是直接复用了进程控制块,即Linux中的所有执行流都是轻量级进程

但也有支持真正线程的操作系统,譬如Windows操作系统就存在专门描述线程的控制块,因此Windows操作系统系统的实现逻辑一定比Linux操作系统更为复杂

Linux中并没有真正意义上的线程系统调用

在Linux中没有真正意义上的线程,那么也就没有真正意义上的线程相关的系统调用。但Linux提供了创建轻量级进程的接口,其中最典型的代表就是vfork函数

pid_t vfork(void);

vfork函数的功能就是创建轻量级进程(只创建task_struct,父子进程共享资源)

返回值:

  • 给父进程返回子进程的PID
  • 给子进程返回0
#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
using namespace std;int g_val = 100;
int main()
{pid_t id = vfork();if (id == 0) { //childg_val = 200;cout << "child:PID:" << getpid() << " PPID:"<< getppid() << " g_val:" <<  g_val << endl;exit(0);}//fathersleep(3);cout << "father:PID:" << getpid() << " g_val:" <<  g_val << endl;return 0;
}

父进程读取到g_val的值是子进程修改后的值,也证明了vfork创建的子进程与父进程是共享地址空间的

1.2 页表

在32位平台下一共有2^{32}个地址,也就意味着有2^{32}个地址需要被映射。若页表就只是单纯的一张表,那么就需要建立2^{32}个虚拟地址和物理地址之间的映射关系,即这张表一共有232个映射表项。

每一个表项中除了要有虚拟地址和与其映射的物理地址以外,实际还需要有一些权限相关的信息,比如我们所说的用户级页表和内核级页表,实际就是通过权限进行区分的。

每个应表项中存储一个物理地址和一个虚拟地址就需要8个字节,考虑到还需要包含权限相关的各种信息,这里每一个表项就按10个字节计算。若有2^{32}个表项,也就意味着存储这张页表需要用2^{32} * 10个字节,即40GB。显而易见,内存中并存储不了这么大的一张表。

以32位平台为例,其页表的映射过程如下:

  • 选择虚拟地址的前10个bit位在页目录(一级页表)当中进行查找,找到对应的二级页表
  • 再选择虚拟地址的10个bit位在二级页表中查找,找到物理内存中对应页框的起始地址
  • 最后将虚拟地址中剩下的12个bit位作为偏移量从对应页框的起始地址处向后进行偏移,找到物理内存中某一个对应的地址

页框、页帧:

  • 物理内存实际上是被划分成一个个4KB大小的页框的(操作系统完成),而磁盘上的程序也是被划分成许多4KB大小的页帧的(编译器编译时完成),当内存和磁盘进行数据交换时(IO)就是以4KB大小为单位进行加载和保存的
  • 4KB就是2^{12}个字节,即一个页框中有2^{12}个字节,且访问内存的最小大小是1字节。因此一个页框中就有2^{12}个地址,正好使用剩下的12个bit位作为偏移量可以找到页框中任意一个字节 

每一个表项还是按10字节计算,一级页表的表项有2^{10}个,那么表的大小就是2^{10} * 10个字节,即10KB。而一级页表有2^{10}个表项也就意味着二级页表有2^{10}个,即一级页表有1张,二级页表有2^{10}张,总共算下来就是10MB左右,内存消耗并不高。而且实际运行中并不会使用所有的地址,因此页表也比预估的更小。

上面所说的所有映射过程,都是由MMU(MemoryManagementUnit)这个硬件完成的,该硬件是集成在CPU内的。页表是一种软件映射,MMU是一种硬件映射,所以计算机进行虚拟地址到物理地址的转化采用的是软硬件结合的方式。

注意: 在Linux中,32位平台下用的是二级页表,而64位平台下使用的是多级页表

1.3 线程的优缺点

1.3.1 优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要更少(CPU中存在寄存器和L1 ~ L3级缓存,进程切换会导致寄存器和缓存中的数据失效且需要重新加载,但线程切换并不需要)
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速IO操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,在多处理器系统上运行,可将计算分解到多个线程中实现从而提高执行效率
  • IO密集型应用,为了提高性能,可将IO操作重叠,使得线程可以同时等待不同的IO操作

概念说明:

  • 计算密集型(CPU密集型):执行流的大部分任务,主要以计算为主。如加密解密、大数据查找等
  • IO密集型:执行流的大部分任务,主要以IO为主。如刷盘、访问数据库、访问网络等

1.3.2 缺点

  • 性能损失: 一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。若计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失(即增加了额外的同步和调度开销,而可用的资源不变)
  • 健壮性降低: 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,即线程之间是缺乏保护的
  • 缺乏访问控制: 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
  • 编程难度提高: 编写与调试一个多线程程序比单线程程序困难得多。

若有水平较高的程序编写者,其实上述这些缺点都可以避免的

1.4 线程异常

  • 单个线程如果出现除零、野指针等问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

二、进程 VS 线程

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

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

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

  • 文件描述符表(进程打开一个文件后,其他线程也能够看到)
  • 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
  • 当前工作目录(cwd)
  • 用户ID和组ID

进程是承担分配系统资源的基本实体,线程是CPU调度的基本单位。线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器(存储每个线程的上下文信息)
  • (每个线程都有临时的数据,需要压栈出栈)
  • errno(C语言提供的全局变量,但每个线程都有自己的)
  • 信号屏蔽字
  • 调度优先级

三、Linux线程控制

3.1 POSIX线程库

在Linux中,站在内核角度上看并没有真正意义上线程相关的接口。但站在用户角度,当用户想创建一个线程时更期望使用thread_create这样类似的接口,而不是vfork函数,因此系统在应用层提供了原生线程库pthread。原生线程库实际就是对轻量级进程的系统调用进行了封装,在用户层模拟实现了一套线程相关的接口

  • 应用层指的是这个线程库并不是操作系统直接提供的,而是由第三方使用系统接口编写的
  • 原生指的是大部分Linux系统都会默认带上该线程库
  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"开头
  • 要使用pthread库,要引入头文件<pthreaad.h>
  • 链接pthread库时,要在编译时要使用"-lpthread"选项

注意:

  • 传统的函数是,成功返回0,失败返回-1,并且对全局变量errno设置以指示错误。pthread函数出错时并不会设置全局变量errno(而大部分POSIX函数会这样做),而是将错误信息通过返回值返回
  • pthread同样也提供了线程内的errno变量,以支持其他使用errno的代码。但对于pthread函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小,且线程的errno是各线程独占的

3.1 线程创建

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

参数:

  • thread:获取创建成功的线程ID,该参数是一个输出型参数
  • attr:用于设置创建线程的属性,传入NULL表示使用默认属性
  • start_routine:该参数是一个函数地址,表示线程例程,即线程启动后要执行的函数
  • arg:传给线程例程的参数(即传给start_routine的形参)

返回值:

线程创建成功返回0,失败返回错误码

使用案例

让主线程调用pthread_create函数创建一个新线程,此后新线程就会跑去执行自己的新例程,而主线程则继续执行后续代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* Routine(void* args)
{while (1) {cout << "I am " << (char*)args << endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid, NULL, Routine, (void*)"thread 1");while (1) {cout << "I am main thread!" << endl;sleep(2);}return 0;
}

使用 ps -aL 命令,可以显示当前的轻量级进程,不带 -L 选项默认显示进程

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

在Linux中,应用层的线程与内核的LWP是对应的,实际上操作系统调度时使用的是LWP,而并非PID。单线程进程时PID和LWP是相等的,所以对于单线程进程而言,调度时采用PID和LWP是一样的;多线程进程时PID与主线程LWP相同。

3.3 线程等待

线程如同进程一般,也是需要被等待的。若主线程不对新线程进行等待,那么新线程的资源不会被回收,会发生类似于"僵尸进程"的问题,即内存泄漏。

使用pthread_join()可以进行线程等待

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

参数:

  • thread:被等待线程的ID
  • retval:线程退出时的退出码信息

返回值:

  • 线程等待成功返回0,失败返回错误码

调用该函数的线程将阻塞到ID为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的

  • 若thread线程通过return返回,retval所指向的单元里存放的是线程的返回值
  • 若thread线程被别的线程调用pthread_cancel()异常终止掉,retval所指向的单元里存放的是宏PTHREAD_CANCELED,即(void*)-1) 
  • 若thread线程是自行调用pthread_exit()终止的,retval所指向的单元存放的是传给pthread_exit的参数
  • 若对thread线程的终止状态不感兴趣,可传NULL给retval参数

使用案例

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* Routine(void *args)
{cout << (char*)args << endl;sleep(3);return (void*)0;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, Routine, (void*)"new thread");void* ret = nullptr;int n = pthread_join(tid,&ret);if(n == 0) {cout << "等待成功" << endl;cout << "返回信息为: " <<(long long)ret << endl;}else {cout << "等待失败" << endl;}return 0;
}

3.4 线程终止

3.4.1 return退出

在创建线程时指定的例程中使用return代表当前线程退出,但在main函数中使用return代表整个进程退出,即主线程退出了那么整个进程就退出了。

3.4.2 pthread_exit()

void pthread_exit(void *retval);

参数retval:线程退出时的退出信息

注意:

  • pthread_exit()或者return返回的指针所指向的内存单元必须是全局的或者堆区开辟的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程已经退出了
  • 线程退出不能使用exit()函数,其作用是退出整个进程,任何一个线程调用都是如此

3.4.3 pthread_cancel()

int pthread_cancel(pthread_t thread);

参数thread:被取消线程的ID

返回值:线程取消成功返回0,失败返回错误码

线程是可以取消自己的(使用pthread_self()函数)。也可以让新线程取消主线程,但不建议这么使用,一般都是使用主线程去控制新线程的。

取消成功的线程的退出码一般是宏PTHREAD_CANCELED,即(void*)-1) 

3.5 线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成内存泄漏。但若本身并不关心线程的返回值,那么join也是一种负担,此时可将该线程进行分离,后续当线程退出时就会自动释放线程资源
  • 线程若被分离了,这个线程依旧使用该进程的资源,且依旧在该进程内运行,甚至这个线程崩溃了一定会影响整个进程,只不过这个线程退出时不再需要主线程去join了,当这个线程退出时系统会自动回收该线程所对应的资源
  • 可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
  • joinable和分离是冲突的,一个线程不能既是joinable又是分离的

使用pthread_detach()函数进程分离线程

int pthread_detach(pthread_t thread);

参数thread:被分离线程的ID

返回值:线程分离成功返回0,失败返回错误码

3.6 线程ID与进程地址空间布局

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

线程ID到底是什么?

可以将线程ID打印出来看看

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* Routine(void* args) {cout << (char*)args << " : " << pthread_self() << endl;return (void*)0;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,Routine,(void*)"new thread");sleep(1);cout << "main thread : " << pthread_self() << endl;pthread_join(tid,nullptr);return 0;
}

可以发现,这个线程ID数值特别大,并不是LWP,那么这个线程ID到底是什么呢?

Linux系统中不提供真正的线程ID,只提供LWP,即操作系统只需通过LWP对轻量级进程进行管理,而供用户使用的线程接口等其他数据,由线程库来管理,因此管理线程时的"先描述,再组织"就应该在线程库中完成

使用 lld 命令可以看到,线程库实际上是一个动态库(默认使用动态库)

进程运行时动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时进程内的所有线程是共享这个动态库的

之前提到每个线程都有独占的栈,其中主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是在共享区中开辟的。除此之外,每个线程都有各自的struct pthread,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。

每一个新线程在共享区都有一个struct pthread对其进行描述,因此要找到一个用户级线程只需要找到该线程内存块的起始地址,然后就可以获取到该线程的各种信息

上面讲述的各种线程函数,本质上都是在库内部对线程属性进行的各种操作,即线程数据的管理本质是在共享区的进行的

至于pthread_t到底是什么类型取决于实现,但对于Linux目前实现的NPTL线程库来说,线程ID本质就是进程地址空间共享区上的一个虚拟地址,同一个进程中所有的虚拟地址都是不同的,因此可以用它来唯一区分每一个线程

相关文章:

Linux多线程

目录 一、认识线程 1.1 线程概念 1.2 页表 1.3 线程的优缺点 1.3.1 优点 1.3.2 缺点 1.4 线程异常 二、进程 VS 线程 三、Linux线程控制 3.1 POSIX线程库 3.1 线程创建 3.3 线程等待 3.4 线程终止 3.4.1 return退出 3.4.2 pthread_exit() 3.4.3 pthread_cancel…...

Webpack5 环境下 Openlayers 标注(Icon) require 引入图片问题

Webpack5 环境下 Openlayers 标注&#xff08;Icon&#xff09; require 引入图片问题环境版本Openlayers 使用 require 问题Webpack5 正确配置构建新环境的时候&#xff0c;偶然发现 Openlayers 使用 require 的方式加载图片&#xff08;Icon&#xff09;报错&#xff0c;开始…...

Zookeeper安装部署

文章目录Zookeeper安装部署Zookeeper安装部署 将Zookeeper安装包解压缩&#xff0c; [rootlocalhost opt]# ll 总用量 14032 -rw-r--r--. 1 root root 12392394 10月 13 11:44 apache-zookeeper-3.6.0-bin.tar.gz drwxrwxr-x. 6 root root 4096 10月 18 01:44 redis-5.0.4 …...

Cow Acrobats ( 临项交换贪心 )

题目大意&#xff1a; N 头牛 &#xff0c; 每头牛有一个重量(Weight)和一个力量(Strenth) &#xff0c; N头牛进行排列 &#xff0c; 第 i 头牛的风险值为其上所有牛总重减去自身力量 &#xff0c; 问如何排列可以使最大风险值最小 &#xff0c; 求出这个最小的最大风险值&am…...

MySQL:为什么说应该优先选择普通索引,尽量避免使用唯一索引

前言 在使用MySQL的过程中&#xff0c;随着表数据的逐渐增多&#xff0c;为了更快的查询我们需要的数据&#xff0c;我们会在表中建立不同类型的索引。 今天我们来聊一聊&#xff0c;普通索引和唯一索引的使用场景&#xff0c; 以及为什么说推荐大家优先使用普通索引&#xf…...

Spring Cloud alibaba之Feign

JAVA项目中如何实现接口调用&#xff1f;HttpclientHttpclient是Apache Jakarta Common下的子项目&#xff0c;用来提供高效的、最新的、功能丰富的支持Http协议的客户端编程工具包&#xff0c;并且它支持HTTP协议最新版本和建议。HttpClient相比传统JDK自带的URL Connection&a…...

零信任-Google谷歌零信任介绍(3)

谷歌零信任的介绍&#xff1f; "Zero Trust" 是一种网络安全模型&#xff0c;旨在通过降低网络中的信任级别来防止安全威胁。在零信任模型中&#xff0c;不论请求来自内部网络还是外部网络&#xff0c;系统都将对所有请求进行详细的验证和审核。这意味着每次请求都需…...

Numpy基础——人工智能基础

文章目录一、Numpy概述1.优势2.numpy历史3.Numpy的核心&#xff1a;多维数组4.numpy基础4.1 ndarray数组4.2 内存中的ndarray对象一、Numpy概述 1.优势 Numpy(Nummerical Python),补充了Python语言所欠缺的数值计算能力&#xff1b;Numpy是其它数据分析及机器学习库的底层库&…...

电商仓储与配送云仓是什么?

仓库是整个供给链的关键局部。它们是产品暂停和触摸的点&#xff0c;耗费空间和时间(工时)。空间和时间反过来也是费用。经过开发数学和计算机模型来微调仓库的规划和操作&#xff0c;经理能够显著降低与产品分销相关的劳动力本钱&#xff0c;进步仓库空间应用率&#xff0c;并…...

【零基础入门前端系列】—HTML介绍(一)

【零基础入门前端系列】—HTML介绍&#xff08;一&#xff09; 一、什么是HTML HTML是用来描述网页的一种语言HTML指的是超文本标记语言&#xff1a;HyperText Markup LanguageHTML不是一种编程语言&#xff0c;而是一种超文本标记语言&#xff0c;标记语言是一套标记标签(ma…...

Elasticsearch索引库和文档的相关操作

前言&#xff1a;最近一直在复习Elasticsearch相关的知识&#xff0c;公司搜索相关的技术用到了这个&#xff0c;用公司电脑配了环境&#xff0c;借鉴网上的课程进行了总结。希望能够加深自己的印象以及帮助到其他的小伙伴儿们&#x1f609;&#x1f609;。 如果文章有什么需要…...

使用Python,Opencv检测图像,视频中的猫

使用Python&#xff0c;Opencv检测图像&#xff0c;视频中的猫&#x1f431; 这篇博客将介绍如何使用Python&#xff0c;OpenCV库附带的默认Haar级联检测器来检测图像中的猫。同样的技术也可以应用于视频流。这些哈尔级联由约瑟夫豪斯&#xff08;Joseph Howse&#xff09;训练…...

浅谈域名和服务器集约化管理的误区

一个正常的网站通常由域名、网站程序、服务器三个部分组成&#xff0c;网站程序由单位开发设计&#xff0c;而域名和服务器则需要租用购买&#xff0c;那么域名和服务器之间的关系是什么&#xff1f;如何实现域名和服务器的有效管理呢&#xff1f; 服务器和域名的关系 服务器…...

迪赛智慧数——柱状图(正负条形图):20212022人才求职最关注的因素

效果图从近两年职场跳槽方向看&#xff0c;相比此前人们对高薪大厂趋之若鹜&#xff0c;如今职场人更关注业务前景。根据相关数据显示&#xff0c;职场人求职最关注的因素中&#xff0c;“薪资福利”权重下降&#xff0c;“个人发展”权重上升&#xff0c;“业务前景”首次进入…...

网络安全-黑帽白帽红客与网络安全法

网络安全-黑帽白帽红客与网络安全法 本章内容较少&#xff0c;因为刚开端。 黑客来源于hacker 指的是信息安全里面&#xff0c;能够自由出入对方系统&#xff0c;指的是擅长IT技术的电脑高手 黑帽黑客-坏蛋&#xff0c;研究木马的&#xff0c;找漏洞的&#xff0c;攻击网络或者…...

Xpath元素定位之同级节点,父节点,子节点

XPath学习:轴(8)——following-siblingXPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素&#xff0c;并且 XQuery 和 XPointer 同时被构建于 XPath 表达之上。推荐一个挺不错的网站&#xff1a;htt…...

华为OD机试 - 挑选字符串(Python)| 真题+思路+代码

挑选字符串 题目 给定 a-z,26 个英文字母小写字符串组成的字符串 A 和 B, 其中 A 可能存在重复字母,B 不会存在重复字母, 现从字符串 A 中按规则挑选一些字母可以组成字符串 B 挑选规则如下: 同一个位置的字母只能挑选一次, 被挑选字母的相对先后顺序不能被改变, 求最…...

python笔记-- “__del__”析构方法

-#### 1、基本概念&#xff08;构造函数与析构函数&#xff09; 特殊函数&#xff1a;由系统自动执行&#xff0c;在程序中不可显式地调用他们 构造函数&#xff1a; 建立对象时对对象的数据成员进行初始化&#xff08;对象初始化&#xff09; 析构函数&#xff1a; 对象生命期…...

支付系统核心架构设计思路(万能通用)

文章目录1. 支付系统总览核心系统交互业务图谱2. 核心系统解析交易核心交易核心基础交易类型抽象多表聚合 & 订单关联支付核心支付核心总览支付行为编排异常处理渠道网关资金核算3. 服务治理平台统一上下文数据一致性治理CAS校验幂等 & 异常补偿对账准实时对账DB拆分异…...

python实现mongdb的双活

如何用python实现mongdb的双活&#xff0c;两个数据库实时同步&#xff1f; 可以使用Pymongo库&#xff0c;它可以提供同步的API来实现MongoDB的双活&#xff0c;两个数据库实时同步。还可以使用MongoDB的复制集功能来进行实时同步。 Pymongo库提供什么同步的API来实现MongoD…...

LeetCode-110. 平衡二叉树

目录题目分析递归法题外话题目来源 110. 平衡二叉树 题目分析 平很二叉树:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 二叉树节点的深度和二叉树节点的高度 递归法 递归三步曲 1.明确递归函数的参数和返回值 参数&#xff1a;当前传入节点。 返回值…...

Python蓝桥杯训练:基本数据结构 [链表]

Python蓝桥杯训练&#xff1a;基本数据结构 [链表] 文章目录Python蓝桥杯训练&#xff1a;基本数据结构 [链表]一、链表理论基础知识二、有关链表的一些常见操作三、力扣上面一些有关链表的题目练习1、[移除链表元素](https://leetcode.cn/problems/remove-linked-list-element…...

华为OD机试 - 找字符(Python)| 真题+思路+代码

找字符 题目 给定两个字符串, 从字符串2中找出字符串1中的所有字符, 去重并按照 ASCII 码值从小到大排列。 输入 字符范围满足 ASCII 编码要求, 输入字符串1长度不超过1024, 字符串2长度不超过100。 输出描述 按照 ASCII 由小到大排序 示例一 输入 bach bbaaccddf…...

使用继承与派生的6大要点

概述 面向对象编程技术非常看重软件的可重用性&#xff0c;在C中&#xff0c;可重用性是通过继承机制来实现的。继承机制允许程序员在保持原有类的数据和功能的基础上进行扩展&#xff0c;增加新的数据和功能&#xff0c;从而构成一个新的类&#xff0c;也称为派生类。原有类&a…...

加一-力扣66-java高效方案

一、题目描述给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。示例 1&#xff1a;输入&#xff1a;di…...

记一次 .NET 某游戏网站 CPU爆高分析

一&#xff1a;背景 1. 讲故事 这段时间经常有朋友微信上问我这个真实案例分析连载怎么不往下续了&#xff0c;关注我的朋友应该知道&#xff0c;我近二个月在研究 SQLSERVER&#xff0c;也写了十多篇文章&#xff0c;为什么要研究这东西呢&#xff1f; 是因为在 dump 中发现…...

集群使用——资源管理和租户创建

概述 OceanBase 数据库是多租户的分布式数据库&#xff0c;租户使用的资源建立在资源池上。资源池包含了资源单元&#xff0c;而资源单元则规定了具体资源的量化&#xff08;如 CPU、Memory、Disk_Size 和 IOPS 等&#xff09;。 创建租户前&#xff0c;必须规定租户使用的资源…...

谷歌浏览器登录失败,提示【无法同步到“...@gmail.com”】

首先安装Chrome同步助手&#xff08;Chrome-Sync-Helper&#xff0c;看了很多博客&#xff0c;谷歌浏览器同步问题好像都要用这个&#xff09;&#xff0c;改成.rar&#xff0c;解压&#xff0c;文件夹_metadata重命名为metadata&#xff0c;然后添加到谷歌浏览器的扩展程序中。…...

75 111111

选择题(共75题,合计75.0分) 1. 选项ABCD中显示了所创造的商业价值以及在产品中实施各种功能需要进行的开发工作。团队应优先实施哪项功能&#xff1f; The business value created and the development effort needed to implement the various features in the product are sh…...

分销系统逻辑

相关概念 主营商户: 提供分销商品和佣金的商户分销商: 拥有自己的销售渠道&#xff0c;能够帮助推动产品销售的个人或商户消费者: 购买分销商品的人。佣金: 主营商户返还给经销商的比例抽成 分销功能设计 (1)分销商准入规则设计 无规则: 没有分销商的准入门槛限制&#xf…...

sae wordpress 3.9/建网站的详细步骤

注意&#xff1a;本文是在乌班图和Windows10环境下配置&#xff0c;Centos与乌班图略有不同&#xff0c;就是Centos的MySQL配置文件路径为/etc/my.cnf,其他操作一致1. 主从同步的定义主从同步使得数据可以从一个数据库服务器复制到其他服务器上&#xff0c;在复制数据时&#x…...

手机网站app制作/天津百度推广电话号码

修复此问题 禁用内核调试&#xff0c;在 Visual Studio 中调试。- 或 -使用内核调试器而不是 Visual Studio 进行调试。- 或 -在内核调试器中禁用用户模式异常。在当前会话中禁用内核调试 在命令提示处&#xff0c;键入&#xff1a; 复制代码 Kdbgctrl.exe -d对所有会话禁用内核…...

网络营销网站的建设与策划/济南优化网络营销

Django处理一个请求 项目启动后根据 settings ROOT_URLCONF 决定项目根URLconf urlpatterns是django.conf.urls.url()实例的一个Python列表 Django依次匹配每个URL模式&#xff0c;匹配成功后就停止 Django匹配成功&#xff0c;调用相应视图函数(或一个基于类的视图)&#…...

wordpress 主题 结构/推广点击器

iOS常用传值小结 ************************************* 最简单的用第二个界面的label来显示第一个界面的textField中的文本 (一)属性传值----前向后传值 1.我们首先要在RootViewController的基础上创建一个DetailViewController&#xff0c;然后我们要记住传值过程中用到什么…...

找外包公司做网站给源码吗/网课免费平台

点击上方“蓝色字”可关注我们&#xff01;暴走时评&#xff1a;欧盟&#xff08;EU&#xff09;对分布式账本技术&#xff08;DLT&#xff09;的“前景和挑战”进行了数月的监测和观察&#xff0c;最终决定进入区块链行业。2018年&#xff0c;欧盟对该领域发起了多个倡议和决议…...

哪个网站开发培训好/网络搜索引擎有哪些

对于矩形孔径&#xff0c;空间谱是一个三角形函数&#xff0c;具有两倍孔径的支撑区间。 For the rectangularaperture, it is a triangle function with a support of twice the aperture width. 其原因很容易看出&#xff1a;单程电压方向图只是孔径函数的反傅立叶变换&…...