当前位置: 首页 > news >正文

Linux中的线程3

死锁

在Linux操作系统中,死锁(Deadlock)是指两个或多个进程(或线程)在执行过程中,因互相持有对方所需的资源而又都在等待对方释放资源,导致它们都无法继续执行下去的一种状态。这种僵局会浪费系统资源,甚至可能导致系统崩溃。

案例:

// 线程A和B,以及资源X和Y的初始状态

资源X: 空闲

资源Y: 空闲

线程A: 未持有资源

线程B: 未持有资源

// 线程A获取资源X

资源X: 被线程A持有

资源Y: 空闲

线程A: 持有资源X

线程B: 未持有资源

// 线程B获取资源Y

资源X: 被线程A持有

资源Y: 被线程B持有

线程A: 持有资源X

线程B: 持有资源Y

// 线程A请求资源Y,但资源Y被线程B持有,线程A阻塞

资源X: 被线程A持有

资源Y: 被线程B持有

线程A: 持有资源X,等待资源Y

线程B: 持有资源Y

// 线程B请求资源X,但资源X被线程A持有,线程B阻塞

资源X: 被线程A持有

资源Y: 被线程B持有

线程A: 持有资源X,等待资源Y

线程B: 持有资源Y,等待资源X

// 死锁发生,线程A和B都无法继续执行

造成的原因

死锁通常发生在多进程或多线程环境中,当满足以下四个条件时,就可能发生死锁:

  1. 互斥条件:一个资源只能被一个进程(线程)访问,即资源独占。
  2. 占有且等待:进程(线程)在占有一个资源时,可以请求其他资源。
  3. 不可剥夺条件:一个资源只能由其持有者释放,不能强行剥夺。
  4. 循环等待条件:多个进程(线程)之间形成一种循环等待资源的关系,每个进程(线程)等待下一个进程(线程)所持有的资源。
解决方式

为了避免和解决死锁,可以采取以下几种方法:

  1. 资源预分配:在程序设计中尽量避免进程(线程)同时申请多个资源,通过资源预分配降低死锁的可能性。
  2. 资源有序性:统一规定资源的获取顺序,尽量避免进程(线程)按不同的顺序请求资源。
  3. 资源剥夺:当一个进程(线程)持有某些资源并请求其他资源时,如果无法满足请求,可以剥夺该进程(线程)之前所持有的资源。
  4. 死锁检测与恢复:使用算法检测死锁的发生,并进行相应的恢复措施,例如终止某些进程(线程)或回滚操作。

读写锁

读写锁的基本概念

读写锁将线程对共享资源的访问请求分为读请求和写请求两种:

  • 读请求:当多个线程发出读请求时,这些线程可以同时执行,共享数据的值可以同时被多个发出读请求的线程获取。
  • 写请求:当多个线程发出写请求时,这些线程只能一个一个地执行(同步执行)。此外,当发出读请求的线程正在执行时,发出写请求的线程必须等待前者执行完后才能开始执行;反之亦然。
  • 读写锁是一把锁

读写锁的核心特性

  1. 读锁和写锁的互斥性
    • 当一个线程持有写锁时,其他线程无法获取读锁或写锁(读写不可以同时进行)。
    • 当一个或多个线程持有读锁时,其他线程可以获取读锁,但无法获取写锁
    • (写的优先级高于读)。
  2. 读锁的共享性
    • 多个线程可以同时持有读锁,而不会相互阻塞。这是因为读取操作不会修改共享资源的数据,因此多个线程同时读取同一个资源不会产生数据竞争。
  3. 写锁的独占性
    • 只有一个线程可以持有写锁,其他线程必须等待该线程释放写锁后才能获取写锁。这是因为写入操作会修改共享资源的数据,如果多个线程同时写入同一个资源,就会导致数据不一致。
  4. 引用计数机制
    • 读写锁的实现通常采用引用计数的方式。当一个线程获取读锁时,读写锁会记录该线程的引用计数,只有当所有持有读锁的线程都释放读锁后,写线程才能获取写锁。这种方式可以确保在写线程获取写锁之前,所有读取操作都已经完成,从而避免数据竞争。

读写锁的操作

