rt-thread 线程间同步方法详解

rt-thread 线程间同步方法详解

  • 一、什么是线程间同步
    • 线程同步的必要性
    • 线程同步的挑战
  • 二、同步方式
    • 1、信号量
      • 信号量工作机制
      • 信号量的管理方式
        • 信号量的创建与删除
        • 信号量的获取与释放
      • 信号量的典型应用场景
      • 信号量的注意事项
    • 2、互斥量
      • 互斥量工作机制
      • 互斥量的特性
      • 互斥量的操作接口
      • 互斥量的使用示例
      • 互斥量与信号量的区别
      • 注意事项
    • 3、事件集
      • 事件集工作机制
      • 事件集的管理方式
      • 事件集使用示例
      • 事件集注意事项
  • 三、对比
      • 信号量、事件集、互斥量对比
      • 关键区别说明

一、什么是线程间同步

线程同步是指多线程环境下,通过特定机制协调线程的执行顺序,避免数据竞争和资源冲突。当多个线程共享同一资源时,无序访问可能导致数据不一致或程序错误,同步机制确保线程有序、安全地操作共享资源。

线程同步的必要性

多线程并发执行时,若未进行同步控制,可能引发以下问题:

  • 竞态条件(Race Condition):多个线程同时修改共享数据,导致结果依赖线程执行顺序。
  • 数据不一致:线程读取到中间状态或未更新的数据。
  • 死锁:线程互相等待对方释放资源,导致程序僵持。

线程同步的挑战

  • 性能开销:锁机制可能导致线程阻塞,降低并发效率。
  • 死锁风险:需避免循环等待、持有锁时请求其他锁等情况。
  • 调试难度:多线程问题通常难以复现和定位。

       在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,那么多个线程之间如何 “默契” 协作才能使这项工作无差错执行?下面举个例子说明。
       例如一项工作中的两个线程:一个线程从传感器中接收数据并且将数据写到共享内存中,同时另一个线程周期性的从共享内存中读取数据并发送去显示,下图描述了两个线程间的数据传递:
在这里插入图片描述
如果对共享内存的访问不是排他性的,那么各个线程间可能同时访问它,这将引起数据一致性的问题。例如,在显示线程试图显示数据之前,接收线程还未完成数据的写入,那么显示将包含不同时间采样的数据,造成显示数据的错乱。

二、同步方式

1、信号量

以生活中的停车场为例来理解信号量的概念:

  • 当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位;
  • 当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候;
  • 当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场;待空车位填满后,又禁止外部车辆进入。

在此例子中,管理员就相当于信号量,管理员手中空车位的个数就是信号量的值(非负数,动态变化);停车位相当于公共资源(临界区),车辆相当于线程。车辆通过获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源。

信号量工作机制

       信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
       信号量工作示意图如下图所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。
在这里插入图片描述

信号量的管理方式

信号量控制块中含有信号量相关的重要参数,在信号量各种状态间起到纽带的作用。信号量相关接口如下图所示,对一个信号量的操作包含:创建 / 初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量。
在这里插入图片描述

信号量的创建与删除

动态创建信号量使用rt_sem_create函数,需指定名称和初始值:

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);

参数flag通常设置为RT_IPC_FLAG_FIFO(按队列顺序唤醒)或RT_IPC_FLAG_PRIO(按优先级唤醒)。

静态信号量通过rt_sem_init初始化:

rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag);

删除动态信号量用rt_sem_delete,释放静态信号量用rt_sem_detach

信号量的获取与释放

获取信号量使用rt_sem_take,可设置超时时间:

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout);

timeoutRT_WAITING_FOREVER表示永久等待,RT_WAITING_NO表示不等待。

无阻塞尝试获取信号量用rt_sem_trytake

rt_err_t rt_sem_trytake(rt_sem_t sem);

释放信号量用rt_sem_give

rt_err_t rt_sem_release(rt_sem_t sem);

信号量的典型应用场景

资源互斥访问
通过二进制信号量保护共享资源:

