【Linux】POSIX信号量{基于环形队列的PC模型/理解信号量的出现/参考代码}

文章目录

  • 1.POSIX信号量
    • 1.1介绍
    • 1.2接口
  • 2.基于环形队列的PC模型
    • 2.1环形队列常用计算
    • 2.2如何设计?
    • 2.3如何实现?
  • 3.细节处理
    • 3.1空间资源和数据资源
    • 3.2push/pop
    • 3.3理解信号量的出现
      • 1.回顾基于阻塞队列的PC模型中条件变量的使用
      • 2.如何理解信号量的投入使用?
    • 3.4多生产多消费的意义在哪里?
  • 4.参考代码
    • 4.1sem.hpp
    • 4.2RingQueue.hpp
    • 4.3pcModel.cc

在这里插入图片描述

1.POSIX信号量

1.1介绍

  1. 进程通信讲的System V信号量 消息队列+信号量
  2. POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

1.2接口

初始化信号量

在这里插入图片描述

#include <semaphore.h>
int sem_init(sem t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非0表示进程间共享
value:信号量初始值

销毁信号量

在这里插入图片描述

int sem_destroy(sem t*sem);

等待信号量

在这里插入图片描述
在这里插入图片描述

功能: 等待信号量,会将信号量的值减1

int sem_wait(sem t*sem);

发布信号量

在这里插入图片描述

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

int sem_post(sem t*sem);

总结sem_init/sem_destroy/sem_post/sem_wait

在Linux下,sem_init、sem_destroy、sem_post和sem_wait是用于操作POSIX信号量的函数,它们为进程或线程间的同步提供了一种机制。

功能:
sem_init
功能:初始化一个未命名的信号量。
底层原理:在内存中为信号量分配空间,并设置其初始值。这个初始值通常表示可用资源的数量或允许进入临界区的线程数。
sem_destroy
功能:销毁一个先前初始化的信号量。
底层原理:释放信号量所占用的内存空间,并取消其关联的任何系统资源。
sem_post
功能:增加(或“发布”)信号量的值。
底层原理:对信号量的值进行原子性增加操作。这通常意味着释放了一个资源或允许更多的线程进入临界区。这个操作可能会唤醒等待该信号量的一个或多个线程。
sem_wait
功能:减少(或“等待”)信号量的值。
底层原理:尝试对信号量的值进行原子性减少操作。如果信号量的值大于0,则将其减1并立即返回;如果信号量的值为0,则调用线程或进程将被阻塞,直到信号量的值变为正数。这个阻塞是通过系统调用和内核的调度机制实现的。

底层原理: 主要涉及到操作系统的内核支持和原子操作。

原子操作:对信号量的增加和减少操作必须是原子的,即不可中断的。这确保了多线程或多进程环境中对信号量操作的正确性和一致性。这些原子操作通常是通过特殊的硬件指令或内核提供的系统调用来实现的。

内核支持:当线程或进程调用sem_wait并因信号量值为0而被阻塞时,操作系统内核会负责将该线程或进程放入等待队列中,并在信号量值变为正数时将其唤醒。同样,当sem_post被调用以增加信号量的值时,内核会检查是否有线程或进程在等待该信号量,如果有,则将其唤醒。

内存管理:sem_init和sem_destroy涉及到信号量在内存中的分配和释放。这通常是通过调用操作系统的内存管理函数来完成的,以确保对信号量对象的正确访问和生命周期管理。

通过这些函数和底层的支持机制,POSIX信号量提供了一种强大而灵活的同步原语,用于解决多线程或多进程环境中的并发访问和同步问题。

如何理解Linux下的posix信号量,他与system v信号量有何异同?

Linux下的POSIX信号量是一种实现进程/线程间通信的机制,主要用于保护共享资源,确保资源在某一时刻只被一个进程(线程)使用。它分为有名信号量和无名信号量两种类型。有名信号量的值保存在文件中,因此它可以用于线程间和进程间的同步;而无名信号量的值保存在内存中,因此它主要用于线程间的同步,如果需要用于进程间同步,则信号量需要放在共享内存中。POSIX信号量包含一个非负整型变量,并带有两个原子操作:wait(也被称为down、P或lock)和signal(也被称为up、V、unlock或post)。

与POSIX信号量相比,System V信号量也是一种在操作系统中提供的进程间通信(IPC)机制,用于实现进程之间的同步和互斥。它通过对计数器进行操作来控制资源的访问。每个System V信号量由一个整型的标识符(semaphore identifier)来标识,每个标识符对应着一个信号量集合(semaphore set),而信号量集合中可以包含多个单独的信号量。

POSIX信号量和System V信号量在功能上有相似之处,但也有一些关键的差异:

平台支持:POSIX信号量在某些平台(如SUSE8)上可能并未被实现,而System V信号量在大多数Linux/UNIX系统中都已经实现。
创建与初始化:POSIX信号量通过sem_open单一的调用就完成了信号量的创建、初始化和权限的设置,而System V信号量需要两步来实现。
多线程/多进程安全性:POSIX信号量被设计为多线程和多进程安全的,而System V信号量在这方面可能存在问题。
标识符类型:System V信号量通过一个int类型的值来标识自己,类似于调用open()返回的fd;而sem_open函数返回sem_t类型(长整形)作为POSIX信号量的标识值。
总的来说,POSIX信号量和System V信号量都是用于进程间或线程间同步和互斥的重要机制,但它们在实现方式、平台支持、安全性以及使用方式上存在一些差异。在选择使用哪种信号量时,需要根据具体的应用场景和需求进行考虑。

为什么wait也被称为P和signal也被称为V?

在操作系统和并发编程中,wait 和 signal 是用于同步原语的两个基本操作。在某些上下文中,特别是与信号量(semaphores)相关的上下文中,wait 和 signal 操作有时也被称为 P 和 V。这两个名称来源于 Dijkstra 的信号量实现,它用这两个字母来代表两个关键操作。

P 操作(Proberen,荷兰语中的“测试”):对应于 wait 或 down 操作。当一个进程或线程执行 P 操作时,它试图减少信号量的值。如果信号量的值大于 0,则将其减 1 并允许该进程或线程继续执行。如果信号量的值为 0,则执行 P 操作的进程或线程将被阻塞,直到信号量的值变得大于 0 为止。这个操作确保了对共享资源的互斥访问。
V 操作(Verhogen,荷兰语中的“增加”):对应于 signal 或 up 操作。当一个进程或线程执行 V 操作时,它会增加信号量的值。这通常表示释放了对某个共享资源的访问权。如果有其他进程或线程因为信号量的值为 0 而被阻塞在 P 操作上,执行 V 操作的进程或线程可能会唤醒其中一个(或多个,取决于调度策略)被阻塞的进程或线程。
使用 P 和 V 作为 wait 和 signal 的别名有助于简洁地描述信号量的操作,特别是在描述算法或进行理论分析时。这种命名方式也强调了这两个操作在信号量机制中的核心作用:P 操作用于请求资源(可能需要等待),而 V 操作用于释放资源(可能唤醒等待的进程或线程)。

需要注意的是,不是所有的系统或文档都使用 P 和 V 作为 wait 和 signal 的别名。在不同的上下文中,这些操作可能有不同的名称或表示方式。但在讨论 Dijkstra 信号量或相关同步机制时,使用 P 和 V 作为术语可以帮助明确指代这两个基本操作。

2.基于环形队列的PC模型

上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写PC模型

  1. 环形队列采用数组模拟,用模运算来模拟环状特性
  2. 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,通过加计数器或者标记位来判断满或者空。也可以预留一个空的位置,作为满的状态
  3. 但是信号量这个计数器可以简单的进行多线程间的同步过程

环形队列在计算机中可以用一维数组来维护,这体现了怎样的设计思想?

环形队列在计算机中通过一维数组的物理结构来维护其逻辑上的循环队列结构,这进一步体现了空间复用和抽象化的设计思想。

首先,从物理结构的角度来看,环形队列使用一维数组作为其底层数据结构。一维数组是计算机内存中的连续存储空间,其元素可以通过索引进行快速访问和修改。环形队列利用数组的连续存储特性,通过维护头尾指针来模拟队列的循环操作。头尾指针在数组中的移动遵循循环规则,当指针到达数组的末尾时,会回到数组的起始位置,形成逻辑上的环形结构。

其次,从逻辑结构的角度来看,环形队列表现为一个循环队列,遵循FIFO(先进先出)的原则。在逻辑上,环形队列的头部和尾部是相连的,形成一个闭合的环。新元素从尾部入队,旧元素从头部出队,当尾部指针追上头部指针时,表示队列已满。这种逻辑结构使得环形队列能够高效地处理元素的入队和出队操作,同时避免了传统队列在元素移动时可能产生的空间浪费。

空间复用体现在环形队列通过循环使用数组空间,使得数组中的每个位置都能被充分利用,减少了空间浪费。而抽象化则体现在环形队列将复杂的循环操作封装在底层实现中,对外只提供简单的入队和出队接口,使得使用者无需关心底层实现细节,只需关注队列的基本操作即可。

综上所述,环形队列通过一维数组的物理结构来维护其逻辑上的循环队列结构,不仅提高了空间的使用效率,也简化了队列的操作和使用,再次体现了空间复用和抽象化的设计思想。

与此类似的例子:

磁盘由一个个同心圆组成,我们把当抽象成数据结构来维护。 ==> 一个结构他在内存中的存储样式(存储结构/物理结构)仅仅是一块空间,这块空间能实现什么样的功能取决于设计者在软件层给这块空间维护了怎样的设计。

Linux下,基于阻塞队列/环形队列的生产者消费者模型,各自的原理以及二者的异同

在Linux下,基于阻塞队列和环形队列的生产者消费者模型是并发编程中常见的模式,用于解决多线程间的数据共享和同步问题。下面我将分别讲解它们的原理,并对比它们的异同。

阻塞队列原理
阻塞队列是一种特殊的队列,它支持两个附加操作:在队列为空时,获取元素的线程将会阻塞,直到有元素可获取;当队列已满时,尝试添加元素的线程也将阻塞,直到队列有空余空间。

在生产者消费者模型中,生产者线程负责向队列中添加数据(生产),消费者线程从队列中移除数据(消费)。使用阻塞队列可以确保当队列满时生产者线程阻塞,当队列空时消费者线程阻塞,从而避免数据的丢失或重复处理。

环形队列原理
环形队列(也称为循环队列)是一种使用固定大小数组实现的队列。当队列的尾部到达数组的末尾时,它会循环回到数组的开头。这种设计可以高效地利用数组空间,避免了传统队列在插入和删除元素时可能需要的数组移动操作。

在生产者消费者模型中,环形队列同样用于存储生产者产生的数据,供消费者线程消费。由于环形队列的空间是固定的,因此当队列满时,生产者线程需要等待消费者线程消费数据以释放空间;同样,当队列空时,消费者线程需要等待生产者线程生产数据。

异同点
相同点:
同步机制:无论是阻塞队列还是环形队列,都需要使用某种同步机制(如互斥锁、条件变量等)来确保生产者和消费者之间的正确同步。
数据共享:两者都用于在多个线程之间共享数据,生产者将数据放入队列,消费者从队列中取出数据。
不同点:
空间管理:阻塞队列通常可以动态地扩展和收缩,以适应不同数量的数据。而环形队列使用固定大小的数组,空间使用更加受限,但操作更加高效。
阻塞行为:阻塞队列在队列满或空时会阻塞相应的线程,直到条件满足。这种特性简化了生产者消费者的同步逻辑,但可能增加线程的上下文切换成本。而环形队列通常需要通过额外的同步机制(如条件变量)来实现阻塞行为。
适用场景:阻塞队列更适合于那些数据量变化较大,或者对内存使用不是非常敏感的场景。而环形队列由于其空间固定且操作高效的特点,更适合于那些对性能要求较高,且数据量相对稳定的场景。
总的来说,阻塞队列和环形队列都是实现生产者消费者模型的有效工具,选择哪种取决于具体的应用场景和需求。

2.1环形队列常用计算

  1. 设置一个计数器 记录当前队列中元素个数
  2. 设定队列中一个特定的空间用来存放元素个数
  3. 使用模运算
1、队空:q.front == q.rear
2、队满:(q.rear + 1)% N = q.front
3、队长:(q.rear - q.front + N) % N
4、循环计数: 
q.front = (q.front + 1) % N
q.rear=(q.rear + 1) % N

2.2如何设计?

在这里插入图片描述

2.3如何实现?

  1. 假定我们这么设计:生产者指向他上次生产的下一个位置即生产者每生产一次就++;消费者指向他未来要消费的资源,消费一次就++。
  2. 如果生产者和消费者指向了环形结构的同一个位置,那么此时队列的状态要么为空要么为满:初始时队列为空,二者指向同一个位置。生产者不断生产,他只能生产到消费者的前一个位置,生产完最后一个后,生产者++,此时二者相遇;消费者不断获取,他只能获取完最后一个元素,获取完后,消费者++,此时二者相遇。
  3. 生产者和消费者存在互斥或同步问题。一般情况下,生产者和消费者指向的是不同的位置。当生产者和消费者指向同一个位置时,进行互斥与同步的控制。当生产者和消费者指向不同的位置,让他们并发执行即可。
  4. 生产者不能超过消费者;消费者不能超过生产者。
  5. 为空:让生产者先运行
  6. 为满:让消费者先运行

3.细节处理

3.1空间资源和数据资源

在这里插入图片描述

3.2push/pop

在这里插入图片描述

先加锁还是先申请信号量

这里说的申请信号量成功/失败的意思是:对信号量的值进行对应的pv操作

  1. 先加锁在获取信号量

多个线程要push/pop时,先申请锁,申请锁成功后去申请对应的信号量,申请信号量不成功则阻塞等待,此时锁不能被其他线程使用;
多个线程要push/pop时,先申请锁,申请锁失败后会阻塞等待锁。

  1. 先申请信号量
    多个线程要push/pop时,先申请信号量,申请信号量成功再去申请锁;
    多个线程要push/pop时,先申请信号量,申请信号量失败则阻塞等待;

  2. 先加锁还是先申请信号量?

很明显,我们要先申请信号量,为什么?我们在不加锁的前提下先申请信号量时,此时是可以有多个线程调用push/pop函数的,信号量的值是大于1的,即可以有多个线程成功申请信号量,申请信号量成功的就去申请锁,申请信号量失败的就阻塞等待;而如果我们先申请锁,如果申请锁失败则阻塞,其他的线程可以申请锁;就算申请锁成功,此时已经进入了临界区,只能由成功申请锁的该线程去申请信号量,如果申请信号量成功也还好,这个线程可以继续执行,如果申请信号量失败,不仅这个申请到锁的线程在阻塞等待信号量,其他未申请到锁的线程也在当地。与先申请信号量相比:线程们都可以申请信号量,之后由某一个申请到信号量又申请到锁的去执行临近区代码;而先加锁,一旦加锁后只能有一个线程执行临界区,其余的线程只能等他执行完才能得到调度。总结:先申请信号量可以先进行信号量的申请,一旦得到调度就可以申请锁继续后续动作。

3.3理解信号量的出现

已经有条件变量了,为什么设计者又搞出一个信号量?显然,是为了进一步的提高效率。俗话来讲,就是人们的思想层次在不断地提高,基于以往知识的理解,设计出更好用的东西。

1.回顾基于阻塞队列的PC模型中条件变量的使用

在这里插入图片描述

条件变量是如何诞生的,或者说是如何投入使用的?

多线程背景下,要保证单线程访问临界资源,就得有锁,申请锁后访问临界资源前,存在资源是否就绪的问题,如果资源不就绪,就要释放锁,那么就出现了这么一种情况:某些/某个线程在不断地申请/访问资源状态/释放锁,这使得丧失了设计多线程的目的,于是有了条件变量,条件变量的出现使得在资源不就绪的情况下,可以让线程在临界区等待,一旦被唤醒/得到通知,就可以继续执行,避免了不断地申请/访问资源状态/释放锁这一无意义的情况。

2.如何理解信号量的投入使用?

在这里插入图片描述

  1. 用条件变量配合互斥锁实现互斥与同步:申请锁 -> 判断与访问资源 -> 释放锁 ===》使用条件变量这样做的原因是我们不清楚临界资源的情况/状态
  2. 信号量提前预订了资源,通过计算机软硬件给我们提供的原子性的epv接口,我们可以在非临界区知晓临界资源的情况,即,我们不再考虑资源是否就绪了!即,条件变量解决的因资源就绪而循环申请/释放锁的情况,信号量解决的是压根就不用再考虑资源是否就绪!
  3. 信号量本质是一把计数器,使得线程可以不用进入临界区就可以得知资源情况==》减少临界区内部的判断!

3.4多生产多消费的意义在哪里?

不要狭隘的认为,任务或数据在交易场所的存放和获取,就是PC模型的意义。==》将数据或任务生产前和拿到之后的处理,才是最耗费时间的
生产的本质:私有的生产的任务/数据放在公共空间中供许多消费者获取
消费的本质:公共空间中的任务/数据获取后私有的进行处理

Linux下的信号量和条件变量哪个更好用?

如果你需要限制对共享资源的并发访问数量,或者需要在多线程或多进程环境中进行同步,那么信号量可能是一个更好的选择。
如果你需要等待某个条件成立,并在条件变化时执行相应的操作,那么条件变量可能更适合你的需求。

为什么要设计多线程?设计多线程的目的是什么?

设计多线程的目的主要是为了充分利用计算机的多核处理器资源,提高程序的执行效率和响应速度,以及实现复杂的并发任务处理。以下是设计多线程的主要目的:

提高系统吞吐量:多线程可以使得多个任务并发执行,从而提高系统的整体吞吐量。如果系统只使用单线程,那么在一个任务执行时,其他任务只能等待,这大大降低了系统的处理速度。通过多线程,系统可以同时处理多个任务,使得资源得到更充分的利用。
提高响应速度:对于需要实时响应的系统(如服务器、游戏等),多线程可以确保在等待一个任务完成时,其他任务仍然可以继续执行。这样可以提高系统的响应速度,使得用户可以更快地得到反馈。
利用多核处理器:现代计算机普遍采用多核处理器,每个核心可以独立地执行线程。设计多线程可以使得程序能够充分利用这些处理器核心,从而提高程序的执行效率。
简化编程模型:多线程使得并发编程更为简单和直观。通过将任务分解为多个线程,程序员可以更容易地管理和控制这些任务的执行。
实现复杂的并发任务处理:有些任务需要并发执行才能满足需求,例如网络编程中的服务器需要同时处理多个客户端的请求。通过多线程,可以轻松地实现这种并发处理。
然而,需要注意的是,多线程编程也带来了一些挑战,如线程同步、数据共享和死锁等问题。因此,在设计多线程程序时,需要仔细考虑这些问题,并采取相应的措施来避免潜在的问题。

4.参考代码

4.1sem.hpp

#ifndef _SEM_HPP_
#define _SEM_HPP_

#include <iostream>
#include <semaphore.h>

class Sem
{
public:
    Sem(int value)
    {
        sem_init(&_sem, 0, value);
    }

    //value=0 wait; value>0 value--
    void p() 
    {
        sem_wait(&_sem);
    }

    //发布信号量 唤醒等待中的线程 资源使用完毕 可以归还资源 将信号量值加1
    void v()
    {
        sem_post(&_sem);
    }
    
    ~Sem()
    {
        sem_destroy(&_sem);
    }

private:
    sem_t _sem;
};

#endif

4.2RingQueue.hpp

#ifndef _Ring_QUEUE_HPP_
#define _Ring_QUEUE_HPP_

#include <iostream>
#include <vector>
#include <pthread.h>
#include "sem.hpp"

const int g_DefaultNum = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int defaultNum = g_DefaultNum, int num = g_DefaultNum,
              int pstep = 0, int cstep = 0,
              int spaceSem = g_DefaultNum, int dataSem = 0)
        : _rqueue(defaultNum),
          _num(num),
          _pstep(pstep),
          _cstep(cstep),
          _spaceSem(spaceSem),
          _dataSem(dataSem)
    {
        pthread_mutex_init(&cLock, nullptr);
        pthread_mutex_init(&pLock, nullptr);
    }

    ~RingQueue()
    {
        pthread_mutex_destroy(&cLock);
        pthread_mutex_destroy(&pLock);
    }

    // 生产者需要空间资源 生产者们的临界资源是 下标
    void push(const T &in)
    {
        /*index只在该函数内可见 外部无法获取 不使用
        static int index = 0;
        _rqueue[index] = in;
        */
        _spaceSem.p();              // 阻塞等待减少生产者空间资源Sem值 生产者发送资源的前提是队列中有空间
        pthread_mutex_lock(&pLock); // 保证多线程生产者环境下 只有一个生产者线程进入
        // 生产者发送资源到队列
        _rqueue[_pstep++] = in;
        _pstep %= _num;
        pthread_mutex_unlock(&pLock);
        // 现已确定队列中肯定有资源
        _dataSem.v(); // 增加消费者数据资源Sem值 使得消费者等待信号量时知道队列中有数据可取
    }
    // 消费者需要数据资源 消费者们的临界资源是下标
    void pop(T *out)
    {
        _dataSem.p();               // 阻塞等待减少消费者数据资源Sem值 消费者获取资源的前提是队列中有资源可取
        pthread_mutex_lock(&cLock); // 保证多线程消费者环境下 只有一个消费者线程进入
        // 消费者从队列中获取资源
        *out = _rqueue[_cstep++];
        _cstep %= _num;
        pthread_mutex_unlock(&cLock);
        // 现已确定队列中肯定有空间
        _spaceSem.v(); // 增加生产者空间资源Sem值 使得生产者等待信号量时知道队列中有空间可放
    }

