[嵌入式系统-61]:RT-Thread-内核:原子操作的支持, 原子操作与互斥锁的比较

目录

原子操作

1. 原子操作简介

2. 原子操作的优点

3. RT-Thread 原子操作的实现与使用方法

4. RT-Thread 原子操作 API

原子读

原子写

原子数据交换

原子加

原子减

原子异或

原子与

原子或

原子标志检查与置位

原子标志清除

原子比较与交换

5. 综合示例


原子操作

1. 原子操作简介

原子操作(Atomic operation)是指一种不可分割的操作,要么完全执行成功,要么完全不执行。原子操作的执行过程中不允许有任何中断,如果出现了中断那么操作的结果就无法保证。原子操作通常用于多线程编程中,保证多个线程之间的并发执行不会出现数据竞争等问题。

在实现原子操作时,通常使用硬件指令或者操作系统提供的原子操作函数来保证操作的原子性。 在应用层面,原子操作可以用于实现一些高级的同步和并发控制机制。例如,在多线程编程中,如果多个线程都需要访问同一个共享变量,为了避免数据竞争问题,可以使用原子操作来保证对该变量的操作是原子的。我们以 ARM 内核执行一个 i++操作为例:

movl i, %eax                            //内存访问,读取 i 变量到 cpu 的 eax 寄存器
addl $1, %eax                           //修改寄存器的值
movl %eax, i                            //将寄存器中的值写回内存复制错误复制成功

我们看到对于编码的工程师我们执行一个 i++的操作仅需一行C语言代码,在编译后 i++就会被翻译成三条汇编指令,所以在这三条指令之间是可能会被系统调度、中断等事件打断的,因而我们在一些场景就需要一气呵成执行完上述操作,原子操作就具备这样的能力。

备注:

操作系统的线程调度的时间片,是以执行汇编指令为基本单位的,而不是以C语言语句为最基本的执行单元。一条C语言语句会编译成多条汇编指令!!!

2. 原子操作的优点

在 RT-Thread 中我们可以采取开关全局中断调度器上锁等方式对临界区资源进行保护,其他 OS 也会提供类似的操作,若采用原子操作后我们可以提高临界区代码的执行效率,大幅提升系统的运行效率,同时也会在一定程度上降低编程的复杂度

关中断解决的中断与线程之间访问共享资源冲突问题;

互斥锁解决的是线程与线程之间访问共享变量的冲突问题。

中断和线程在访问共享资源时可能遇到的冲突,以及如何解决这些冲突。

现在,让我们更详细地讨论一下这两个概念及其解决方案。

  1. 中断与线程访问共享资源的冲突

    • 中断:中断是硬件或软件向CPU发出的信号,用于请求CPU暂停当前正在执行的程序,转而执行与中断相关的处理程序。中断处理程序执行完毕后,CPU会返回到被中断的程序并继续执行。
    • 冲突:当中断处理程序和线程同时访问同一共享资源时,可能会发生数据不一致或破坏的情况。
    • 解决方案:关中断
      • 为了避免这种情况,在操作系统或某些硬件驱动中,可能会在访问某些关键共享资源时暂时关闭中断。这样,中断处理程序就不会打断当前线程的执行,从而避免了潜在的冲突。
      • 但这种方法有一个明显的缺点:它会降低系统的响应性,因为中断是系统响应外部事件(如硬件输入、定时器到期等)的重要方式。因此,关中断应该尽可能短,并且只在必要时使用。
  2. 线程与线程之间访问共享变量的冲突

    • 线程:线程是操作系统调度的最小单位,是进程中的一个执行流。多个线程可以共享进程的资源,包括内存空间、文件句柄等。
    • 冲突:当多个线程同时访问并修改同一个共享变量时,可能会导致数据不一致或破坏的情况。这种现象被称为“竞态条件”(Race Condition)。
    • 解决方案:互斥锁(Mutex)
      • 互斥锁是一种同步原语,用于保护共享资源,防止多个线程同时访问。当一个线程获得互斥锁时,其他尝试获取该锁的线程将被阻塞,直到锁被释放。
      • 使用互斥锁可以确保在任何时候只有一个线程能够访问被保护的共享资源,从而避免了竞态条件。
      • 但使用互斥锁也需要谨慎,因为过度或不当地使用它们可能会导致死锁(Deadlock)或其他同步问题。

