学习系统编程终章【多线程剩余知识】

news2024/9/27 15:22:07

引言:

北京时间:2023/8/3/19:21,刚刚将文章更新,是近期以来为数不多的一次早更,不然每次更文都要卡在12点左右,这是我们实现日更的一个好开端,再接再厉实现日更不是梦。最近失眠一直困扰着我,不知道是真的有点焦虑,还是白天睡太多了,反正每天躺在床上就是睡不着,老是想一些七七八八的事情,没有两个小时以上根本睡不着,莫名不知道什么原因,大概率是作息问题,但是我记得以前无论我睡多久,什么时候睡,都能实现倒头就睡,睁眼就是天亮,睡的还贼舒服,现在反正每天都是强制起床,这个问题有待解决,目前的解决方法就是寻找一部小说听听,以前睡觉之前都是听着小说睡,听着听着就睡着了,但是最近书荒,不过可以考虑入手很久以前发现的有关耳根的小说,看评论对耳根的评价好像非常高,像什么《求魔》好像挺火,所以我考虑去听一听它的另一本小说《我欲封天》,由于是第一次接触耳根的小说,具体适不适合我,有待深入。正式进入该篇博客的主题,有关线程池、单例设计模式、自旋锁、读者写者问题的学习。

在这里插入图片描述

线程池相关知识

首先对这块知识我们不要害怕,因为线程池相关代码实现与生产消费模型颇有类似之处,所以此时我们同理只要对线程池的实现原理和基础概念明白之后,线程池的代码实现自然就是手到擒来,所以下面让我们来分析分析有关线程池的知识吧!

常见的资源管理方式
第一点明白什么是池化技术,想要明白什么是池化技术,那么前提我们要来看看系统内部的一些资源管理方式,谁让池化技术的本质就是一种资源管理方式呢? 此时我们明白对于系统内部的资源管理,最常见的就是两种,一种是延迟加载,一种是预先加载,对于这两种资源管理方式我们都不陌生,一路走来多多少少都是了解过一点的,只是因为我们没有进行系统的了解,所以并不知道它们都是对系统资源的管理方式。其中对于延迟加载,我们在学习地址空间和页表时体会最为深刻,我们知道当一个可执行程序被加载到内存之后(进程),根据该可执行程序,操作系统就会帮我们创建一份地址空间,当CPU在执行该进程地址空间代码段上的代码时,就会通过页表将地址空间上的地址映射到内存中的物理地址,当然此时对应内存地址中可能并不存在相应的数据,因为系统要满足延迟加载,也就是当某数据在没有使用之前,不允许它加载到内存中浪费内存资源,但此时因为CPU已经在访问相关数据了,所以操作系统就会触发缺页中断,在内存中申请空间提供给地址空间中的地址映射,但此时因为操作系统只申请了内存地址,并没有数据,所以操作系统还需要再进行一次缺页中断,将数据从磁盘上加载到内存中刚刚申请的地址中,最后让CPU可以成功访问到其需要执行的代码数据。明白了这个过程之后,我们对延迟加载就有了很好的认识,本质延迟加载就是为了提高系统性能和资源的利用率。 当然上述讲到的知识,无论是缺页中断还是页表映射,在之前博客中我们都详细进行了介绍,所以这里默认你们都懂哈!搞定了延迟加载相关的知识,此时就轮到预先加载了,谈到预先加载就不得不提局部性原理,因为以前我们认为局部性原理的本质就是进行预加载,但是这里有个误区,就是局部性原理其实是计算机中的一种对数据和指令访问模式的观察原则,其中分为时间局部性和空间局部性,时间局部性表示当某一个数据被频繁访问时,该数据就会被保留在缓存中,而空间局部性表示,在访问某数据时,因为该数据附近的数据大概率下次也会被访问,所以此时会通过预先加载的方式将该数据附近的数据一起加载到缓冲中。所以明白局部性原理本质是计算机中的一种规则,并不等价于预先加载,这里需要注意。但是通过局部性原理这一规则此时我们明白预先加载的本质就是将那些大概率会被使用、会被频繁使用的资源进行事先加载,从而实现下次真正需要使用的时候,不需要再浪费时间去加载,从而优化系统的性能和资源的利用率(以空间换时间)。