rt_sem_t sem_mutex = rt_sem_create("mutex", 1, RT_IPC_FLAG_FIFO);/* 线程A */
rt_sem_take(sem_mutex, RT_WAITING_FOREVER);
/* 访问共享资源 */
rt_sem_release(sem_mutex);/* 线程B */
rt_sem_take(sem_mutex, RT_WAITING_FOREVER);
/* 访问共享资源 */
rt_sem_release(sem_mutex);

线程同步
生产者-消费者模型中,用信号量通知数据就绪:

rt_sem_t sem_data = rt_sem_create("data", 0, RT_IPC_FLAG_FIFO);/* 生产者线程 */
produce_data();
rt_sem_release(sem_data);/* 消费者线程 */
rt_sem_release(sem_data, RT_WAITING_FOREVER);
consume_data();

应用示例

#include <rtthread.h>#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5/* 信号量退出标志 */
static rt_bool_t sem_flag = 0;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{static rt_uint8_t count = 0;while (1){if (count <= 100){count++;}else{rt_kprintf("thread1 exiting...\n");sem_flag = 1;rt_sem_release(dynamic_sem);count = 0;return;}/* count 每计数 10 次,就释放一次信号量 */if (0 == (count % 10)){rt_kprintf("t1 release a dynamic semaphore.\n");rt_sem_release(dynamic_sem);}}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{static rt_err_t result;static rt_uint8_t number = 0;while (1){/* 永久方式等待信号量,获取到信号量,则执行 number 自加的操作 */result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);if (sem_flag && result == RT_EOK){rt_kprintf("thread2 exiting...\n");rt_sem_delete(dynamic_sem);sem_flag = 0;number = 0;return;}else{number++;rt_kprintf("t2 take a dynamic semaphore. number = %d\n", number);}}
}/* 信号量示例的初始化 */
int semaphore_sample(void)
{/* 创建一个动态信号量,初始值是 0 */dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);if (dynamic_sem == RT_NULL){rt_kprintf("create dynamic semaphore failed.\n");return -1;}else{rt_kprintf("create done. dynamic semaphore value = 0.\n");}rt_thread_init(&thread1,"thread1",rt_thread1_entry,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",rt_thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY - 1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);

运行结果

 \ | /
- RT -     Thread Operating System/ | \     4.1.1 build Sep  2 2024 14:52:062006 - 2022 Copyright by RT-Thread team
msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 1
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 2
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 3
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 4
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 5
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 6
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 7
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 8
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 9
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 10
thread1 exiting...
thread2 exiting...msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 1
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 2
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 3
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 4
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 5
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 6
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 7
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 8
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 9
thread1 release a dynamic semaphore.
thread2 take a dynamic semaphore. number = 10
thread1 exiting...
thread2 exiting...

信号量的注意事项

  • 优先级反转问题:高优先级线程等待低优先级线程释放信号量时,可能被中优先级线程抢占。可通过优先级继承或屏蔽中断解决。
  • 死锁风险:避免多个信号量嵌套获取时形成循环等待。
  • 性能影响:频繁的信号量操作会增加系统开销,需合理设计临界区范围。

RT-Thread还提供rt_sem_control函数修改信号量属性,如调整等待线程的唤醒顺序策略。实际开发中应根据场景选择信号量类型,并配合其他IPC机制(如互斥锁、事件集)使用。

2、互斥量

互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。互斥量类似于只有一个车位的停车场:当有一辆车进入的时候,将停车场大门锁住,其他车辆在外面等候。当里面的车出来时,将停车场大门打开,下一辆车才可以进入。

互斥量工作机制

      互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。
      互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图时所示。这个特性与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。
在这里插入图片描述

互斥量的特性

  • 独占性:互斥量一次只能被一个线程持有,其他线程必须等待。
  • 优先级继承:当高优先级线程等待低优先级线程释放互斥量时,低优先级线程会临时继承高优先级,以避免优先级反转。
  • 递归锁:同一线程可以多次获取互斥量,但需要对应次数的释放操作。

互斥量的操作接口

RT-Thread 提供以下 API 用于操作互斥量:

// 创建互斥量
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);// 删除互斥量
rt_err_t rt_mutex_delete(rt_mutex_t mutex);// 获取互斥量(阻塞)
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout);// 释放互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex);