总之,关中断和互斥锁是解决不同类型冲突的有效方法,但都需要谨慎使用以避免引入其他问题

下文是一个简单变量自增的示例:

采用开关全局中断的方式实现的对临界区的保护:

  ...  
    int a = 5;
    level = rt_hw_interrupt_disable();
    a++;
    rt_hw_interrupt_enable(level);
  ...复制错误复制成功

我们若采用 RT-Thread 提供的原子操作 API 可以这么做:

  ...      
    int a = 5;
    rt_atomic_add(&a,1);
  ...  复制错误复制成功

显然采用原子操作的方式更加简单一些,且避免了开关全局中断带来的性能损失。

3. RT-Thread 原子操作的实现与使用方法

RT-Thread 对 32-bit 的 ARM、32-bit 的 RISC-V 与 64-bit 的 RISC-V 中支持原子操作的内核提供了原子操作支持,使用对应平台的原子操作汇编指令与相关指令实现,默认支持,无需用户关心实现,用户使用时仅需在工程包含rtatomic.h即可使用该文件提供的原子操作 API,详细支持情况如下:

指令架构RT-Thread 适配内核的原子指令支持情况
32-bit ARM采用 ARM 指令集的绝大多数内核支持原子指令,不支持的内核有 cortex-m0,cortex-m0+,arm926,lpc214x,lpc24xx,s3c24x0,AT91SAM7。
32-bit RISC-V采用 RV32 指令集的大部分内核支持原子操作,部分不支持的 BSP 有:core-v-mcu,rv32m1_vega。
64-bit RISC-V采用 RV64 指令集的大部分内核支持原子操作,部分不支持的 BSP 有:juicevm。

若工具链支持 C11 标准的原子操作 API 也可以使用 menuconfig 配置RT_USING_STDC_ATOMIC 宏,此时调用rtatomic.h中提供的宏实际上最终会调用 C11 标准提供的 API,menuconfig 的配置方法如下:

RT-Thread Kernel --->
   [*]Use atomic implemented in stdatomic.h复制错误复制成功

对于不支持原子操作的内核,用户在工程中包含rtatomic.h并使用该文件提供的 API 时,此时会采用开关全局中断的方式软实现原子操作

原子操作(Atomic Operation)是指在多线程或并发环境中,不会被其他线程或进程打断的操作。这些操作在执行过程中是不可中断的,从而保证了对共享资源访问的完整性和一致性。在汇编语言中,一些指令被设计为原子操作,以确保它们在执行过程中不会被其他线程或进程干扰。

以下是一些常见的原子操作汇编指令(这些指令可能因处理器架构和指令集的不同而有所差异):

  1. x86 架构(如 Intel 和 AMD)
    • xchg:交换两个寄存器或内存位置的值。
    • lock prefix:与某些指令(如 addsubincdecandorxorcmpxchgbtsbtrbtcbtscxaddcmpxchg8bcmpxchg16b 等)结合使用,以确保操作的原子性。
    • cmpxchg:比较并交换,如果目标位置的值与给定值相等,则将该位置的值替换为新值。
    • xadd:交换并加法,将源操作数的值加到目标操作数上,并将目标操作数的原始值存储到源操作数中。
    • cmpxchg8b 和 cmpxchg16b:用于比较并交换 64 位和 128 位的数据。
  2. ARM 架构
    • LDREX(Load Exclusive)和 STREX(Store Exclusive):这两个指令一起使用来实现原子操作。LDREX 加载一个值并标记内存位置为“独占”,然后 STREX 尝试存储一个新值到该位置。如果自 LDREX 以来该位置没有被其他处理器修改过,则 STREX 成功并返回 0;否则返回非 0 值。
    • LDREXD 和 STREXD:这些是 64 位版本的 LDREX 和 STREX
    • LDAEX(Load-Acquire Exclusive)和 STLEX(Store-Release Exclusive):这些是 ARMv8 架构中引入的指令,用于支持更复杂的内存顺序模型。
  3. 其他架构
    • 其他处理器架构(如 MIPS、PowerPC、RISC-V 等)也有自己的原子操作指令集,但具体指令和用法各不相同。