private:
    std::vector<T> _rqueue;
    int _num;
    int _pstep; // 生产者当前的位置
    int _cstep; // 消费者当前的位置
    Sem _spaceSem;
    Sem _dataSem;
    pthread_mutex_t cLock;
    pthread_mutex_t pLock;
};

#endif

4.3pcModel.cc

#include "RingQueue.hpp"
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>

void *productor(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args;
    while (true)
    {
        // sleep(1); // 生产者慢

        // 生产数据或构建任务(外部获取/自己创造) -- 有时间消耗
        std::cout << "Producer is producing data... ";
        int data = rand() % 100 + 1;

        // pthread_t tid是一个地址 整形值很大 我们将他%10000是为了验证看到是不同的生产者线程
        std::cout << " 生产: " << data << " [" << pthread_self() % 10000 << "]" << std::endl;

        // 发送到交易场所 -- 环形队列
        rq->push(data);
    }
}

void *consumer(void *args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args;
    while (true)
    {
        sleep(1); // 消费者慢
        int data;

        // 从环形队列中获取任务或数据
        rq->pop(&data);

        // 进行数据的处理 -- 有时间消耗
        std::cout << "Consumer is processing data... ";
        std::cout << " 消费: " << data << " [" << pthread_self() % 10000 << "]" << std::endl;
    }
}
#define prodctrNum 3
#define consmrNum 2
int main()
{
    srand((uint64_t)time(nullptr) ^ getpid());

    RingQueue<int> *rq = new RingQueue<int>();
    // rq->debug(); for debug

    pthread_t consmr[3], prodctr[2];
    for (int i = 0; i < prodctrNum; i++)
        pthread_create(prodctr + i, nullptr, productor, (void *)rq);
    for (int i = 0; i < consmrNum; i++)
        pthread_create(consmr + i, nullptr, consumer, (void *)rq);

    for (int i = 0; i < prodctrNum; i++)
        pthread_join(prodctr[i], nullptr);
    for (int i = 0; i < consmrNum; i++)
        pthread_join(consmr[i], nullptr);
    return 0;
}

