学习系统编程No.29【线程执行过程之页表详解】

引言:

北京时间:2023/7/3/14:09,刚睡醒,放假在家起床时间确实不怎么好调整,根本固定不了一点,当然通俗点说也就是根本起不来,哈哈哈,已经很少见到那种7点起来码字的情形了,再加上在家里,琐碎的事情很多,不像是在学校,除了睡觉吃饭就没什么其它事情需要我去花费精力,哎,感叹!加上在家吃饭时间比较慢,间接影响了我的睡觉时间,一般想在11点左右睡觉,当时又是码字状态最好的时候,睡不了一点,直接导致睡的比较迟,这点有待改善,从今天开始,刚好由于昨天更文了,今天不强制更文,所以今天11点前必须躺平睡觉,然后对于更文和学习进度上,目前顺其自然吧!毕竟我们时间充足,前期还是走稳一些较好,虽然还需要进行刷题,但是毕竟还有时间,莫慌,到时候船到桥头,自然也就直了,咱还是继续慢悠悠的向前走吧!该篇博客我们就来详谈一下进程地址空间和物理内存之间具体是如何通过页表进行映射,当然重点还是多线程相关知识。

在这里插入图片描述

深入线程概念

上篇博客的标题是多线程概念,有关线程的基本概念我们都讲解完了,但是由于时间原因,并没有进行深入的学习,所以在学习有关页表映射,线程创建和控制等知识前,我们先来补充一下线程概念相关知识。

1.如何理解之前学习的进程
在学习了线程之后,我们明白进程并不是CPU的执行单位,线程才是,那么此时应该如何理解以前学习的进程呢?首先明白,两者并没有任何的冲突,而是互相补充,也就是以前在学习进程时,由于进程知识复杂,所以并没有展开讲解内部与线程相关的知识,而只是从表面单一的执行流去学习它。其次明白,以前的进程是内部只有一个task_struct的进程,也就是可以理解为是内部只有一个执行流(线程)的进程,而今天我们学习的进程,内部不仅不止只有一个执行流,而是有多个执行流,并且这些执行流共享该进程中的所有资源(时间片,地址空间等)。明白了这点之后,接下来我们再谈谈操作系统对于线程的实现,我们就正式进入多线程深入学习。

2.不同操作系统对于线程的实现是否相同?
首先答案肯定是不同的,按照最普遍的两种操作系统(Windows和Linux)来说,它们之间对于线程的实现就大为不同,具体不同原因如下:

  • Windows
    操作系统要不要管理线程,答案肯定是肯定的,同理,谈到计算机中的管理,不过就是先描述,后组织而已,如同操作系统管理进程一般,将所有的进程都描述成了对应的进程pcb(task_struct),然后对进程pcb进行管理,同理,操作系统对于线程的管理也是将线程先描述成一个TCB(task_struct),然后对TCB进行管理,当然由于线程是根据进程pcb创建,那么其TCB中的数据肯定没有pcb那么复杂,一般包含线程标识符,对应的上下文、栈空间、所述进程、线程状态、寄存器集合等数据!所以此时当线程和进程一样,拥有了自己的控制块,那么操作系统就可以像管理进程一样去管理线程,同理完成进程调度,也可以完成线程调度等工作,但是由于进程的数量已经非常多,那么线程的数量肯定更多,此时如果在管理进程的同时,又去管理线程,那么就会导致操作系统在运行时,非常的复杂,程序设计起来也没有那么简单,所以按照这种方法设计操作系统难度较大,当然我们现在使用的Windows操作系统(内部有真线程)就是按照这种方法来设计的。

  • Linux
    Linux操作系统在设计进程和线程时,就发现,进程有PCB,线程有TCB,进程需要被调度,线程也需要被调度,并且线程的TCB是根据进程的PCB设计,那么此时Linux系统在设计时,那么世界顶级的程序员就让线程直接去复用PCB结构体,用PCB模拟线程的TCB,这样线程的数据结构和管理方式我就不需要重新设计,并且相应的线程调度算法也不需要重新设计,而是直接复用进程的设计方案就行,所以对于Linux来说,系统内部没有真正意义上的线程,而是使用进程来模拟线程(区分Windows系统有真正的线程)的方法设计。