请注意,在使用原子操作时,需要确保你的代码符合目标平台的内存模型和并发模型。此外,编译器也可能提供内置函数或内联汇编来简化原子操作的使用。例如,在 C/C++ 中,可以使用 <stdatomic.h> 头文件中的函数来进行原子操作。

最后,虽然原子操作在某些情况下很有用,但它们并不是解决所有并发问题的万能药。过度使用原子操作可能会导致性能下降和复杂性增加。在设计并发系统时,应该仔细考虑并发控制策略,并根据具体需求选择适当的同步原语。

原子操作指令虽然为并发编程提供了强大的工具,用于确保在多线程环境中对共享资源的访问是原子性的,但它们也存在一些缺点和不足:

  1. 性能开销
    • 原子操作通常比非原子操作要慢,因为它们需要额外的机制来确保操作的原子性。这可能导致在性能敏感的应用程序中成为瓶颈
    • 原子操作可能导致CPU的缓存行在不同核心之间频繁地来回传输(缓存行弹跳),这称为“缓存行伪共享”(False Sharing)或“缓存行争用”(Cache Line Bouncing),进一步降低性能。
  2. 忙等待
    • 某些原子操作(如自旋锁)可能会导致线程忙等待即线程不断地检查某个条件是否成立,而不是让出CPU给其他线程使用。这浪费了CPU资源,尤其是在多核处理器上。
  3. 不支持复杂操作
    • 原子操作通常只支持简单的、不可分割的操作,如递增、递减、交换等。对于更复杂的操作(如读取-修改-写入操作),可能需要将多个简单的原子操作组合起来,这增加了编程的复杂性和出错的可能性。
  4. 不支持条件等待
    • 原子操作通常不支持条件等待和通知机制。如果需要在满足特定条件时阻塞线程,并在条件满足时通知线程,那么需要结合其他同步机制(如条件变量)来使用。
  5. 死锁和优先级反转
    • 虽然原子操作本身不会导致死锁,但在复杂的并发系统中,如果不恰当地使用原子操作(如嵌套锁、循环等待等),仍然可能出现死锁问题。此外,原子操作也可能导致优先级反转问题,尤其是在结合中断禁用或其他同步机制时。
  6. 有限的应用场景
    • 原子操作主要适用于对共享资源进行简单、原子性操作的场景。对于需要复杂的同步和协作的问题(如读写操作的优化、复杂的数据结构更新等),原子操作可能无法满足需求,需要使用其他更复杂的同步机制。
  7. 硬件依赖
    • 原子操作指令的具体实现和性能可能因处理器架构和指令集的不同而有所差异。这要求程序员在编写并发代码时需要考虑到目标平台的特性,增加了编程的复杂性。

因此,在使用原子操作指令时,需要权衡其优点和缺点,并根据具体的应用场景和需求来选择合适的同步机制。

原子操作与互斥锁的比较:

原子操作和互斥锁在并发编程中都起着重要作用,但它们在实现机制、适用场景和性能方面有所不同。

  1. 定义和特性:
    • 原子操作:是指在执行期间不会被其他线程中断的操作。它们通常用于对单个内存位置的读取、写入或修改操作,确保对该内存位置的操作是原子的,即不会被其他线程的操作所干扰。原子操作具有不可分割、不可变性和原子性等特性,可以确保对共享数据的操作要么完全执行,要么完全不执行,从而避免数据不一致的问题。
    • 互斥锁(Mutex):是一种同步机制,用于保护共享资源,防止并发访问。当一个线程获得了互斥锁后,其他线程必须等待该锁被释放才能继续执行。互斥锁通过保护临界区来防止数据的竞争和不一致性。它的基本操作包括加锁和解锁两个步骤。
  2. 适用场景:
    • 原子操作适用于对单个内存位置进行简单、不可分割的操作的场景。由于原子操作通常比互斥锁具有更高的性能,因此在需要高性能和高并发性的情况下,原子操作是一个很好的选择。
    • 互斥锁则更适用于保护较大块的共享资源或执行多个互斥操作的场景。当多个线程需要同时访问和修改共享资源时,互斥锁可以确保在任何时候只有一个线程能够访问该资源,从而避免数据的竞争和不一致性。
  3. 性能:
    • 原子操作通常比互斥锁具有更高的性能,因为它们不需要额外的内存开销(如锁的数据结构)和上下文切换的开销。此外,由于原子操作是不可分割的,因此它们可以减少因线程间竞争而导致的性能下降。
    • 互斥锁在保护较大块的共享资源或执行多个互斥操作时可能会带来一些性能开销。此外,当多个线程尝试同时获取互斥锁时,它们可能会被阻塞或进行上下文切换,从而导致性能下降。
  4. 编程复杂性:
    • 原子操作相对简单,通常只需要使用特定的原子操作指令或函数即可。然而,对于复杂的并发问题,可能需要组合多个原子操作来实现所需的同步效果。
    • 互斥锁的使用相对复杂一些,需要程序员在代码中显式地加锁和解锁,以确保对共享资源的正确访问。此外,还需要注意避免死锁和优先级反转等问题。

总之,原子操作和互斥锁都是并发编程中常用的同步机制,它们各有优缺点和适用场景。在选择使用哪种机制时,需要根据具体的应用需求和性能要求来权衡利弊。

4. RT-Thread 原子操作 API

RT-Thread 提供了 11 个使用频率较高的原子操作 API。

通过上述API,可以确保多线程环境中,该操作完成后,才会切换到其他线程,保证了操作的完整性。