在这里插入图片描述

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

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

相关文章

【活动预告】SLMOps 系列(一)|SLMOps 基础 - Azure AI Studio 的 SLM 应用构建

点击蓝字 关注我们 编辑&#xff1a;Alan Wang 排版&#xff1a;Rani Sun 2023年&#xff0c;Azure OpenAI Service 引领了 AI 2.0 时代的热潮&#xff0c;各行业企业都在 AI 模型的探索与应用中持续发力。相比复杂度更高的大模型&#xff0c;有时候轻量且高效的小模型&#xf…

时序预测 | MATLAB实现BiTCN双向时间卷积神经网络的时间序列预测

时序预测 | MATLAB实现BiTCN双向时间卷积神经网络的时间序列预测 目录 时序预测 | MATLAB实现BiTCN双向时间卷积神经网络的时间序列预测预测效果基本介绍程序设计参考资料预测效果 基本介绍 MATLAB实现BiTCN双向时间卷积

【C语言基础】:自定义类型(一)--> 结构体

文章目录 一、内置类型与自定义类型1.1 内置类型&#xff08;基本数据类型&#xff09;1.2 自定义类型 二、结构体2.1 结构体的声明2.2 结构体变量的创建和初始化2.3 结构体的特殊声明2.4 结构体的自引用 三、结构体内存对齐3.1 对齐规则3.2 为什么存在内存对齐3.3 修改默认对齐…