为什么有池的概念?
成功完成了对上述资源管理方式的理解,此时我们顺理成章的来看看什么是池化技术、什么是线程池吧!明白池化技术就是一种对资源进行预先加载的资源管理方式,它通过预先创建一定数量的资源提供给操作系统使用,从而达到提高系统性能的作用。所以此时我们明白,池化技术本身就是一种预先加载技术,同理线程池就是一种预先创建一批线程提供给系统必要时使用的方法。 同理有关内存资源的管理,虽然为了避免内存资源被浪费,操作系统设计了延迟加载的方式,但是延迟加载同理会带来效率问题,也就是虽然延迟加载可以提高内存资源的利用率,但是它也会使系统的执行速度降低,所以为了缓解这一问题,操作系统对于内存资源的管理不单单是简单的延迟加载,其中还使用了预先加载的管理方式,当然此时对于内存来说更准确一点可以称为“预取”。也就是当CPU在执行代码时,需要频繁的向内存申请空间,此时操作系统就需要频繁的执行内存管理算法,然后分配相应的内存给你,特别是如果当你频繁的执行系统调用代码(形成栈帧),那么此时不仅需要频繁从磁盘中加载数据而且还要频繁向内存申请空间,那么此时程序执行效率就会非常低,所以当我们在使用系统调用或者是某些容器时,此时底层代码在开辟空间时,就会额外申请一块空间,从而当你额外需要使用内存时,可以直接使用,不需要再向内存申请,从而提高程序的执行效率,当然这一规则我们称为内存池(以空间换时间)。所以同理这一原则,在网络中当用户发送某个请求时,操作系统虽然会立刻创建一个线程去处理,但是创建线程的这个过程被计算到完成这个请求的过程中,从而导致系统的响应速度下降,所以为了解决该问题,我们就会在系统内部事先创建一批线程,当用户发送请求时,直接将该请求封装成某个任务加载到任务队列中,然后去唤醒某个事先创建好的线程去执行该任务,从而实现高响应。这也就是线程池概念最大的应用和实现规则。

线程池工作原理
在系统启动时,线程池会创建一批线程,并存储在容器中。当任务队列不为空时,线程池会从线程池中获取某空闲的线程来执行该任务。任务执行完成后,线程并不会被销毁,而是重新放回线程池中,等待下一个任务的到来。如果线程池中的线程都在执行任务,而没有空闲线程可用,新的任务就会被存储在任务队列中,反之,如果任务队列中没有任务,那么线程将全部被阻塞,直到被唤醒。

线程池简易代码实现

在这里插入图片描述
当然,上述代码中涉及了一个静态成员函数的问题没有详解,这里我简单谈一谈,本质是因为在类中,所有成员函数的第一个参数默认是this指针,而因为我在使用pthread_create接口创建线程时,回调的是一个类内成员函数,所以此时当我们在传参(void* args)时,就会导致this指针和args之间的类型不匹配问题,所以此时最好的解决方法就是使用静态存储,将该类内成员函数存储在静态区,实现与该类的解耦,从而解决第一个参数是this导致的类型问题。但因为我们实现了与类的解耦,所以此时该成员函数并不能访问到类内属性(对象),所以pthread_create接口在传参时就会将this指针传过去给它使用,从而解决这一问题,当然最后虽然我们把this指针传过去了,但是由于类内成员变量是private状态,所以该静态成员函数依然访问不到类内属性,所以此时我们使用了友元的方式,让该静态成员函数可以成功访问到类内私有属性,达到编码目的。当然想要实现访问类内私有属性方法很多,你也可以使用公共成员函数返回或者直接调用公共成员函数来实现。当然上述代码只是一份最简易的代码,有待优化,下述在有关单例模式相关的知识中,我们将进行进一步的线程池代码优化。

线程安全的单例模式