总而言之,Windows系统对于线程的设计方法非常的朴实无华,而Linux则更加的巧妙,当然两者可以说是各有千秋,但是从代码维护和代码复用性、安全性上来看,Linux的设计方法肯定更优,所以这也就导致Linux系统可以不间断运行,而Windows系统不行。

3.那么Linux系统中的线程到底是什么呢?
明白了上述知识,此时我们就知道Linux系统中只有进程并没有真线程,所以在CPU看来,虽然执行的都是task_struct,但是此时的task_struct和Windows系统中的task_struct肯定是有区别的,因为Windows中的进程不仅是一个执行流,其中还包括了很多的线程,而对于Linux来说,Linux中由于没有真正的线程,所以把进程和线程的概念统称为任务,CPU在执行时,执行的不是线程也不是进程,而是任务,并且该任务也被称为轻量级进程(Lightweight Process)。

使用代码看看Linux系统下的线程

明白了上篇博客有关线程的概念和上述有关知识,一切都还是基于概念上的纸上谈兵而已,具体到底是怎样的,还有待探究,如下图所示,就是使用对应线程相关接口,创建线程,然后查看Linux系统中对应线程之间的关系(ps -aL),如下:
在这里插入图片描述
如上图所示,此时我们就可以很好的看出,这四个执行流(线程)属于同一个进程(8541),并且除了PID之外,还有一个LWP,此时这个LWP代表的就是轻量级进程的标识符,并且其中一个轻量级进程的LWP和PID相同,也就是表示该轻量级进程就是所谓的主线程。

上述代码中值得注意的地方有两点:其一是pthread_create接口的用法,功能创建一个线程,头文件:pthread.h,基本使用方式:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);简单使用就是如上述代码一样,这里不详解讲解该接口,其二是在Linux操作系统下执行相应线程代码时,由于pthread.h头文件并不是Linux系统的默认动态库,所以在执行相应pthread中的接口时,需要包含对应的静态库(libpthread.a),具体执行方式:g++ -o test -lpthread thread.cpp -std=c++11

详谈虚拟到物理之间的转换过程

搞定了上述有关线程概念相关知识的补充,此时我们就再来谈谈虚拟地址空间到物理内存之间的转换过程,当然,这块知识和我们学习线程息息相关,因为涉及到了线程的整体执行过程,虽然之前在学习虚拟地址空间和进程相关知识时,我们已经了解了大致过程,但是不够具体,并且有关页表映射相关的知识我们也没有详谈,所以接下来就让我们通过讲解页表映射,来回顾一下进程的整体执行过程吧!

页表映射问题

无论是在之前学习进程相关知识,还是学习虚拟地址空间,我们明白虚拟地址空间的设计本质是为了配合进程,当然也就是为了完善整个体系结构,在提高效率的同时,使系统更加安全稳定。并且我们明白,虚拟地址空间本质并不能存储任何数据,它起到的作用只是一个桥梁作用,让进程或者是线程可以通过虚拟地址空间中对应的地址访问到物理内存中对应的数据,从而执行相应的代码,当然上述理解在概念上并没有什么问题,但是当我们深入理解,认识了什么是页表之后,上述概念就有问题,因为进程虽然只有通过虚拟地址空间上的地址才能访问到物理内存中的数据,但虚拟地址空间本质是通过页表的映射才找到对应的物理地址,所以从这点看来,页表才是进程能够访问到物理内存中数据的桥梁。明白了这点之后,今天我们就来谈谈页表到底是如何实现虚拟地址空间到物理内存的映射吧!

1.那么页表应该有多大呢?
通过上述知识,我们知道,页表是进程找到物理地址的桥梁,并且我们也知道虚拟地址空间的大小默认是4GB,当然这个大小取决于我们电脑的体系结构是32位寻址还是64位寻址,当然对于目前市面上来说,电脑都是64位的操作系统,但是对于我们学习知识来讲,我们以32位为例,假如此时我们的物理内存也是4GB,那么就会面临一个问题,当然也就是页表映射问题,页表应该如何设计才能让虚拟地址空间上2^32的地址映射到物理内存上呢?首先,页表不可能是直接映射,也就是不可能和虚拟地址空间、物理内存一样,拥有那么大的空间,因为想要实现直接映射的话,那么页表就需要有48GB的空间,如下图所示:
在这里插入图片描述
显然,我们的页表不可能拥有48GB的存储空间,所以页表不可能是采用直接映射的方法,实现虚拟地址空间到物理内存的映射,那么页表到底是使用什么方法,来实现映射的呢?想要明白这个点,此时我们就需要先复习一下有关IO的知识,如下所述:

2.复习相关IO知识
明白,当我们需要高频的从磁盘中加载数据到内存中时,由于磁盘的工作原理,它只有进行相应的机械运动,才可以将磁盘上的数据加载到物理内存中,那么就注定这个过程的效率非常低效,所以对于我们的计算机体系结构来说,肯定是不允许这种情况的发生,此时在设计对应磁盘加载数据到内存中时,就应该减少加载数据的频率,增大一次加载数据的大小,减少寻址,当然也就是减少机械运动,从而提高效率。具体如何减少磁盘数据加载到物理内存,如下所示:

所以体系结构就规定操作系统,当磁盘需要加载数据到内存中时,一次只加载4KB,无论你想要从磁盘中加载多大的数据到内存,磁盘每次都是按照4KB来加载,当然这个知识点和我们的局部性原理有一定的关系,这里我们先不做详解,那么此时就会面临一个问题,为什么是4KB不是8KB或者16KB呢?面对这个问题,在学习过文件系统,了解了磁盘的工作原理之后,对于我们来说并不是什么大问题,本质就是因为,在磁盘中,数据是以块为单位进行存储,当然磁盘中一个块也就是4KB大小,所以对于磁盘来说,在将数据加载到内存时,就是通过一个块一个块的加载,这也就是导致为什么每次都加载4KB的直接原因。当然这样的设计方法,好处肯定是巨大的,不仅可以减少机械运动,而且可以让每一块数据都和操作系统规定的大小相匹配,从而大大提高加载速度,不需要进行二次切割等操作。同理,对于物理内存来说,它在保存磁盘加载的数据时,也是按照4KB大小来保存,虽然内存中是以字节为单位,但是操作系统在管理物理内存时,也是通过一个一个4KB大小的内存单元进行管理,所以在这样的设计下,磁盘和内存之间的交互效率就得到很大的提高,并且对于物理内存中对应的4KB数据块,我们也称为页框,对于磁盘中对应的4KB数据块,我们也称为页帧 ,对于物理内存中页框的管理我们采用的是struct page结构体,对于所有页框的管理,我们使用的是struct page mem[]数组,且struct page结构体中一般包含的属性非常的少,因为该结构体本质只是用于判断该页框是否被占用(status),而struct page mem[]数组的使用就更加的简单,本质就是通过该数组对应的下标找到没有被占用的那一个页框(遍历),然后将磁盘中对应加载的4KB数据保存起来而已。具体如下图所示:
在这里插入图片描述
总:内存管理的本质就是将磁盘中特定的4KB数据块内容加载到某一个物理内存的4KB空间中。

3.再谈局部性原理
明白了上述知识,此时我们就知道,由于磁盘加载数据到内存的过程一定是按照4KB的大小,也就是以块为单位来加载,以块为单位进行保存,那么此时我们就会遇到一个问题,当然这个问题和我们的局部性原理息息相关,也就是当我们只需要使用几个字节的数据时,如果再从磁盘中一次性加载4KB数据到内存,那么就非常的浪费效率,那么为什么还要这样设计呢?当然这样设计肯定是有好处的,无论是除了上述减少机械运动之外,这样设计还有另一个好处,也就是为什么不担心我只使用10字节的数据,而照样加载4KB,因为在我们的电脑中存在局部性原理,也就是允许我们提前加载正在访问数据相邻或者附近的数据到内存中! 所以因为有局部性原理的存在,操作系统本身就算你只使用10字节的数据,它也会将对应数据附近的数据也加载到内存中,因此就算是只用10字节数据,我们一次性将该数据对应的块数据(4KB)加载到内存也是非常合理的,既满足效率问题,也符合局部性原理的概念,一石二鸟。局部性原理的本质: 进行数据的预加载,当将来想要执行该代码附近的代码时,CPU很大概率会直接命中内存中预加载的数据,就不需要再通过磁盘加载数据,大大提高效率。