十七、InnoDB 一次更新事务的执行过程

一、InnoDB的一次更新事务是怎么实现的&#xff1f; InnoDB的一次更新事务涉及到多个组件和步骤&#xff0c;包括Buffer Pool、BinLog、UndoLog、RedoLog以及物理磁盘。 下面是一次完整的事务更新操作过程&#xff1a; 1. 加载数据到缓存中&#xff08;Buffer Pool&#xff0…

Huggingface模型下载

1. 基础信息 huggingface的模型排行榜&#xff08;需要翻墙&#xff09;&#xff1a;https://huggingface.co/spaces/mteb/leaderboard 2. 下载模型 2.1 手动一个个下载&#xff08;方式1&#xff09; 2. 使用huggingface-cli下载(方式2) pip install -U huggingface_hub h…

英文网站怎么推广,英文网站推广排名方案

英文网站的推广对于吸引国际用户、提升品牌知名度和增加业务收入至关重要。而在全球范围内&#xff0c;谷歌是最主要的搜索引擎之一&#xff0c;因此谷歌SEO排名优化是英文网站推广的重要手段之一。本文将介绍英文网站推广的方法&#xff0c;并重点探讨谷歌SEO排名优化推广策略…

STM32嵌套中断向量控制器NVIC

一、嵌套终端向量控制器NVIC 1.1NVIC介绍 NVIC&#xff08;Nest Vector Interrupt Controller&#xff09;&#xff0c;嵌套中断向量控制器&#xff0c;作用是管理中断嵌套 先级。 核心任务是管理中断优 管理中断嵌套&#xff1a;我们在处理某个中断的过程中还没处理完这个中…