首先明白,单例模式本身是一种经典的设计模式,而对于我们来说,设计模式就是一套被广泛接受和使用的解决特定问题的经验总结和最佳实践。并且提供了一套标准化的方法来解决常见的设计问题,从而提高代码的可读性、可维护性和可扩展性。

为什么有单例模式?
上述我们知道单例模式本身是一个设计模式,那么为什么要提出单例模式这个概念呢?当然想要回答这个问题,本质就是明白单例模式的好处是什么,当然由于我们此时具体还不知道单例模式的详情,所以我们先来谈单例模式的优点是比较抽象的,但是明白了这点是有利于我们下述理解单例模式的,所以互相印证就行。其中实现单例模式的好处有:可以实现全局访问,可以避免重复创建对象,如何理解呢?也就是在单例模式中我们的单例对象是通过静态存储的方式实现,所以它并不属于某个类,我们可以在程序的任意位置直接使用类名进行访问。并且同理由于我们将单例对象存储在了静态区,那么在多线程访问该类时,所有线程都不会像以前那样将类对象拷贝到自己的栈空间中,而是共享静态区上的同一个对象,并且通过同步机制对单例对象的保护,从而实现避免重复创建对象的效果。当然有关使用单例模式的好处还非常多,这里不重点讲解,此时我们只要明白,它可以让多线程在访问类对象时,减少对象的重复创建,从而提高效率就行。

什么是单例模式
首先明白单例模式最大的三个特征,一是构造函数私有化、禁止使用拷贝构造和赋值构造,二是实现单例对象静态存储、并且类型是类类型,三是需要提供一个静态获取方法来获取单例对象,确保整个程序中只有一个地方可以获取该类的实例。具体为什么要将获取方法设置为静态获取,主要是为了实现全局使用类名就能访问和简化对单例对象的访问。所以只要一个类符合上述三种情况,那么它就是一个单例模式实现的类,同理,我们自己想要实现一个单例模式的类,就必须遵守上述三个规则

注意: 当我们按照上述三个规则实现了单例模式之后,那么此时该类就只允许通过静态获取方法获取该类中的单例对象,因为此时该类不提供任何形式的构造,然后因为我们将单例对象的类型设计成了类类型,所以此时就可以通过获取单例对象的方法来调用该类中的其它成员变量和成员函数。并且由于该单例对象被存储在静态区中,所以当多线程想要使用静态获取方法获取单例对象来访问该类时,此时这个对象在什么时候被创建就分为两种不同的方式,也就是饿汉模式和懒汉模式。其中这两种方式的区别就是一种是当可执行程序被加载到内存准备执行的时候该单例对象就被创建出来(饿汉模式),另一种是可执行程序加载到内存之后在第一次使用时该单例对象时才被创建(懒汉模式)。在明白上述有关资源管理方式和池相关的知识之后,饿汉模式和懒汉模式的优缺点我们很容易就能搞定,类似延迟加载和预先加载的区别,饿汉模式会导致空间资源浪费和加载速度慢,但是能提高代码执行效率,而懒汉模式则节省加载时间和空间资源,但是代码执行效率降低。

饿汉模式实现和懒汉模式实现

首先是饿汉模式
在这里插入图片描述
同理,依据饿汉模式的原理,此时单例对象在可执行程序被加载到内存准备执行时,单例对象就已经被创建并且在内存中分配空间,因为此时我们的单例对象instance是一个被初始化完成的是静态成员变量,所以当该代码被加载到内存之后,编译器就会创建并且分配内存空间给该初始化单例对象。反之,如果是未初始化静态成员变量,那么此时编译器只会创建并不会分配内存空间。明白了这些之后,上述的代码就是一个标准的单例模式代饿汉代码实现。最后注意:此时的单例对象是一个静态成员变量,重点区分懒汉模式的单例对象是一个静态指针变量。