在Linux中,读写锁可以通过多种方式实现,包括但不限于使用POSIX线程(pthread)库中的函数。以下是一些常用的操作:

  1. 初始化读写锁
    • 使用PTHREAD_RWLOCK_INITIALIZER宏赋值给读写锁变量,或者调用pthread_rwlock_init()函数进行初始化。
  2. 获取锁
    • 读锁:通过pthread_rwlock_rdlock()pthread_rwlock_tryrdlock()函数获取。前者在锁不可用时会阻塞线程,后者则不会阻塞线程,直接返回错误码
    • 写锁:通过pthread_rwlock_wrlock()pthread_rwlock_trywrlock()函数获取。同样,前者在锁不可用时会阻塞线程,后者则不会阻塞线程
  3. 释放锁
    • 无论是读锁还是写锁,都可以通过pthread_rwlock_unlock()函数释放。
  4. 销毁读写锁
    • 当读写锁不再使用时,可以通过pthread_rwlock_destroy()函数将其销毁。

读写锁的应用场景

读写锁特别适用于那些对共享资源进行频繁读取而入较的场景。例如,在Web服务器中,缓存数据通常被频繁读取而很少写入,此时使用读写锁可以显著提升并发性能。

线程A加写锁成功,线程B请求读锁:

线程B阻塞

线程A持有读锁,线程B请求写锁 :

线程B阻塞

线程A拥有读写,线程B请求读锁 :

线程B加锁

线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁:

线程B阻塞,线程C阻塞

线程B加锁,线程C阻塞

线程C加锁

线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁 :

线程B阻塞,线程C阻塞

线程C加锁, 线程B阻塞

线程B加锁

---------------------------------------------------------------------------------------------------------------------------------

互斥锁:读写串行

读写锁:

读:并行

写:串行

举例说明读写锁的具体应用

通过上面的代码可以预防读写的数据紊乱,使其能够正确读操作和写操作

条件变量

条件变量不是锁,可以阻塞线程(但不是什么时候都能阻塞线程)

条件变量通常与互斥锁(mutex)一起使用,以确保线程在访问共享资源或检查条件时的互斥性。

1. 条件变量的基本概念

    定义:条件变量是线程间同步的一种机制,它给多个线程提供了一个会合的场所。
    作用:通过允许线程阻塞和等待另一个线程发送信号的方法,条件变量弥补了互斥锁(Mutex)  的不足。
    使用场景:当线程需要等待某个条件成立时,可以使用条件变量来挂起线程,并在条件成立时被唤醒。

2. 条件变量的初始化

条件变量在使用前需要进行初始化。Linux中提供了两种初始化方式:

    静态初始化:使用宏PTHREAD_COND_INITIALIZER直接初始化静态分配的条件变量。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态初始化:使用pthread_cond_init函数对动态分配的条件变量进行初始化。

pthread_cond_t cond;  

    pthread_cond_init(&cond, NULL); // NULL表示使用默认属性

3. 等待条件变量

线程可以通过调用pthread_cond_wait或pthread_cond_timedwait函数来等待(阻塞)条件变量。

    pthread_cond_wait:无限期等待条件变量变为真。

    pthread_cond_wait(&cond, &mutex);

在调用此函数之前,线程必须锁定与条件变量关联的互斥锁。函数内部会先解锁互斥锁,然后使线程阻塞在条件变量上。当条件变量被信号唤醒时,线程会重新锁定互斥锁并继续执行。

限时等待:pthread_cond_timedwait:等待条件变量变为真,但设置了超时时间。

struct timespec ts;  
clock_gettime(CLOCK_REALTIME, &ts);  
ts.tv_sec += timeout_seconds; // 设置超时时间

pthread_cond_timedwait(&cond, &mutex, &ts);

    此函数在达到超时时间或条件变量被信号唤醒时返回。

4. 通知条件变量

当条件成立时,可以通过调用pthread_cond_signal或pthread_cond_broadcast函数来通知等待条件变量的线程。

    pthread_cond_signal:唤醒等待条件变量的一个线程。

    pthread_cond_signal(&cond);

如果有多个线程在等待条件变量,则由调度策略决定哪个线程被唤醒。

pthread_cond_broadcast:唤醒等待条件变量的所有线程。

    pthread_cond_broadcast(&cond);

    这会导致所有等待条件变量的线程都被唤醒,但它们需要重新竞争互斥锁以访问共享资源。

5. 销毁条件变量

当条件变量不再需要时,应使用pthread_cond_destroy函数进行销毁。

pthread_cond_destroy(&cond);