明白了上述局部性原理相关知识,我们就可以明白,为什么一个占用内存非常大的大型游戏,可以被运行起来了,也就是一个可执行程序,它需要占用非常大的内存才能被执行,普通电脑的内存根本装不下,所以我们明白,想要让该可执行程序执行起来,就需要让它在执行的时候,一部分一部分的执行,也就是在加载的过程中,只加载一部分,游戏运行到哪里,它就加载哪部分的可执行程序,本质就是在不断切换内存中相应区段的可执行程序,从而让该游戏一直可以运行下去,而这个一部分一部分执行代码的过程,肯定就离不开我们上述所说的局部性原理。

4.页表映射问题详解
搞定了上述1、2、3知识点,此时我们正式来看看页表映射问题,也就是页表到底是如何实现地址空间到物理内存的映射,如下图所示:
在这里插入图片描述
通过上图,此时我们可以看出,当我们想要将地址空间中的某一个地址映射到物理内存中时,此时需要通过页表上对应的索引关系,才能让虚拟地址转换为物理地址,如上图所示,我们将任意一个虚拟地址(32位)分为了三部分(10+10+12),其中前10个比特位表示的是一级页表中对应的1024个地址,并且由于页表本质是一种K/V关系的数据结构(索引),所以这1024个地址表示的就是如上图中所示的1024个大页地址,目的也就是通过某个虚拟地址的前10个比特位,找到1024大页地址中唯一的大页地址,并且此时这个大页地址,我们也称为二级页表,同理,这个二级页表由10个比特位构成,通过这10个比特位,我们同理就能找到1024个页框地址中唯一的那个页框地址,最终根据虚拟地址的第三部分,也就是最后12个比特位,找打该页框中对应的唯一物理地址(1个字节),成功将虚拟地址通过页表映射为物理地址。并且明白:上述通过二级页表寻找物理地址的过程是一个起始地址+偏移量的过程,类似我们之前学习过的寻找某一种类型占用的物理地址(编译器只提供我们起始地址,也就是一个字节,然后根据类型,也就是偏移量,找到对应数据对应所占的空间)。

搞定了上述页表映射问题,我们也就解决了页表应该有多大的问题,因为页表是被存储在物理内存中,所以按照上述的原理也就是占用了1024个2^10空间,当然因为一个地址占用4个字节,此时页表的大小就是4MB,相比于之前的48GB,可以说是非常小了,当然对于内存来说,也是非常小的。但是由于页表结构是建立了映射关系之后,才去开辟空间,也就是虚拟地址空间上有多少地址,我才映射多少地址,并不会把所有地址都给先创建好,所以一般页表结构只需要几个字节或者几十个字节就能搞定,具体页表的大小,是由我们的程序,也就是的代码,当然也就是虚拟地址空间上的地址数量等因素决定的。

此时值得注意的是:因为一个页框为4KB,也就是4096个字节,所以在通过虚拟地址最后12个比特位作为偏移量去寻址的时候,每一个字节的地址都是刚好相匹配的,千万不能将这里的地址数,也就是字节数给理解成了1024,否则就出问题了。

总而言之:当进程访问到一个虚拟地址时,也就是CPU在访问某一句代码时,CPU会将对应的虚拟地址发送给集成在CPU内部的内存管理单元(MMU)处理,由于内存管理单元中存储着一级页表(页表目录)的物理地址,所以在内存管理单元接收到对应的虚拟地址之后,内存管理单元(MMU)首先是将对应物理地址的页表(页表目录)加载进来,其次才是根据内存管理单元接收到的虚拟地址的高位(前10个比特位)和页表目录找到对应二级页表(页表项)的物理地址,然后再根据页表项中对应的索引(K/V)和虚拟地址的最后12个比特位找到特定的物理地址,最终进程就可以使用该物理地址读取或者写入数据啦!

什么是缺页中断
搞定了上述过程,也就是进程如何通过页表找到对应物理内存中数据的过程,理解缺页中断,对于我们来说,不过只是多理解一个概念,或者是理解一个现象而已,顺水推舟,结合之前的编码经验,我们知道,我们编码过程中在什么栈区、堆区、静态区开辟空间,本质都只是在虚拟地址空间上申请了一个地址而已,同理,从操作系统资源管理方面来看,操作系统不可能因为我们编码过程中申请了一个虚拟地址,它就把这个地址或者说这个地址对应的数据映射到内存中去,这样会导致内存资源的浪费,所以只有当我们完成了编码,生成了可执行程序,运行可执行程序之后,操作系统才会自动帮我们向内存申请空间,那么此时问题也就来了,当然也就是缺页中断概念来了,操作系统怎样自动帮我们向内存申请空间呢?从概念上来讲,这个申请空间的过程我们就叫做缺页中断,详情如下:

有了上述缺页中断的概念,那么我们就意识到,在通过页表建立映射关系时,不是先有映射关系,而是因为有缺页中断,才有映射关系,也就是说,当MMU接收到虚拟地址加载完页表之后,准备映射时,发现对应页表中并没有相应的索引关系,也就是物理内存中没有匹配的空间,那么此时MMU就会触发缺页中断,当触发了中断之后,操作系统就会去执行对应的处理方法,当然此时也就是去执行申请内存的操作。同理,缺页中断除了可以触发操作系统自动申请内存外,它还是让磁盘中的数据自动加载到内存中,也就是当访问内存中的数据时,检测到内存中并没有对应的匹配数据,同理发生缺页中断,操作系统执行相应处理方法,最终将磁盘中的数据加载到内存中。

深入分析线程

搞定了上述知识,无论是进程还是线程,它们的执行过程我们就都搞定了,接下来让我们分析一下线程的优点和缺点,通过分析线程的优缺点,将线程概念相关知识彻底搞懂!如下:

线程的优点

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

线程的缺点

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

有关线程优缺点知识的分析,我们留到下篇博客,由于时间问题,该篇博客就这样吧!

总结:有关线程概念和线程整体执行过程等问题,我们就彻底搞定啦!下篇博客我们正式进入线程控制讲解,See you!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UART-GD32

UART-GD32 通信的概念 同步通信和异步通信 数据帧格式 波特率 使用步骤 引脚分布

gitLab配置ssh实现私钥访问

1.配置ssh文件 1.cd C:\Users\用户名\.ssh 找到文件夹 删除.ssh 里面所有其他文件方面我们配置要最新的 2.win r cmd 呼出命令行 ssh-keygen -t rsa -C "必须对应gitLab用户名" 3.生成文件夹拿到ssh 4.复制id_rsa_pub 文件的全部字符串 公钥给到GitLab服务器 2.公…

Spring Boot 中的模板引擎是什么,如何使用

Spring Boot 中的模板引擎是什么,如何使用 在 Web 应用程序中,模板引擎是一种用于动态生成 HTML、XML、JSON 等文档的工具。Spring Boot 内置了多种常见的模板引擎,例如 Thymeleaf、Freemarker、Velocity 等,让我们可以轻松地创建…

线性代数行列式的几何含义

行列式可以看做是一系列列向量的排列,并且每个列向量的分量可以理解为其对应标准正交基下的坐标。 行列式有非常直观的几何意义,例如: 二维行列式按列向量排列依次是 a \mathbf{a} a和 b \mathbf{b} b,可以表示 a \mathbf{a} a和…

Lua学习笔记:浅谈对垃圾回收的理解

前言 本篇在讲什么 Lua的垃圾回收 本篇适合什么 适合初学Lua的小白 本篇需要什么 对Lua语法有简单认知 依赖Sublime Text编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 ★提高阅读体验★ &#x1f…

3、boostrap图片视频上传展示

boostrap图片视频上传展示 1、展示效果2、html代码 1、展示效果 项目目录结构 2、html代码 html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!--<link rel"st…

记一次 .NET 某工控视觉系统 卡死分析

一&#xff1a;背景 1. 讲故事 前段时间有位朋友找到我&#xff0c;说他们的工业视觉软件僵死了&#xff0c;让我帮忙看下到底是什么情况&#xff0c;哈哈&#xff0c;其实卡死的问题相对好定位&#xff0c;无非就是看主线程栈嘛&#xff0c;然后就是具体问题具体分析&#x…

一起来看看文档翻译哪个好吧

在繁忙的都市生活中&#xff0c;小玲是一位年轻的职场人士。她的工作经常需要处理各种文档和文件&#xff0c;而其中不乏需要与外国合作伙伴交流的时候。然而&#xff0c;她并不熟悉其他语言&#xff0c;这给她的工作带来了一定的困扰。于是&#xff0c;她开始寻找免费的文档翻…

什么是AOP?