其次是懒汉模式
在这里插入图片描述
同理区分饿汉模式,此时懒汉模式的实现则略显复杂,主要是涉及第一次开辟内存空间由于单例对象是静态变量导致,因为对于多线程来说静态变量就是共享资源,所以此时需要对其进行加锁处理。值得注意的是此时保护静态成员变量我们只能使用静态锁,而不能使用局部锁,为什么呢?这个问题在开始的时候困扰了我挺久,在不断的查询之后,我有了一定的浅显理解:也就是因为对于多个线程来说,由于它们都有独立的栈空间,所以如果当我们使用局部锁来保护静态成员变量的话,虽然只有一个线程会最终抢到锁,进行串行访问,但是其余未抢到锁的线程并不会因为阻塞而不能访问其它局部对象,也就是说如果我们对单例模式下的静态成员变量使用局部锁保护,那么此时就会导致未抢到局部锁的其它线程可能会去访问其它的局部锁,而如果当某线程访问的其它加锁局部对象是单例模式中的某个局部对象,那么此时就可能会导致数据不一致问题,也就是线程安全问题。所以我们必须要使用静态锁来保证访问单例模式中对象的线程只有一个,无论是单例对象还是其它局部对象,因为只要当线程没有抢到静态锁,那么该线程就只能访问其它类中的静态资源,而不能再访问目标类中其它被加锁的局部对象,从而实现对整个类中无论是静态成员变量还是加锁局部变量都进行了保护。总而言之:静态锁的作用就是对类级别的资源进行保护,当碰到一个类中既有静态成员变量也有局部变量同时需要被保护时,我们只能使用静态锁,而非局部锁。

搞懂了上述为什么使用静态锁的问题,此时对于懒汉模式实现只是使用上的一些理解,如:因为单例对象只需要被定义一次,所以为了防止线程重复加锁,此时我们就会在加锁外部再增加一个判断条件,当单例对象被第一次加锁完成之后,所有线程在访问时,都共享同一资源直接返回静态区地址,不需要再进行额外的加锁过程,从而提高一定的代码执行效率。最后注意:在懒汉模式中我们的单例对象是一个类指针类型,不是静态变量,因为只有通过指针的方式我们才可以实现懒汉模式,也就是在第一次需要使用的时候再去开辟空间,然后将地址返回给我们定义的指针,从而实现延迟加载的目的。

使用单例模式实现线程池代码

在这里插入图片描述
该份代码和第一份线程池代码最大的区别除我们使用了单例模式实现之外,我们还使用了自己封装的线程库,使用单例模式的好处在上述我们已经介绍过了,而使用自己实现的线程库主要是为了帮助我们深入理解线程库中的传参和回调函数理解,其次就是可以让我们获取到线程状态(属性),并且在加锁和解锁方面我们使用的也是上次讲过的RAII方法,通过定义局部对象的方式,实现加锁(构造)和解锁(析构)的自动化。

STL和智能指针是否涉及线程安全问题

对于这块知识我们简单了解一下就行,大部分的STL容器都不是线程安全的,因为其内部并没有实现同步机制保护,容器的目的只是为了提高我们的编码效率而已,所以当我们在使用STL容器时,我们就需要对其实现同步机制保护来避免线程安全问题。而对于智能指针问题,具体由于我们还没有深入学习过有关知识,所以我们浅浅的了解一下就行,对于unique_ptr来说,因为其只在当前代码块范围内生效,因此不涉及线程安全问题,对于shared_ptr来说,由于多个对象共享一个引用计数变量,所以存在线程安全问题,但是在标准库内部使用了原子操作对shared_ptr进行保护,所以shared_ptr并不涉及线程安全问题。具体深入智能指针的知识以后博客中将会更新。

其它常见的锁