6. 注意事项

    条件变量的使用必须配合互斥锁,以确保线程在访问共享资源或检查条件时的互斥性。
    pthread_cond_wait和pthread_cond_timedwait函数在返回时,都会重新锁定与条件变量关联的互斥锁。
    使用条件变量时,应避免唤醒丢失问题,即在条件变量被信号唤醒和线程重新锁定互斥锁之间,条件可能已经不再满足。

例:使用条件变量实现生产者,消费者模型

成功运行

修改后的源码如下

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <time.h> // 引入时间头文件,用于随机数生成  // 定义链表节点结构体  
typedef struct node {  int data;           // 节点存储的数据  struct node* next;  // 指向下一个节点的指针  
} Node;  // 全局变量  
Node* head = NULL;      // 链表头指针,初始为空  
pthread_mutex_t mutex;  // 互斥锁,用于保护共享资源(链表)  
pthread_cond_t cond;    // 条件变量,用于线程间同步  // 生产者线程函数  
void* produce(void *arg) {  while (1) { // 无限循环,模拟持续生产  Node* pnew = (Node*)malloc(sizeof(Node)); // 分配新节点内存  if (!pnew) { // 内存分配失败检查  perror("malloc failed");  exit(EXIT_FAILURE); // 退出程序  }  pnew->data = rand() % 1000; // 生成0到999之间的随机数作为节点数据  pthread_mutex_lock(&mutex); // 加锁,保护链表操作  pnew->next = head; // 新节点指向当前头节点  head = pnew;       // 更新头节点为新节点  printf("Produce: %ld, %d\n", (long)pthread_self(), pnew->data); // 打印生产信息  pthread_cond_signal(&cond); // 发送信号,通知可能等待的消费者线程  pthread_mutex_unlock(&mutex); // 解锁  sleep(rand() % 3); // 生产者休眠随机时间,模拟生产耗时  }  return NULL; // 实际上这个return语句永远不会执行,因为存在无限循环  
}  // 消费者线程函数  
void* consume(void *arg) {  while (1) { // 无限循环,模拟持续消费  pthread_mutex_lock(&mutex); // 加锁,保护链表操作  while (head == NULL) { // 如果链表为空,则等待  pthread_cond_wait(&cond, &mutex); // 等待条件变量,同时释放锁,并在条件满足时被唤醒时重新获取锁  }  Node* pdel = head; // 取出头节点作为待删除节点  head = head->next; // 更新头节点为下一个节点  printf("Consume: %ld, %d\n", (long)pthread_self(), pdel->data); // 打印消费信息  free(pdel); // 释放已消费节点的内存  pthread_mutex_unlock(&mutex); // 解锁  sleep(rand() % 2); // 消费者休眠随机时间,模拟消费耗时  }  return NULL; // 实际上这个return语句永远不会执行,因为存在无限循环  
}  int main() {  pthread_t p1, p2; // 定义两个线程ID  srand(time(NULL)); // 初始化随机数生成器  pthread_mutex_init(&mutex, NULL); // 初始化互斥锁  pthread_cond_init(&cond, NULL); // 初始化条件变量  pthread_create(&p1, NULL, produce, NULL); // 创建生产者线程  pthread_create(&p2, NULL, consume, NULL); // 创建消费者线程  pthread_join(p1, NULL); // 等待生产者线程结束(注意:这里实际上会导致死锁,因为生产者有无限循环)  pthread_join(p2, NULL); // 等待消费者线程结束(同样,这也会导致死锁)  // 在实际应用中,你可能需要其他机制来优雅地终止线程,比如使用全局变量作为停止标志  pthread_mutex_destroy(&mutex); // 销毁互斥锁  pthread_cond_destroy(&cond); // 销毁条件变量  // 注意:由于生产者和消费者都有无限循环,这里的main函数实际上无法正常退出。  // 为了演示目的,这里保留了原样,但在实际应用中应该避免这种情况。  return 0; // 程序正常结束(但在这个例子中,由于线程的死循环,这行代码实际上不会被执行)  
}

信号量

一、概念

在Linux系统中,线程信号量(Semaphore)是一种用于控制多个线程对共享资源访问的同步机制。它本质上是一个非负整数计数器,用于协调多个线程对共享资源的访问。信号量不直接传输数据,而是作为一种许可或限制,确保在任何时刻,对共享资源的访问是安全的、有序的。

帮助理解:

假设有一个公共的水井,多个村民(线程)需要来打水(访问共享资源)。信号量就像是一个计数器,记录了当前水井旁可以容纳多少村民(信号量的值)。

  • 当一个村民来打水时(线程访问共享资源),他首先查看信号量的值(调用sem_wait)。如果信号量的值大于0(表示水井旁还有空位),则他减去1(进入水井旁打水,信号量值减1),开始打水。如果信号量的值为0(表示水井旁已经满了),则他等待(线程被阻塞)。
  • 当一个村民打完水离开时(线程完成共享资源的访问),他通过增加信号量的值(调用sem_post)来通知其他等待的村民(唤醒一个阻塞的线程)。

信号量主要用于实现两种功能:

  1. 互斥(Mutual Exclusion):确保同一时刻只有一个线程能够访问某个共享资源,防止数据竞争和不一致。
  2. 同步(Synchronization):协调多个线程的执行顺序,确保它们按照预期的顺序访问共享资源或执行特定操作。
二、相关函数

在Linux中,操作信号量的主要函数包括sem_initsem_waitsem_postsem_getvaluesem_destroy等。这些函数定义在<semaphore.h>头文件中,通常与pthread库一起使用。

  1. sem_init
    • 功能:初始化一个信号量。
    • 原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
    • 参数:
      • sem:指向信号量对象的指针。
      • pshared:指定信号量的作用域。0表示线程间共享,非0表示进程间共享。
      • value:信号量的初始值。
    • 返回值:成功返回0,失败返回-1。
  2. sem_wait(P操作)
    • 功能:将信号量的值减1。如果信号量的值小于等于0,则调用线程将被阻塞,直到信号量的值大于0。
    • 原型:int sem_wait(sem_t *sem);
    • 参数:sem为指向信号量对象的指针。
    • 返回值:成功返回0,失败返回-1。
  3. sem_post(V操作)
    • 功能:将信号量的值加1。如果有线程因为sem_wait调用而阻塞在该信号量上,则其中一个线程将被唤醒。
    • 原型:int sem_post(sem_t *sem);
    • 参数:sem为指向信号量对象的指针。
    • 返回值:成功返回0,失败返回-1。
  4. sem_getvalue
    • 功能:获取信号量的当前值。
    • 原型:int sem_getvalue(sem_t *sem, int *sval);
    • 参数:
      • sem:指向信号量对象的指针。
      • sval:用于存储信号量当前值的指针。
    • 返回值:成功返回0,失败返回-1。
  5. sem_destroy
    • 功能:销毁一个信号量,释放其资源。
    • 原型:int sem_destroy(sem_t *sem);
    • 参数:sem为指向信号量对象的指针。
    • 返回值:成功返回0,失败返回-1。

通过这种方式,信号量确保了任何时刻对水井的访问都是有序的,避免了冲突和混乱。

使用信号量知识点生成上个案例同样的功能

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <semaphore.h>  
#include <unistd.h>  
#include <time.h>  #define BUFFER_SIZE 10  typedef struct node {  int data;  struct node* next;  
} Node;  Node* head = NULL;  
Node* tail = NULL;  // 信号量:用于控制队列的容量  
sem_t empty;  
// 信号量:用于同步生产者和消费者  
sem_t full;  
// 互斥锁:保护队列的修改  
pthread_mutex_t mutex;  void* produce(void *arg) {  while (1) {  // 等待队列有空位  sem_wait(&empty);  // 加锁以保护队列  pthread_mutex_lock(&mutex);  // 创建新节点  Node* pnew = (Node*)malloc(sizeof(Node));  if (!pnew) {  perror("malloc failed");  pthread_mutex_unlock(&mutex);  sem_post(&empty); // 释放信号量,因为实际上没有增加队列中的元素  continue;  }  pnew->data = rand() % 1000;  // 将新节点添加到队列尾部  if (tail == NULL) {  head = tail = pnew;  pnew->next = NULL;  } else {  tail->next = pnew;  tail = pnew;  pnew->next = NULL;  }  // 解锁  pthread_mutex_unlock(&mutex);  // 通知消费者队列中有数据了  sem_post(&full);  printf("Produce: %d\n", pnew->data);  sleep(rand() % 3);  }  return NULL;  
}  void* consume(void *arg) {  while (1) {  // 等待队列中有数据  sem_wait(&full);  // 加锁以保护队列  pthread_mutex_lock(&mutex);  // 从队列头部取出节点  Node* pdel = head;  if (head == NULL) {  // 理论上不应该发生,因为full信号量保证了队列不为空  pthread_mutex_unlock(&mutex);  sem_post(&empty); // 释放信号量,因为实际上没有消费任何数据  continue;  }  head = head->next;  if (head == NULL) {  tail = NULL; // 如果队列为空,则尾指针也为空  }  // 解锁  pthread_mutex_unlock(&mutex);  // 消费数据  printf("Consume: %d\n", pdel->data);  free(pdel);  // 通知生产者队列中有空位了  sem_post(&empty);  sleep(rand() % 2);  }  return NULL;  
}  int main() {  pthread_t p1, p2;  sem_init(&empty, 0, BUFFER_SIZE); // 初始时队列为空,有BUFFER_SIZE个空位  sem_init(&full, 0, 0); // 初始时队列中没有数据  pthread_mutex_init(&mutex, NULL);  srand(time(NULL));  pthread_create(&p1, NULL, produce, NULL);  pthread_create(&p2, NULL, consume, NULL);  // 假设我们不想在这里等待线程结束,可以注释掉以下两行  // pthread_join(p1, NULL);  // pthread_join(p2, NULL);  // 注意:在实际应用中,你应该等待线程结束或者采取其他措施来避免程序过早退出  // 清理资源  // 注意:由于我们注释掉了pthread_join,这里的清理代码可能不会执行,或者执行时线程可能还在运行  // sem_destroy(&empty);  // sem_destroy(&full);  // pthread_mutex_destroy(&mutex);  return 0;  
}

相关文章:

Linux中的线程3

死锁 在Linux操作系统中&#xff0c;死锁&#xff08;Deadlock&#xff09;是指两个或多个进程&#xff08;或线程&#xff09;在执行过程中&#xff0c;因互相持有对方所需的资源而又都在等待对方释放资源&#xff0c;导致它们都无法继续执行下去的一种状态。这种僵局会浪费系…...

内网权限维持——利用WMI进行权限维持

文章目录 一、WMI事件订阅机制简介二、利用事件订阅进行权限维持三、防御方式 一、WMI事件订阅机制简介 WMI&#xff08;Windows Management Instrumentation&#xff0c;Windows管理规范&#xff09;是windows提供的一种能够直接与系统进行交互的机制&#xff0c;旨在为系统中…...

【数据结构算法经典题目刨析(c语言)】括号匹配问题(图文详解)

&#x1f493; 博客主页&#xff1a;C-SDN花园GGbond ⏩ 文章专栏&#xff1a;数据结构经典题目刨析(c语言) 目录 一、题目描述 二、解题思路 三、代码实现 一、题目描述 二、解题思路 问题要求将三种类型括号匹配&#xff0c;其中包括顺序匹配和数量匹配 使用栈的后进先…...

浅谈 Spring AOP框架 (1)

文章目录 一、什么是 Spring AOP二、为什么要使用 Spring AOP三、AOP 的一些应用场景四、AOP 的组成五、如何使用 Spring AOP六、Spring AOP 的实现原理6.1、JDK 和 CGLIB 的区别 一、什么是 Spring AOP AOP (Aspect Oriented Programming) &#xff1a;面向切面编程&#xff…...

Linux 面试准备 - 2024

复习一下&#xff0c;资料来自慕课网课程 Linux 速成班和一些网上面试资料。 1. Linux 内核功能 1. 内存管理 2. 进程管理 3. 设备驱动程序 4. 系统调用和安全防护 2. 文件系统 - 一切皆文件 2.1 文件目录 /根目录etc配置文件bin必要命令usr 二级目录&#xff08;非用户…...

C++笔记---类和对象(中)

1. 类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。 一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;分别为&#xff1a;构造函数&#xff0c;析构函数&#xff0c;拷贝构…...

【C++】入门基础知识

河流之所以能够到达目的地&#xff0c;是因为它懂得怎样避开障碍。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;C的发展历史 • &#x1f330;1.C发展历史 • &#x1f330;2.C的迭代与更新 • &#x1f330;3.编程语言排…...

AI的应用场景和未来展望

AI&#xff08;人工智能&#xff09;的应用场景广泛且多样&#xff0c;已经深入到我们生活的方方面面&#xff0c;成为现代社会不可或缺的一部分。 AI的应用场景 1、通用软件与工具型应用 办公软件&#xff1a;如钉钉、飞书等&#xff0c;通过AI技术提供内容生成与摘要、智能…...

vim、sublime、notepad文本编辑器的使用

VIM&#xff1a; Windows上配置gvim并作为C和C的IDE Windows上配置gvim并作为C和C的IDE | Reasuon sublime notepad...

PyCharm中的外部更改识别:终极解决方案指南

标题&#xff1a;PyCharm中的外部更改识别&#xff1a;终极解决方案指南 引言 PyCharm&#xff0c;作为JetBrains公司开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;以其强大的功能和高效的代码编辑体验而广受开发者喜爱。然而&#xff0c;在开发过程中&#xf…...

Qt——QTCreater ui界面如何统一设置字体

第一步&#xff1a;来到 ui 设计界面&#xff0c;鼠标右键点击 改变样式表 第二步&#xff1a;选择添加字体 第三步&#xff1a;选择字体样式和大小&#xff0c;点击 ok 第四步&#xff1a;点击ok或apply&#xff0c;完成设置...

Linux驱动入门实验班day03-GPIO子系统概述

3.通用框架1——最简单方式1&#xff1a;执行命令cat /sys/kernel/debug/gpio查看串口信息 gpio4对应的下列 方式2&#xff1a; 对于按键GPIO4_14:对应第四组第14个引脚 gpiochip3 ,从96开始&#xff0c; 9614110&#xff1b;...

240803-沉侵式翻译插件配置Ollama的API实现网页及PDF文档的翻译

1. 在插件中点击Options按钮 2. 在开发者模式中启动Enable Beta Testing Features 3 在General中进行设置 ## 4. 在Expand中设置API的URL 5. Qwen&#xff1a;0.5B网页翻译效果 6. Qwen&#xff1a;0.5BPDF翻译效果 7. 参考文献 gemma - 给沉浸式翻译插件配置本地大模型o…...

HTML-08.表单标签

一.表单标签 场景&#xff1a;在网页中主要负责数据采集功能&#xff0c;如注册、登录等数据采集 标签&#xff1a;<form> 表单项&#xff1a;不同类型的input元素、下拉列表、文本域等 <input>:定义表单项。通过type属性控制输入形式 <select>:定义下拉列表…...

SAP ABAP se16n 双击跳转实现

参考老白 SAP小技巧 改造SE16N(九 双击跳转及字段描述优化) (qq.com) se16n 双击跳转实现 我的实现 se38 lse16nlcl 287行 call method cl_gui_control>set_focusexporting control alv_grid. *.....at the moment do detail view on double clickCALL METHOD cl_gu…...

Linux shell编程学习笔记68: curl 命令行网络数据传输工具 选项数量雷人(上)

0 前言 在网络时代&#xff0c;有经常需要在网络上传输数据&#xff0c;时我们需要通过网络下载文件&#xff0c;为了满足这种时代需要&#xff0c;Linux提供了众多网络命令&#xff0c;我们今天先研究curl命令。例如&#xff0c;我们可以使用 curl 从 URL 下载文件&#xff0…...

马尔科夫决策过程

马尔科夫决策过程 贝尔曼方程 贝尔曼方程&#xff08;Bellman Equation&#xff09;是动态规划中的一个核心概念&#xff0c;用于解决最优决策问题。贝尔曼方程通过递归的方式&#xff0c;将问题分解为子问题&#xff0c;从而使得最优策略的求解变得可行。贝尔曼方程广泛应用…...

未知攻焉知防:从攻击者视角看网络安全的“攻守之道”

自首届网络安全攻防实战演练开展以来&#xff0c;这一活动已成为网络安全领域备受关注的大事件。今年&#xff0c;攻防实战演练更上升到了一个全新高度&#xff0c;包括行动任务数量、演练周期时长、攻击强度以及演练类别等&#xff0c;较以往都有极大提升&#xff0c;堪称“史…...

数字孪生赋能智慧城市大脑智建设方案(可编辑65页PPT)

引言&#xff1a;随着科技的飞速发展&#xff0c;智慧城市的建设已成为全球城市发展的新趋势。数字孪生技术作为其中的关键技术之一&#xff0c;正逐步赋能智慧城市大脑的建设&#xff0c;推动城市治理从数字化向智能化、智慧化转型升级。本方案旨在简要介绍数字孪生赋能智慧城…...

c++----内存管理

okk&#xff0c;大家好。我们大家学习了鄙人的前面前面几篇博客&#xff0c;并且还稍微使用了一些c的基础知识。并且我们前面都说过&#xff0c;我们前面学习的知识都说过。我们前面的几篇博客都是我们以后使用c基础。但是我们大家都知道现在代码都关注什么时间啊&#xff0c;内…...

C++——哈希结构

1.unordered系列关联式容器 本节主要介绍unordered_map和unordered_set两个容器&#xff0c;底层使用哈希实现的 unordered_map 1.unordered_map是储存<key,value>键值对的关联式容器&#xff0c;其允许通过key快速查找到对应的value&#xff0c;和map非常相似&#x…...

智能小程序 Ray 开发面板 SDK —— 无线开关一键执行模板教程(一)

1. 准备工作 前提条件 已阅读 Ray 新手村任务&#xff0c;了解 Ray 框架的基础知识已阅读 使用 Ray 开发万能面板&#xff0c;了解 Ray 面板开发的基础知识 构建内容 在此 Codelab 中&#xff0c;您将利用面板小程序开发构建出一个支持一键执行及自动化的无线开关面板&…...

rockDB(1)

文章目录 概述编译rocksdb压缩库 基本接口 小结 概述 RocksDB 是 Facebook 的一个实验项目&#xff0c;目的是希望能开发一套能在服务器压力下&#xff0c;真正发挥高 速存储硬件性能的高效数据库系统。这是一个C库&#xff0c;允许存储任意长度二进制 KV 数据。支持原 子读写…...

[element-ui] 自动获取el-input的焦点

<el-input v-model"filterPlanName" ref"autoFocus" ></el-input>this.$nextTick((_) > {this.$refs.autoFocus.focus(); })参考&#xff1a; [element-ui]自动获取el-input的焦点...

智能闹钟的睡眠评估算法是如何工作的呢

智能闹钟的睡眠评估算法是智能闹钟功能的核心部分&#xff0c;它主要通过以下几个步骤来工作&#xff1a; 一、数据收集 传感器数据&#xff1a;智能闹钟内置多种传感器&#xff0c;如心率传感器、呼吸传感器、体动传感器以及环境传感器&#xff08;如温度、湿度、光线传感器…...

Vue + View-ui-plus Upload实现手动上传

本文实现Vue Upload组件多文件手动上传&#xff0c;支持上传图片&#xff08;image&#xff09;、压缩文件(zip/rar)、表格(excel)、pdf 一、dom结构 <Row><Col :span"19"></Col><Col :span"2"><div class"ivu-btn-uplo…...

Radxa ROCK 3C开发板编译Opencv,支持调用树莓派摄像头模块V2

目录 1、ROCK 3C和树莓派摄像头模块V2介绍2、ROCK 3C在rsetup开启支持3、测试指令4、编译Opencv4.1 增加swap&#xff0c;确保内存够用4.2 安装依赖和下载opencv4.3 编译参考链接 5、使用opencv调用树莓派摄像头模块V2 1、ROCK 3C和树莓派摄像头模块V2介绍 ROCK 3C 是一款基于…...

Spring02

文章目录 1. IOC/DI注解开发2. IOC/DI注解开发管理第三方bean3. Spring整合4. AOP简介5. AOP入门案例6. AOP工作流程7. AOP配置管理8. AOP事务管理 1. IOC/DI注解开发 注解开发定义bean用的是2.5版提供的注解&#xff0c;纯注解开发用的是3.0版提供的注解 pom.xml添加依赖 &l…...

Linux系统中的高级内核模块调试技术

引言 在Linux系统中进行高级内核模块开发时&#xff0c;调试是不可或缺的重要环节。调试技术能够帮助开发人员发现和解决代码中的错误和问题&#xff0c;提高开发效率和代码质量。本文将深入探讨Linux系统中高级内核模块调试的技术和方法&#xff0c;包括常用的调试工具、调试…...

竞赛报名管理系统asp.net+sqlserver

竞赛报名管理系统 功能简单 内容单调 适合学习 asp.net 三层架构 sqlserver2022数据库 账号登陆注册 用户管理 克赛管理 竞赛报名 竞赛评分 公告维护 修改密码 新增竞赛 2019数据库版本低 附加不了 需要高版本数据库 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据…...