RT-Thread 原子操作 API作用
rt_atomic_t rt_hw_atomic_load(volatile rt_atomic_t *ptr)原子的从 ptr 地址加载/读一个字
void rt_atomic_store(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 val 写入 ptr 地址,不返回当前的值。
rt_atomic_t rt_atomic_exchange(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值替换为 val,并返回当前的值
rt_atomic_t rt_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值与 val 相加
rt_atomic_t rt_atomic_sub(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值与 val 相减
rt_atomic_t rt_atomic_xor(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值与 val 按位异或
rt_atomic_t rt_atomic_and(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值与 val 按位与
rt_atomic_t rt_atomic_or(volatile rt_atomic_t *ptr, rt_atomic_t val)原子的将 ptr 地址处的值与 val 按位或
rt_atomic_t rt_atomic_flag_test_and_set(volatile rt_atomic_t *ptr)原子的将 ptr 地址处的值置 1
void rt_atomic_flag_clear(volatile rt_atomic_t *ptr)原子的将 ptr 地址处的值清 0
rt_atomic_t rt_atomic_compare_exchange_strong(volatile rt_atomic_t *ptr, rt_atomic_t *old, rt_atomic_t new)原子的将 ptr 地址处的值与 val 进行比较与交换,并返回比较结果

原子操作函数详细的释义:

原子读
rt_atomic_t rt_atomic_load(volatile rt_atomic_t *ptr);复制错误复制成功

该操作函数的语义为:使用原子操作方式从 ptr 地址指向的 4 字节空间加载一个字。

参数描述
ptr原子对象地址
返回值返回 ptr 地址处的 4 字节数据
原子写
void rt_atomic_store(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 val 写入 ptr 地址指向的 4 字节空间。

参数描述
ptr原子对象地址
val期望写入 ptr 地址处的数据
返回值NULL
原子数据交换
rt_atomic_t rt_atomic_exchange(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节空间的数据交换为 val,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望交换的数据
返回值返回 ptr 地址处交换前的 4 字节数据
原子加
rt_atomic_t rt_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节数据与 val 相加,将结果写入 ptr 地址指向的 4 字节空间,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望相加的值
返回值返回 ptr 地址处修改前的 4 字节数据
原子减
rt_atomic_t rt_atomic_sub(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节数据减去 val,将结果写入 ptr 地址指向的 4 字节空间,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望减去的值
返回值返回 ptr 地址处修改前的 4 字节数据
原子异或
rt_atomic_t rt_atomic_xor(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节数据与 val 进行按位异或,将结果写入 ptr 地址指向的 4 字节空间,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望异或的值
返回值返回 ptr 地址处修改前的 4 字节数据
原子与
rt_atomic_t rt_atomic_and(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节数据与 val 进行按位相与,将结果写入 ptr 地址指向的 4 字节空间,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望相与的值
返回值返回 ptr 地址处修改前的 4 字节数据
原子或
rt_atomic_t rt_atomic_or(volatile rt_atomic_t *ptr, rt_atomic_t val);复制错误复制成功

该操作函数的语义为:使用原子操作方式将 ptr 地址指向的 4 字节数据与 val 进行按位相或,将结果写入 ptr 地址指向的 4 字节空间,返回 ptr 地址处修改前的 4 字节数据。

参数描述
ptr原子对象地址
val期望相或的值
返回值返回 ptr 地址处修改前的 4 字节数据
原子标志检查与置位
rt_atomic_t rt_atomic_flag_test_and_set(volatile rt_atomic_t *ptr);复制错误复制成功

该操作函数的语义为:对 ptr 地址指向的 4 字节原子标志进行设置,并返回该原子标志对象做设置操作之前的值。若 ptr 地址指向的 4 字节数据之前状态为 0,那么经过此操作之后,该原子标志对象的状态变为了状态 1,并且返回 0 。如果 ptr 地址指向的 4 字节数据之前为状态 1,那么经过此操作之后,它仍然为状态 1,并且返回 1。所以若我们将原子标志对象作为一个“锁”来用的话,可判断这个函数接口的返回值,若返回 0,则说明锁成功,可以对多线程共享对象做相关的修改操作;如果返回的是 状态 1,则该原子标志已经被其他线程占用,需等待释放。

参数描述
ptr原子对象地址,此处地址指向的 4 字节数据只能为 0 或 1
返回值设置状态
原子标志清除
void rt_atomic_flag_clear(volatile rt_atomic_t *ptr);复制错误复制成功

该操作函数的语义为:清除标志,将标志清 0,对 ptr 地址指向原子标志进行清零操作。如果我们将原子标志对象用作“锁”的话,那么执行此操作就相当于释放锁。

参数描述
ptr原子对象地址
返回值NULL
原子比较与交换
rt_atomic_t rt_atomic_compare_exchange_strong(volatile rt_atomic_t *ptr, rt_atomic_t *old, rt_atomic_t new);复制错误复制成功

该操作函数的语义为:第一个参数指向原子类型对象;第二个参数指向要进行比较的对象,并且如果比较失败,那么该操作会将原子对象的当前值拷贝到该参数所指向的对象中;第三个参数指定存储到原子对象中的值。 如果比较成功,那么 new 值会被存放到原子对象中,并且返回 1;如果比较失败,那么当前原子对象的值会被拷贝到 old 所指向的对象中,并且返回 0。

参数描述
ptr原子对象地址
old被比较的对象
new期望更新的对象
返回值比较结果

5. 综合示例

在工程中包含rtatomic.h,然后将示例添加至工程即可进行简单的原子操作验证。

/* 在工程中添加该头文件 */
#include <rtatomic.h>

rt_atomic_t value1 = 10;
rt_atomic_t value2 = 5;
int main(void)
{
    /* atomic add */
    result = rt_atomic_add(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic sub */
    result = rt_atomic_sub(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic xor */
    result = rt_atomic_xor(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic or */
    result = rt_atomic_or(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic and */
    result = rt_atomic_and(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic exchange */
    result = rt_atomic_exchange(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic compare and exchange */
    result = rt_atomic_compare_exchange_strong(&value1, value2, 6);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    /* atomic load */
    result = rt_atomic_load(&value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic store */
    result = rt_atomic_store(&value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    value1 = 0;
    /* atomic flag test and set */
    result = rt_atomic_flag_test_and_set(&value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);

    /* atomic flag clear */
    result = rt_atomic_flag_clear(&value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\n", result, value1, value2);
}

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

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

相关文章

有公网IP的好处?

1. 维护远程连接需求的解决方案 公网IP是指可以通过互联网直接访问的IP地址&#xff0c;相对于私有IP地址而言具有重要的好处。公网IP的最大好处之一是解决了各行业客户的远程连接需求。由于天联组网操作简单、跨平台应用、无网络要求以及独创的安全加速方案等原因&#xff0c…

牛客美团2024年春招第一场笔试【技术】解题

1.小美的平衡矩阵 小美拿到了一个n∗n的矩阵&#xff0c;其中每个元素是 0 或者 1。 小美认为一个矩形区域是完美的&#xff0c;当且仅当该区域内 0 的数量恰好等于 1 的数量。 现在&#xff0c;小美希望你回答有多少个i∗i的完美矩形区域。你需要回答1≤i≤n的所有答案 输出…

实现优先队列——C++

目录 1.优先队列的类模板 2.仿函数的讲解 3.成员变量 4.构造函数 5。判空&#xff0c;返回size&#xff0c;返回队头 6.插入 7.删除 1.优先队列的类模板 我们先通过模板来进行初步了解 由上图可知&#xff0c;我们的模板里有三个参数&#xff0c;第一个参数自然就是你要存储的数…

ServiceNow 研究:通过RAG减少结构化输出中的幻觉

论文地址&#xff1a;https://arxiv.org/pdf/2404.08189 原文地址&#xff1a;rag-hallucination-structure-research-by-servicenow 在灾难性遗忘和模型漂移中&#xff0c;幻觉仍然是一个挑战。 2024 年 4 月 18 日 灾难性遗忘&#xff1a; 这是在序列学习或连续学习环境中出现…

工业光源-环形光源-特点

◆高密度LED排列&#xff0c;科学的结构设计&#xff1b; ◆从360方向照射&#xff0c;消除阴影&#xff1b; ◆中间开孔&#xff0c;使光源与相机镜头完美契合&#xff1a; ◆多角度可选&#xff0c;可适应不同工作距离的应用&#xff1b; ◆可选配漫射板&#xff0c;使光线均…

C++算法之sort

sort默认排序方式为从小到大 vector<int> v{3,2,6,1,-2};sort(v.begin(),v.end());for(int i0;i<v.size();i){cout<<v[i]<<" ";}想要sort从大到小排序&#xff1a; 1.是自定义cmp 2.使用自带的函数&#xff1a;

Redis__事务

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__事务 ⏱️ 创作时间&#xff1a;2024年05月02日 ———————————————— 这里写目录标题 文章目…

【Web】CTFSHOW 中期测评刷题记录(1)

目录 web486 web487 web488 web489 web490 web491 web492 web493 web494 web495 web496 web497 web498 web499 web500 web501 web502 web503 web505 web506 web507 web508 web509 web510 web486 扫目录 初始界面尝试文件包含index.php&am…

WSL2连接Windows主机的Mysql

文章目录 需求查看主机IP防火墙设置Mysql设置允许远程连接WSL2连接Mysql 需求 在WSL2&#xff08;本机Ubuntu20.04&#xff09;运行的程序需要将数据写入到本机的Mysql服务器中 查看主机IP 两种办法&#xff1a; Windows主机输入 ipconfig&#xff0c;找到带有WSL后缀的部分…

C 语言笔记:字符串处理函数

一、获取字符串长度函数 头文件&#xff1a;#include <string.h> 函数定义&#xff1a;size_t strlen(const char *s); 函数功能&#xff1a; 测字符指针 s 指向的字符串中字符的个数&#xff0c;不包括’\0’ 返回值&#xff1a;字符串中字符个数 #include <stdio.…

不坑盒子激活码免费领取

不坑盒子的一些新出来的大功能&#xff0c;都需要账号有Pro权限才能使用了。 关键是这些功能还很强大呢&#xff01;不用还不行&#xff01; 今天发现一个可以免费领不坑盒子Pro激活码的方法&#xff1a; 扫码进去后&#xff0c;就能看到激活码了&#xff1a; 复制激活码&…

基于alpha shapes的边缘点提取(matlab)

1、原理介绍 由Edelsbrunner H提出的alpha shapes算法是一种简单、有效的快速提取边界点算法。其克服了点云边界点形状影响的缺点&#xff0c;可快速准确提取边界点。如下图所示&#xff0c;对于任意形状的平面点云&#xff0c;若一个半径为a的圆&#xff0c;绕其进行滚动&…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt+hadoop + redis 医院就诊系统 设计与实现

一.项目介绍 前端&#xff1a;患者注册 、登录、查看首页、医生排班、药品信息、预约挂号、就诊记录、电子病历、处方开药、我的收藏 后端分为&#xff1a; 医生登录&#xff1a;查看当前排班信息、查看患者的挂号情况、设置患者就诊记录、电子病历、给患者开药和个人信息维护 …

安装部署大语言模型 | 通义千问

下载安装 进入ollama的仓库下载 「 qwen 7b 」 libraryGet up and running with large language models.https://ollama.com/library查找阿里的 「 qwen 」 根据自己的电脑配置情况&#xff0c;选择合适的模型 总体来说&#xff0c;模型是越大&#xff0c;效果越好&#xff0c…

Jammy@Jetson Orin Nano - Tensorflow GPU版本安装

JammyJetson Orin Nano - Tensorflow GPU版本安装 1. 源由2. 问题2.1 Tensorflow跑以下示例代码的时候&#xff0c;发现jtop中6个CPU占用率都跑满了。2.2 Jetson Orin Nano运行Tensorflow示例结果不一致 3. 分析3.1 当前版本Tensorflow 2.16.13.2 GPU版本二进制安装3.3 GPU版本…

Nginx深度解析:核心特性、应用场景与全局、events、http等全面配置指南

Nginx是一款高性能的Web服务器与反向代理服务器软件&#xff0c;以其高并发处理能力、低内存消耗和反向代理负载均衡功能闻名。它通过事件驱动、异步非阻塞I/O模型&#xff0c;实现了极高的效率和稳定性&#xff0c;广泛应用于网站部署、API代理、静态资源服务及微服务架构中&a…

边沿JK触发器

边沿JK触发器 电路组成 & 逻辑符号 工作原理 Q n 1 D Q^{n1}D Qn1D J Q n ‾ K Q n ‾ \overline{\overline{JQ^n}KQ^n} JQn​KQn​ ( J Q n ) ( K ‾ Q n ‾ ) (JQ^n)(\overline{K}\overline{Q^n}) (JQn)(KQn​) J K ‾ J Q n ‾ K ‾ Q n Q n ‾ Q n J\over…

《HCIP-openEuler实验指导手册》1.6 Apache静态资源配置(目录访问)

知识点 常用用途&#xff1a; 软件仓库镜像及提供下载服务&#xff1a; 配置步骤 删除网站主目录中的文件&#xff08;本实验机目录为/home/source ip为192.168.12.137 端口为81&#xff09; cd /home/source rm -rf *在主目录中新建6个文件夹如下图 mkdir test{1..6}新建…

Eclipse 开创性地集成 Neon Stack,将 EVM 兼容性带到 SVM 网络

2024年5月2日&#xff0c;全球——在塑造区块链网络的战略联盟的过程中&#xff0c;Eclipse 通过集成 Neon EVM 核心团队开发的技术堆栈 Neon Stack&#xff0c;成为首个打破 EVM-SVM 兼容性障碍的生态。 Eclipse 旨在通过结合以太坊和 Solana 的最佳特性&#xff0c;来重构区…

SpringBoot-@Transactional注解失效

Transactional注解失效 Transactional失效场景 以下是一些常见导致Transactional注解失效的场景&#xff0c;配合相应的Java代码演示&#xff1a; 1、方法修饰符非公开&#xff08;非public&#xff09; Transactional注解失效的原因在于Spring事务管理器如何实现对事务的代…