Python环境下基于慢特征分析SFA的过程监控(TE数据)

近几年来&#xff0c;慢特征分析&#xff0c;作为一种新兴的非监督型特征提取算法&#xff0c;正在逐渐兴起。它以变量随时间的一阶导数的大小来衡量变量变化的快慢&#xff0c;并从建模数据中提取出变化最慢的潜在特征变量&#xff0c;称为“不变量”或“慢特征”。 因为工业…

在新能源充电桩、智能充电枪、储能等产品领域得到广泛应用的两款微功耗轨至轨运算放大器芯片——D8541和D8542

D8541和D8542是我们推荐的两款微功耗轨至轨运算放大器芯片&#xff0c;其中D8541为单运放&#xff0c; D8542为双运放&#xff0c;它特别适用于NTC温度采集电路、ADC基准电压电路、有源滤波器、电压跟随器、信号放大器等电路应用&#xff0c;在新能源充电桩、智能充电枪、…

显示器亮度调节,如何调屏幕亮度

我们常说的显示器亮度&#xff0c;其实就是屏幕亮度。在使用电脑的时候呢&#xff0c;屏幕亮度直接可以影响我们的视觉感官&#xff0c;太亮度或者过暗都会伤害视力&#xff0c;而且眼睛看着也不舒服。那么电脑如何调屏幕亮度呢?操作方法很简单。接下来小编为大家介绍&#xf…