这部分知识因为没有具体的场景和示例,所以我们简单了解就行,重点放在下述有关读者写者的问题,具体如下所述:

  • 悲观锁:它是一种假设并发环境中一定会产生线程安全问题的策略,所以当多线程同时访问某共享资源时,线程就一定需要先获取锁才能继续向后执行,此时这个获取到的锁我们就称为悲观锁,由于悲观锁的这种策略,所以它一般被用在并发冲突频繁的场景下,缺点:性能较低。

  • 乐观锁:悲观锁反之则是乐观锁,它是一种假设并发环境不会产生线程安全问题的策略,所以当多线程在访问共享资源时,它不会优先获取锁,而是在线程访问共享资源之后,检查数据时候被修改,如果被修改,则恢复数据重新尝试,反之合理,所以同理它一般被用在并发冲突较少的场景。

  • 自旋锁:自旋锁本质是一种轮询等待的策略,线程在没有获取到锁时,不会进入阻塞状态,也不会去访问其它的锁,而是通过轮询的方式不断的尝试去获取锁,直到获取到为止,一般用在临界区很小,也就是短时间内能释放锁的场景,优点:可以减少线程切换的消耗。

由于悲观锁和乐观锁不是具体的锁,而自旋锁是具体的锁,所以下述我们来看看自旋锁的接口使用方式,如下所示:
本质与之前学习的互斥锁相同

自旋锁(spinlock)接口使用
定义一把自旋锁:pthread_spinlock_t mutex;
初始化:pthread_spin_init(pthread_spinlock_t* mutex,PTHREAD_PROCESS_PRIVATE);
加锁:pthread_spin_lock(pthread_spinlock_t* mutex);
锁定:pthread_spin_trylock(pthread_spinlock_t* mutex);
解锁:pthread_spin_unlock(pthread_spinlock_t* mutex);
销毁:pthread_spin_destroy(pthread_spinlock_t* mutex);

注意: 如果自旋锁被锁定(非阻塞式加锁),那么同理获取锁失败之后会直接返回,不会再轮询式等待。

读者写者问题

来到多线程相关知识的最后一个知识点,有关读者写者的问题,本质还是离不开线程间通信的范畴,只不过读者和写者问题拥有属于自己的特性,并且在一些场景中被使用,所以我们需要进行一定的了解。谈到读者和写者问题,我们第一时间想到的就是生产消费模型,因为它们之间有非常多的相似之处,如一个缓冲区、两个角色、三种关系,同理只不过是因为读者写者模型有自己的特性,所以需要额外注意。

此时我们就明白,读者写者问题首先应该从三种关系原则出发:读者和读者、写者和写者、读者和写者,那么它们之间具体是什么关系呢?明白,对于写者和写者来说,它们也是互斥关系,读者和写者之间同理是同步且互斥关系,而唯一和生产消费模型不同的就是读者和读者之间的关系,读者和读者之间没有关系,区别消费者和消费者之间的互斥关系。那么为什么对于生产消费模型中消费者和消费之间的关系就是互斥关系,而对于读者写者模型中读者和读者之间就是没有关系呢?本质其实是因为在生产消费模型中消费者每次需要从缓冲区中拿走数据,而读者却不需要从缓冲区中拿走数据,这也就是为什么读者和读者之间没有关系的原因。

所以此时按照这三种关系,此时我们想要实现读者和写者模型,此时同理需要使用互斥锁、条件变量等同步机制来控制。如下就是有关读者写者问题的系统调用接口:

读写锁(rwlock)接口使用
定义一把读写锁:pthread_rwlock_t rwlock;
初始化读写锁:pthread_rwlock_init(pthread_rwlock_t* rwlock,nullptr);
销毁读写锁: pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
对读者进行加锁:pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
对读者锁定加锁:pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
对写者进行加锁:pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
对写者锁定加锁:pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
对读者和写者解锁:pthread_rwlock_unlock(pthread_rwlock_t* rwlock);

读者写者模型伪代码实现

在这里插入图片描述
通过上述伪代码实现,明白此时我们就是想要维护读者写者问题中的三种关系,实现当写者在写的时候,不允许读者读以及在读者读取不允许写者写的同时运行其它读者也能够读取。所以此时通过二元信号量,我们就实现了写者与写者之间的互斥关系以及写者与读者之间的同步且互斥关系。