目录 一、AOP简介 1、AOP简介和作用 2、AOP的概念 二、AOP的基本实现 三、AOP工作流程 1 、AOP工作流程 2、AOP核心概念 四、AOP切入点表达式 1、语法格式 2、通配符 五、AOP通知类型 1、AOP通知分类 2、AOP通知详解 &#xff08;1&#xff09;前置通知 &#xf…

MySQL-分库分表详解(四)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

【ArcGIS微课1000例】0069:用ArcGIS提取一条线的高程值

本实验讲解用ArcGIS软件,基于数字高程模型DEM提取一条线的高程值并导出。 文章目录 一、加载实验数据二、将线转为折点三、提取折点高程值四、导出高程值五、注意事项【相关阅读】:【GlobalMapper精品教程】060:用dem提取一条线的高程值 一、加载实验数据 本实验使用的数据…

初学者一步步学习python 学习提纲

当学习Python时&#xff0c;可以按照以下提纲逐步学习&#xff1a; 入门基础 了解Python的历史和应用领域安装Python解释器和开发环境&#xff08;如Anaconda、IDLE等&#xff09;学习使用Python的交互式解释器或集成开发环境&#xff08;IDE&#xff09;进行简单的代码编写和…

Seafile搭建个人云盘 - 内网穿透实现在外随时随地访问

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4. 公网访问测试5. 结语 转载自cpolar极点云文章&#xff1a;使用SeaFile搭建…

【电影推荐系统】基于 ALS 的协同过滤推荐算法

目录 目的 用户电影推荐矩阵主要思路如下 1 UserId 和 MovieID 做笛卡尔积&#xff0c;产生&#xff08;uid&#xff0c;mid&#xff09;的元组 2 通过模型预测&#xff08;uid&#xff0c;mid&#xff09;的元组。 3 将预测结果通过预测分值进行排序。 4 返回分值最大的 …

elk中kibana使用

1.前言 kibana是一款作为elasticsearch可视化的一款软件&#xff0c;将elasticsearch中的数据以可视化的状态展现出来&#xff0c;kibana也提供了查询、统计、修改索引等功能 2.kibana使用 索引管理 在索引管理中&#xff0c;可以看到所有索引的状态、运行状况、主分片、副本…

pytorch快速入门中文——07(TensorBoard)

使用 TensorBoard 可视化模型&#xff0c;数据和训练 原文&#xff1a;https://pytorch.org/tutorials/intermediate/tensorboard_tutorial.html 在 60 分钟突击中&#xff0c;我们向您展示了如何加载数据&#xff0c;如何通过定义为nn.Module子类的模型提供数据&#xff0c;如…

计算机体系结构基础知识介绍之缓存性能的十大进阶优化之编译器控制的预取和利用HBM扩展内存层次(七)

优化九&#xff1a;编译器控制的预取以减少丢失惩罚或丢失率 硬件预取的替代方案是编译器在处理器需要数据之前插入预取指令来请求数据。 预取有两种类型&#xff1a; ■ 寄存器预取将值加载到寄存器中。 ■ 高速缓存预取仅将数据加载到高速缓存。 这两种类型都可以分为有错…

跟我一起从零开始学python(一)编程语法必修

前言 随着互联网的高速发展&#xff0c;python市场越来越大&#xff0c;也越来越受欢迎&#xff0c;主要源于它&#xff1a;易学易用&#xff0c;通用性广&#xff0c;时代需要&#xff0c;源代码的开放以及人工智能浪潮&#xff0c;接来下我们就从这几个方向谈谈为何python越…

17 MFC进程通信

文章目录 剪切板管道匿名管道父进程写入数据子进程读出数据 命名管道 邮槽邮槽服务器邮槽客户端 剪切板 设置界面 发送 //设置剪切板数据 void CClipboardDlg::OnBnClickedBtnSend() {UpdateData(TRUE);if (m_strSend.IsEmpty()){MessageBox(L"请输入需要设置的文本&quo…

微信小程序如何进行开发?

文章目录 0.引言1.注册微信公众平台账号2.准备微信开发者工具3.创建微信小程序并预览 0.引言 笔者编程一般编得较多的是桌面软件&#xff0c;有时也会编手机软件&#xff0c;这些软件都必须安装才能使用&#xff0c;这限制了软件的推广。而现有社交软件如微信使用得较广泛&…