2024测试员最佳跳槽频率是多少?进来看看你是不是符合!

最近笔者刷到一则消息&#xff0c;一位测试员在某乎上分享&#xff0c;从月薪5K到如今的20K&#xff0c;他总共跳了10次槽&#xff0c;其中还经历过两次劳动申诉&#xff0c;拿到了大几万的赔偿&#xff0c;被同事们称为“职场碰瓷人”。 虽说这种依靠跳槽式的挣钱法相当奇葩&…

预训练大模型最佳Llama开源社区中文版Llama2

Llama中文社区率先完成了国内首个真正意义上的中文版Llama2-13B大模型&#xff0c;从模型底层实现了Llama2中文能力的大幅优化和提升。毋庸置疑&#xff0c;中文版Llama2一经发布将开启国内大模型新时代。 作为AI领域最强大的开源大模型&#xff0c;Llama2基于2万亿token数据预…

ObjectiveC-04-类的创建以属性、方法定义详细

在本小节中&#xff0c;笔者会详细讲解下ObjC的类的相关内容&#xff0c;包括创建、构造、方法、属性以及属性读取等相关知识&#xff0c;先来看下类的组成&#xff1a; 类的创建 ObjC是在C语言基础上扩展的&#xff0c;在编写OS软件时可以混用两种语言。但它们之间是有区别的…