但是此时会碰到一个问题,当读者非常多,就会导致写者拿不到信号量,导致写者的饥饿问题,所以对于这一现象我们分为两种不同的场景,一种是读者优先,一种是写者优先,如下所述:

  • 读者优先:在这种策略下,如果有读者正在读取数据,或者有读者在等待读取数据,那么写者必须等待,直到所有的读者都读取完数据。这种策略优先保证读者的访问,可以避免读者被长时间阻塞,但是如果读者的请求非常频繁,可能会导致写者饥饿,也就是写者长时间得不到访问资源的机会。

  • 写者优先:在这种策略下,一旦有写者请求写入数据,那么后续的读者必须等待,直到所有的写者都写入完数据。这种策略优先保证写者的访问,可以避免写者饥饿,但是如果写者的请求非常频繁,可能会导致读者饥饿,也就是读者长时间得不到访问资源的机会。

总结:上述就是有关多线程相关剩余知识学习啦!学完多线程也就意味着我们将系统编程相关的知识学完了,所以接下来就让我们进军网络的学习吧~~~~

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

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

相关文章

智慧影院--java开源电影票优惠券制作系统快速开发

搭建一个智慧影院可以通过使用Java开源电影票优惠券制作系统来快速开发。这个系统可以帮助影院管理电影票的销售和优惠活动,提供便捷的购票方式和优惠券的生成与使用功能。 首先,我们需要建立一个数据库来存储电影、影厅、放映计划、订单等信息。在数据…

查看日志信息

查看日志信息 在我们编写代码的过程中可能看不懂错误提示信息,或者不知道错出在什么地方的情况,我们可以打印输出日志信息来检查 使用lombok提供的日志记录器,自定义编程查看调试信息 1、引入lombok依赖 2、在application.properties中配置日…

Observable设计模式简介

Observable设计模式存在于许多Java API和响应式编程中。下面介绍Java中永恒的Observable模式。 Observable设计模式用于许多重要的Java API。一个众所周知的示例是使用ActionListenerAPI执行操作的JButton。在这个例子中,我们ActionListener在按钮上进行了监听或…

玩嵌入式,一般怎么入门?

入门阶段:(不要只看书,要多动手,但千万不是直接动手,不去看书) C语言:嵌入式编程大多用C语言、少量汇编,先学习C语言,汇编用到的时候再上网查询。教材:随便一…

快讯|新 CEO:Tubi 将成为下一代观众的首选

在每月一期的 Tubi 快讯中,你将全面及时地获取 Tubi 最新发展动态,欢迎关注【比图科技】,一起成长变强! 你将通过本文了解 Tubi 在 2023 年 7 月的重要大事: 新 CEO:Tubi 将成为下一代观众的首选 Tubi…

【数学】协方差介绍、相关系数介绍,Python代码

协方差 协方差(Covariance)是统计学中用来衡量两个随机变量之间关系的一种度量。它反映了这两个变量的变化趋势是否一致,即当一个变量偏离其均值时,另一个变量是否也倾向于偏离其均值。协方差可以帮助我们了解变量之间的线性关系…

❤ TypeError: Assignment to constant variable-Vue3 项目使用

❤ TypeError: Assignment to constant variable 背景: Vue3 项目使用 TypeError: Assignment to constant variable. 原因: 因为我对const定义的常量重新赋值了 解决方法: 换成 var 声明

Flink源码之JobManager启动流程

从启动命令flink-daemon.sh中可以看出StandaloneSession入口类为org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint, 从该类的main方法会进入ClusterEntrypoint::runCluster中, 该方法中会创建出主要服务和组件。 StandaloneSessionClusterEntrypoint:…

内存新一轮暴跌,即将大量流行“官方翻新”

如果说最近一年你有在关注 PC 硬件价格,内存、SSD 想必是值得感慨的。 一次次的好价抄底,似乎永没有尽头。 SSD 降价归功于国产长江存储闪存颗粒大量出货,但内存的猛降能理解但又不完全能理解。 DDR4 到 DDR5 换代没错,但更早知…

COS控制台体验升级 - 文件列表支持网格布局