互斥量的使用示例

以下是一个简单的互斥量使用示例,展示如何保护共享资源:

#include <rtthread.h>static rt_mutex_t mutex;
static int shared_data = 0;void thread1_entry(void *parameter)
{while (1) {rt_mutex_take(mutex, RT_WAITING_FOREVER);shared_data++;rt_kprintf("Thread1: shared_data = %d\n", shared_data);rt_mutex_release(mutex);rt_thread_mdelay(100);}
}void thread2_entry(void *parameter)
{while (1) {rt_mutex_take(mutex, RT_WAITING_FOREVER);shared_data--;rt_kprintf("Thread2: shared_data = %d\n", shared_data);rt_mutex_release(mutex);rt_thread_mdelay(100);}
}int mutex_sample(void)
{mutex = rt_mutex_create("test_mutex", RT_IPC_FLAG_FIFO);if (mutex == RT_NULL) {rt_kprintf("create mutex failed.\n");return -1;}rt_thread_t tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, 512, 20, 10);rt_thread_t tid2 = rt_thread_create("thread2", thread2_entry, RT_NULL, 512, 20, 10);if (tid1 != RT_NULL) rt_thread_startup(tid1);if (tid2 != RT_NULL) rt_thread_startup(tid2);return 0;
}

互斥量与信号量的区别

  • 用途不同:互斥量用于保护共享资源,信号量用于线程间同步。
  • 所有权:互斥量具有所有者(获取的线程),信号量没有所有者。
  • 优先级继承:互斥量支持优先级继承,信号量不支持。
  • 计数:互斥量只能是 0 或 1,信号量可以有多个计数。

注意事项

  • 避免死锁:确保互斥量的获取和释放成对出现,避免嵌套获取互斥量导致死锁。
  • 优先级设置:合理设置线程优先级,以充分发挥优先级继承机制的作用。
  • 超时设置:在获取互斥量时设置合理的超时时间,避免线程长期阻塞。

RT-Thread 的互斥量机制为多线程环境下的资源共享提供了可靠保障,合理使用可以显著提升系统的稳定性和性能。

3、事件集

事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。下面以坐公交为例说明事件,在公交站等公交时可能有以下几种情况:

  • P1 坐公交去某地,只有一种公交可以到达目的地,等到此公交即可出发。
  • P1 坐公交去某地,有 3 种公交都可以到达目的地,等到其中任意一辆即可出发。
  • P1 约另一人 P2 一起去某地,则 P1 必须要等到 “同伴 P2 到达公交站” 与“公交到达公交站”两个条件都满足后,才能出发。

这里,可以将 P1 去某地视为线程,将 “公交到达公交站”、“同伴 P2 到达公交站” 视为事件的发生,情况①是特定事件唤醒线程;情况②是任意单个事件唤醒线程;情况③是多个事件同时发生才唤醒线程。

事件集工作机制

事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。
RT-Thread 定义的事件集有以下特点:

  • 事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
  • 事件仅用于同步,不提供数据传输功能;
  • 事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。
在这里插入图片描述

事件集的管理方式

事件集控制块中含有与事件集相关的重要参数,在事件集功能的实现中起重要的作用。事件集相关接口如下图所示,对一个事件集的操作包含:创建 / 初始化事件集、发送事件、接收事件、删除 / 脱离事件集。
在这里插入图片描述

  1. 创建事件集

    rt_event_t rt_event_create(const char *name, rt_uint8_t flag);
    
    • name:事件集名称,用于调试。
    • flag:可选RT_IPC_FLAG_FIFO(默认)或RT_IPC_FLAG_PRIO(按优先级唤醒线程)。
  2. 删除事件集

    rt_err_t rt_event_delete(rt_event_t event);
    

    需确保没有线程正在等待该事件集。

  3. 发送事件

    rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
    
    • set:事件标志位,如0x01表示事件1,0x03表示事件1和2。
  4. 接收事件

    rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved);
    
    • optionRT_EVENT_FLAG_AND(与逻辑)或RT_EVENT_FLAG_OR(或逻辑)。
    • timeout:等待超时时间(RT_WAITING_FOREVER为永久等待)。
    • recved:输出参数,返回实际接收到的事件标志位。