全球X射线源市场持续增长 我国高端产品研制能力较弱

全球X射线源市场持续增长 我国高端产品研制能力较弱 X射线源&#xff0c;即X射线发生器&#xff0c;是产生和发射X射线的装置&#xff0c;从阴极发射电子&#xff0c;经阴极与阳极之间的电场加速后&#xff0c;高速轰击阳极靶面&#xff0c;产生X射线射出&#xff0c;是X射线检…

LeetCode.2908. 元素和最小的山形三元组 I

题目 2908. 元素和最小的山形三元组 I 分析 首先&#xff0c;看到这道题&#xff0c;第一反应就是暴力方法&#xff0c;三层for循环&#xff0c;枚举每一种情况&#xff0c;代码如下 class Solution {public int minimumSum(int[] nums) {int min Integer.MAX_VALUE;for(i…

Linux 进程信号:内核中信号结构、阻塞信号、捕捉信号

目录 一、阻塞信号 1、信号的状态 2、内核中的信号 信号集&#xff08;Signal Set&#xff09; task_struct 结构体 信号处理函数&#xff08;Handler&#xff09; 信号传递与调度 3、“signal_struct结构体”与“信号集sigset_t” 4、信号集操作函数 5、信号屏蔽字si…

【Hadoop大数据技术】——Hive数据仓库(学习笔记)