前言 对象存储(Cloud Object Storage,COS)控制台文件列表页以表格的形式列出存储桶下的所有文件,为了提高用户在COS控制台文件列表页的操作体验,我们对其进行了改版,现在文件列表页支持网格视图&#xff0…

攻防世界-web-shrine

1. 题目描述 打开链接,发现是一串源码: 从源码中不难发现关键词是flask.render_template_string(safe_jinja(shrine)) ,这个函数说明了题目的关键点在于模板渲染,即存在模板注入 2. 思路分析 从代码中不难发现,即使…

什么是思维导图?怎么制作思维导图?看这篇就够了!

在当下快节奏的社会中,无论是学习、工作还是生活,我们都需要处理大量的信息和任务。对于这些复杂的信息和任务,如何有效地理解、记忆和管理,成为了我们面临的一个重要挑战。对于诸如此类场景,使用思维导图就能很好地辅…

python-docx常用方法总结

由于最近有任务需要自动生成word报告,因此学习了一些python-docx的使用方法,在此总结。 目前网上相关的资料不算太多,且大多数都很简单。有一些稍微复杂的需求往往找不到答案,很多想要的方法这个库似乎并没有直接提供。在git上看…

面部表情识别(Pytorch):人脸检测模型+面部表情识别分类模型

目录 0 相关资料1 基于人脸检测面部表情分类识别方法2 项目安装2.1 平台与镜像2.2 项目下载2.3 模型下载2.4 上传待测试图片2.5 项目安装 3 demo测试 0 相关资料 面部表情识别2:Pytorch实现表情识别(含表情识别数据集和训练代码):https://blog.csdn.net…

链表oj (环形链表oj)

文章目录 1.数组oj 2.链表oj 文章内容 1.数组oj 1. 原地移除数组中所有的元素值为val&#xff0c;要求时间复杂度为O(N)&#xff0c;空间复杂度为O(1)。力扣 int removeElement(int* nums, int numsSize, int val){int k numsSize;int a 0;int b 0;while(b<k){if(nu…

Centos7安装jdk8教程——rpm安装

1. rpm文件下载 下载链接 Java SE 8 Archive Downloads (JDK 8u211 and later) 2.上传到服务器指定路径下并安装 切换到上传目录&#xff0c;然后执行以下命令 rpm -ivh jdk-8u221-linux-x64.rpm3. 设置环境变量并重载配置 # 设置环境变量 vim /etc/profile# 文件末尾添加…

【网站搭建】开源社区Flarum搭建记录

环境 服务器系统&#xff1a;腾讯云 OpenCloudOS 宝塔版本&#xff1a;免费版8.0.1 Nginx&#xff1a;1.24.0 MySQL&#xff1a;5.7.42 PHP&#xff1a;8.1.21 萌狼蓝天 2023年8月7日 PHP设置 1.安装扩展&#xff1a;flieinfo、opcache、exif 2.解除禁用函数&#xff1a;putenv…

Llama 2 with langchain项目详解(一)

Llama 2 with langchain项目详解(一) 2023年2月25日,美国Meta公司发布了Llama 1开源大模型。随后,于2023年7月18日,Meta公司发布了Llama 2开源大模型,该系列包括了70亿、130亿和700亿等不同参数规模的模型。相较于Llama 1,Llama 2的训练数据增加了40%,上下文长度提升至…

直线模组在AGV物流设备起什么作用?

在物流产业高速发展的今天&#xff0c;机器人技术的应用程度已经成为决定企业间相互竞争和未来发展的重要衡量因素。智能机器人运用到物流产业&#xff0c;其效率不言而喻。AGV智能仓储作为现代物流系统的重要组成部分&#xff0c;物流自动化、智能化不光是能提升效率和安全性&…

前端懒加载

懒加载的概念 懒加载也叫做延迟加载、按需加载&#xff0c;指的是在长网页中延迟加载图片数据&#xff0c;是一种较好的网页性能优化的方式。在比较长的网页或应用中&#xff0c;如果图片很多&#xff0c;所有的图片都被加载出来&#xff0c;而用户只能看到可视窗口的那一部分…