事件集使用示例

#include <rtthread.h>#define THREAD_PRIORITY      9
#define THREAD_TIMESLICE     5#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)/* 事件控制块 */
static struct rt_event event;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;/* 线程 1 入口函数 */
static void thread1_recv_event(void *param)
{rt_uint32_t e;/* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e) == RT_EOK){rt_kprintf("thread1: OR recv event 0x%x\n", e);}rt_kprintf("thread1: delay 1s to prepare the second event\n");rt_thread_mdelay(1000);/* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e) == RT_EOK){rt_kprintf("thread1: AND recv event 0x%x\n", e);}/* 执行完该事件集后进行事件集的脱离,事件集重复初始化会导致再次运行时,出现重复初始化的问题 */rt_event_detach(&event);rt_kprintf("thread1 leave.\n");
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;/* 线程 2 入口 */
static void thread2_send_event(void *param)
{rt_kprintf("thread2: send event3\n");rt_event_send(&event, EVENT_FLAG3);rt_thread_mdelay(200);rt_kprintf("thread2: send event5\n");rt_event_send(&event, EVENT_FLAG5);rt_thread_mdelay(200);rt_kprintf("thread2: send event3\n");rt_event_send(&event, EVENT_FLAG3);rt_kprintf("thread2 leave.\n");
}int event_sample(void)
{rt_err_t result;/* 初始化事件对象 */result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO);if (result != RT_EOK){rt_kprintf("init event failed.\n");return -1;}rt_thread_init(&thread1,"thread1",thread1_recv_event,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY - 1, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",thread2_send_event,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);

运行结果:

 \ | /
- RT -     Thread Operating System/ | \     4.1.1 build Sep  5 2024 15:53:212006 - 2022 Copyright by RT-Thread team
msh >event_sample
thread2: send event3
thread1: OR recv event 0x8
thread1: delay 1s to prepare the second event
msh >thread2: send event5
thread2: send event3
thread2 leave.
thread1: AND recv event 0x28
thread1 leave.msh >event_sample
thread2: send event3
thread1: OR recv event 0x8
thread1: delay 1s to prepare the second event
msh >thread2: send event5
thread2: send event3
thread2 leave.
thread1: AND recv event 0x28
thread1 leave.

事件集注意事项

  • 事件标志位:通常使用32位无符号整数,每位代表一个独立事件。
  • 线程唤醒逻辑RT_EVENT_FLAG_OR在任一事件发生时唤醒线程;RT_EVENT_FLAG_AND需所有事件均发生。
  • 资源管理:动态创建的事件集需手动删除,静态初始化可用rt_event_init
  • 优先级反转:高优先级线程可能因等待低优先级线程发送事件而被阻塞,需合理设计线程优先级。

三、对比

以下为 RT-Thread 中信号量、事件集、互斥量的对比表格:

信号量、事件集、互斥量对比

特性信号量事件集互斥量
用途线程同步/资源计数多事件触发同步临界区资源独占访问
资源类型计数器(二进制/计数)32位标志位(每位代表一个事件)所有权机制(带优先级继承)
释放方式rt_sem_release()rt_event_send()rt_mutex_release()
获取方式rt_sem_take()rt_event_recv()rt_mutex_take()
阻塞超时支持支持支持
优先级继承不支持不支持支持
递归锁不支持不支持支持(同一线程重复获取)
适用场景任务调度、资源分配多条件触发(如按键+定时器)共享资源保护(如全局变量)

关键区别说明

信号量

  • 二进制信号量:计数器值为 0 或 1,用于简单同步。
  • 计数信号量:计数器值可大于 1,表示剩余资源数量。

事件集

  • 支持“或”触发(任一事件满足)和“与”触发(所有事件满足)。
  • 事件标志位可手动清除或自动清除。

互斥量

  • 避免优先级反转:持有互斥量的低优先级线程会临时继承高优先级。
  • 必须由获取线程释放,不可跨线程操作。

在这里插入图片描述

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

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

相关文章

Spring Boot + Vue2 实现腾讯云 COS 文件上传:从零搭建分片上传系统

目录 一、项目目标 二、腾讯云 COS 基本配置 1. 创建存储桶 2. 获取 API 密钥 3. 设置跨域规则&#xff08;CORS&#xff09; 三、后端&#xff08;Spring Boot&#xff09;实现 1. 依赖配置 2. 配置腾讯云 COS&#xff08;application.yml&#xff09; 3. 初始化 COS…

使用 Java 获取 PDF 页面信息(页数、尺寸、旋转角度、方向、标签与边框)

目录 引言 一、安装和引入PDF处理库 二、获取 PDF 页数 三、获取页面尺寸&#xff08;宽高&#xff09; 四、获取页面旋转角度 五、判断页面方向&#xff08;横向 / 纵向&#xff09; 六、获取页面标签 七、获取页面边框信息 八、总结 引言 了解 PDF 页面属性是我们在…

基于 AI 的大前端安全态势感知与应急响应体系建设

大前端应用&#xff08;Web、APP、小程序&#xff09;作为用户交互的入口&#xff0c;面临日益复杂的安全威胁&#xff1a;从传统的 XSS 攻击、CSRF 伪造&#xff0c;到新型的供应链投毒、AI 驱动的自动化爬虫&#xff0c;再到针对业务逻辑的欺诈攻击&#xff08;如薅羊毛、账号…

Java 与 MySQL 性能优化:MySQL全文检索查询优化实践

文章目录一、引言二、InnoDB引擎下的全文检索功能详解2.1 全文索引的基本概念与原理2.2 全文索引的创建与管理2.3 全文检索的三种查询模式2.4 中文全文检索的挑战与解决方案三、CMS 场景下的全文检索性能瓶颈分析3.1 索引构建与维护开销3.2 查询性能瓶颈3.3 锁机制与并发性能问…

应用软件格式渗透 利用word去渗透(MS10-087)

用到的靶机为&#xff1a;WinXP漏洞原理&#xff1a;一、漏洞触发机制与核心组件 漏洞根源&#xff1a;RTF文件解析逻辑缺陷 触发组件&#xff1a;Microsoft Word的RTF&#xff08;Rich Text Format&#xff09;解析引擎&#xff0c;具体涉及 mso.dll 模块中的 路径规范化函数&…

解密AWS VPC路由表:显式关联与隐式关联,谁决定了网络出口?

大家好&#xff0c;今天我们来聊一个在 AWS 云计算世界里既基础又关键的话题&#xff1a;VPC 路由表。 很多刚接触 AWS 的朋友&#xff0c;在配置网络时可能会遇到这样的困惑&#xff1a;为什么我的 EC2 实例无法访问互联网&#xff1f;为什么某些子网的网络策略和其他子网不一…

LeetCode题解---<203.移除链表元素>

文章目录题目代码及注释关键点题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,…

【JavaScript高级】构造函数、原型链与数据处理

目录构造函数和原型构造函数实例成员和静态成员构造函数的问题构造函数原型 prototype对象原型 \_\_proto\_\_constructor 构造函数构造函数、实例、原型对象三者之间的关系原型链JavaScript 的成员查找机制&#xff08;规则&#xff09;原型对象的this指向扩展内置对象继承cal…

项目进度与预算脱节,如何进行同步管理

项目进度与预算脱节会导致资源浪费、成本超支和项目延期。进行同步管理的方法包括&#xff1a;建立统一的项目进度预算管理体系、实施实时监控与反馈机制、采用项目管理工具辅助同步管理。尤其是实施实时监控与反馈机制&#xff0c;通过持续监测进度与预算的匹配情况&#xff0…

TCP半关闭

理解TCP半关闭&#xff1a;像水管一样的网络连接控制 从全关闭到半关闭&#xff1a;为什么需要这种机制&#xff1f; 想象你和朋友正在通电话讨论一个重要项目&#xff1a; 全关闭&#xff1a;就像突然挂断电话&#xff0c;双方都无法再说话半关闭&#xff1a;你说"我说完…

衡石科技技术手册--仪表盘过滤控件详解

过滤控件说明 过滤控件 的定义 过滤控件用于在仪表盘中过滤图表数据&#xff0c;分为仪表盘内过滤控件和全局过滤控件。 过滤控件结构说明 字段类型描述uidSTRING过滤控件唯一识别 idappIdLONG过滤控件所属的应用 iddataAppIdLONG字段来源是数据包时的数据包 iddashboar…

ASP.NET Core中数据绑定原理实现详解

在ASP.NET Core 中&#xff0c;数据绑定是将 HTTP 请求中的数据&#xff08;如表单、查询字符串、请求体等&#xff09;映射到控制器动作方法参数或模型对象的过程。以下将从原理、核心组件、执行流程及关键机制等方面详细解析其实现逻辑。 一、数据绑定的核心原理与组件 1. 数…

牛客:HJ24 合唱队[华为机考][最长递增子集][动态规划]

学习要点 求最长递增字列求最长递减子列 题目链接 合唱队_牛客题霸_牛客网 题目描述 解法&#xff1a;动归求最长递增子列 #include <iostream> #include <vector> using namespace std;int main() {int n;while (cin >> n) {// 输入的数组int tmp;vect…

C语言的相关基础概念和常用基本数据类型

1.相关概念变量与常量的定义常量&#xff1a;在程序运行中其值不能改变的量。变量&#xff1a;在程序运行中其值可以改变的量。存储器的区分 RAMROM中文名易失存储器不易失存储器特点掉电丢失数据&#xff0c;但存取快掉电不丢失数据&#xff0c;但存取幔标识符标识符只能…

Spring boot整合dubbo+zookeeper

Spring boot整合dubbozookeeper 下文将简述springboot整合dubbozookeeper实现apiproviderconsumer模式&#xff0c;Api用于定于interface,provider和consumer依赖Api,provider实现api接口&#xff0c;consumer调用provider。 spring boot版本&#xff1a;3.5.3 jdk版本&#xf…

ImportError: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32‘ not found

简介&#xff1a;在复现 VLM-R1 项目并尝试将其中的 GRPO 算法应用到自己的任务时&#xff0c;按照官方文档配置好环境后&#xff0c;运行过程中遇到了一个非常离谱的错误&#xff1a; ImportError: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32 not found 这个问题极…

基于Spring Boot的生活用品电商网站的设计与实现

第1章 摘要随着电商行业的飞速发展&#xff0c;生活用品电商网站作为线上购物的一部分&#xff0c;逐渐成为消费者日常购物的重要渠道。为提升网站的管理效率和用户体验&#xff0c;设计并实现了一款基于Spring Boot的生活用品电商网站。该系统通过合理的架构设计&#xff0c;提…

数据结构 单链表(1)

1.概念和结构概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。通过指针链接次序实现的要怎么理解呢?这是一张链表的结构图:与顺序表不同的是&#xff0c;链表里的每节“车厢” (仔细观察这…

Python爬虫实战:研究PyMongo库相关技术

1. 引言 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地获取这些数据并进行存储和分析,成为了数据科学领域的重要研究方向。网络爬虫作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 MongoDB 作为一种流行的 NoSQL 数据库,能够灵…

【世纪龙科技】迈腾B8汽车整车检测与诊断仿真实训系统

在汽车技术日新月异的今天&#xff0c;如何培养既懂理论又精实践的高素质汽修人才&#xff0c;成为职业教育领域亟待突破的课题。江苏世纪龙科技凭借深厚的技术积淀与教育洞察&#xff0c;重磅推出《汽车整车检测与诊断仿真实训系统》&#xff0c;以迈腾B8为原型&#xff0c;通…