&#x1f4d6; 前言&#xff1a; Hive起源于Facebook&#xff0c;Facebook公司有着大量的日志数据&#xff0c;而Hadoop是实现了MapReduce模式开源的分布式并行计算的框架&#xff0c;可轻松处理大规模数据。然而MapReduce程序对熟悉Java语言的工程师来说容易开发&#xff0c;但…

还在问要不要学Python?看完这篇你就知道了

前不久教育界的一个消息&#xff0c;引发了广泛的关注。 今年9月开学后&#xff0c;浙江三到九年级信息技术课将替换新教材&#xff0c;八年级将新增Python课程内容。新高一信息技术编程语言由VB替换为Python&#xff0c;大数据、人工智能、程序设计与算法按照教材规划五六年级…

抽象类和接口(java初识)

1.抽象类 1.1抽象类的概念 在面向对象中&#xff0c;所有对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息描绘一个具体的对象&#xff0c;这样的类就是抽象类。 例子&#xff1a; 说明…

【C语言终章】预处理详解(上)

【C语言终章】预处理详解&#xff08;上&#xff09; 当你看到了这里时&#xff0c;首先要恭喜你&#xff01;因为这里就是C语言的最后一站了&#xff0c;你的编程大能旅途也将从此站开始&#xff0c;为坚持不懈的你鼓个掌吧&#xff01; &#x1f955;个人主页&#xff1a;开敲…