【操作系统笔记】并发安全问题

news2024/11/25 0:35:47

用户态抢占和内核态抢占

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

内核中可以执行以下几种程序:

  • ① 当前运行的进程陷阱程序(系统调用)故障程序(page fault) ,进程运行在内核态的时候,其实就是在执行进程在用户态触发的异常对应的异常处理程序
  • ② 中断处理程序
  • ③ 内核线程

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

用户态线程抢占的调度时机

检查当前线程是否需要被抢占的时机点(检查点):

  • 时钟中断发生,在时钟中断处理程序中判断进程实际运行的时间大于规定运行的最长时间且运行队列有优先级更高的任务

当前线程被抢占的时机点(抢占点):

  • 中断处理程序回到用户态之前

在这里插入图片描述

内核抢占的调度时机

检查当前线程是否需要被抢占的时机点(检查点):

  • 时钟中断发生,在时钟中断处理程序中判断进程实际运行的时间大于规定运行的最长时间且运行队列有优先级更高的任务

  • 线程被唤醒的时候,唤醒的任务虚拟运行时间更小(优先级高)比如,磁盘 I/O 中断唤醒正在等待数据的线程。比如,fork/ clone 创建的新进程/线程,被唤醒时。

当前线程被抢占的时机点(抢占点):

  • ① 从中断处理程序回到内核态之前
  • ② 开启抢占(preemt_enable)的时候
  • 条件:标记了 tlf_need_sched 且抢占计数器等于 0(开启抢占)

疑问点:时钟中断发生时,当前任务被抢占的条件到底是什么呢?

其实这个是要区分任务类型的,如果当前运行的任务是普通任务的话,那么就需要根据任务运行时间和它的 vruntime 来决定要不要抢占当前进程,如下代码逻辑:

在这里插入图片描述

如果当前运行的任务是实时任务的话,那么就是根据 RR 调度算法来决定要不要抢占当前任务,如下图:
在这里插入图片描述
在这里插入图片描述

根据使用场景来决定要不要配置内核抢占:

  • PREEMPT_NONE:不支持抢占,吞吐量优先,后台计算场景(处理数据)
  • PREEMPT_VOLUNTARY: 内核中放置了一些抢占点,桌面应用
  • PREEMPT:支持内核抢占,低延迟的桌面应用、嵌入式

支持内核抢占的内核,称为抢占式内核,不支持内核抢占的内核,称为非抢占式内核。

数据并发访问安全问题

内核中:每个内核程序都可以访问所有的物理内存

  • ① 两个中断处理程序同时访问一个全局数据

  • ② 中断处理程序和内核态中运行的线程同时访问一个全局数据

  • ③ 两个运行在内核态的线程同时访问一个全局数据

用户态:

  • ① 运行在同一个进程中的多个线程,共享全局内存

  • ② 进程与进程之间也可以共享内存,进程间通信

在这里插入图片描述

  • 内核可配置为抢占和非抢占,在抢占式内核中,多个线程同时访问共享内存时,会发生并发安全问题。

两个线程访问一个全局变量

一个进程中的所有线程共享这个进程的虚拟地址空间,多个线程可以同时访问一个全局变量。

在这里插入图片描述

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

  • 并发问题的根本原因是操作临界区的共享全局变量的操作不是原子操作,在高级开发语言中的一行代码可能对应多条底层汇编指令代码,如cnt++,在底层可能对应三条汇编指令,分别是从内存读取cnt保存到寄存器、将寄存器中的cnt+1,将寄存器中的cnt写回到内存,而因为抢占,我们无法预测线程在哪一条指令上被抢占,线程可能在任意指令上被抢占后切换到另一个线程去执行。

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

  • 使用对应底层是原子操作的指令代码,如atomic一类的api,使用不能被打断的原子指令,确保指令执行时不会被中断或抢占,两次读写访问的是同一个内存单元

  • 使用lock加锁技术锁定内存总线,在指令执行结束之前,其他CPU不能访问这个内存单元

  • 原子操作需要硬件级别保证,高级语言中都有对应的原子操作类

CAS 的 ABA 问题

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

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

说白了,就是某个变量中间被别人改过了(A->B),但是又改回去了(B->A),但是我们不知道它中间是否被人改过,此时必须通过增加版本号,也就是添加额外标志信息,来比较是否曾经被改过,每改一次版本号就加 1,这样比较时,除了变量值一样,版本号也必须一致,才认为是没有被修改过的。否则,就是被动过的。

CAS 自旋锁

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

普通的自旋锁存在并发问题。

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

typedef struct _lock_t {
    // 正如同餐厅叫号,turn表示当前的号码,ticket表示手中的号码
    atomic_int ticket;
    atomic_int turn; 
} lock_t;

void init(lock_t *mutex) { 
    mutex->ticket = 0; 
    mutex->turn = 0; 
}

void lock(lock_t *mutex) {
    int my_turn = atomic_fetch_add(&mutex->ticket, 1); 
    while (mutex->turn != my_turn); // spin 
}

void unlock(lock_t *mutex) {
    // 每一个顾客(线程)用完之后就呼叫下一个号码。
    mutex->turn++;
}

CAS 自旋锁的问题 — 浪费 CPU

  • 由于自旋锁导致每个线程都在执行 while 操作,占用 CPU 时间
  • 如果进入临界区的线程执行的时间过长的话,那么等待的线程,一直占用着 CPU,导致极大的 CPU 资源浪费

解决方案:

  • ① 将没有拿到锁的线程,放入到等待队列中
  • ② 阻塞没有拿到锁的线程,放弃CPU执行权
  • ③ 当正在执行临界区的线程,离开临界区的时候,从等待队列中唤醒一个线程

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

#include <stdatomic.h> 
#include <sys/types.h>
#include <unistd.h> 
#include "queue.c"

typedef struct _lock_t {
    // guard 是 lock 和 unlock 过程的一个自旋锁
    atomic_flag guard;
    // 用于标记当前锁是否被一个线程获取
    // 0表示当前还没有线程获取这把锁
    // 1表示当前已经有一个线程获取这把锁了
    int flag;
    // 如果 flag==1,那么再来获取这把锁的线程
    // 将在这个等待队列中阻塞、等待
    queue_t *wait_queue;
} lock_t;

void init(lock_t *mutex) {
    queue_init(mutex->wait_queue);
    mutex->flag = 0;
}
void lock(lock_t *mutex) {
    while(atomic_flag_test_and_set(&mutex->guard)); // spin
    if (mutex->flag == 0) { 
        mutex->flag = 1;
        atomic_flag_clear(&mutex->guard); 
    } else {
        // 将当前线程 pid 放入到队列中
        queue_add(mutex->queue, gettid()); 
        atomic_flag_clear(&mutex->guard);
        // 伪代码,阻塞当前线程,需要发起一次系统调用
        park();
    }
} 
void unlock(lock_t *mutex) {
    while(atomic_flag_test_and_set(&mutex->guard)); // spin
    if (queue_empty(mutex->wait_queue)) { 
        mutex->flag = 0;
    } else {
        // 伪代码,唤醒队列中队头的线程,需要发起系统调用
        unpark(queue_remove(mutex->wait_queue));
        // flag 值本来就是 1,这里加不加没关系
        mutex->flag = 1;
    }
    atomic_flag_clear(&mutex->guard); 
}

相当于先用一把自旋锁来保证锁内全局共享变量的安全,这里设置flag和队列入队出队操作非常快,因此不会导致自旋锁执行while等待太久导致CPU浪费。

C语言中的互斥锁API:

#include <stdio.h>
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
#include <stdlib.h> 
pthread_mutex_t mut; 

// 打印机
// 将字符串以单个字符循环输出
void my_print(char *str) 
{
    pthread_mutex_lock(&mut); 
    while(*str!=0)
    {
        printf("%c", *str++); 
        fflush(stdout); 
        sleep(1); 
    }
    printf("\n");
    pthread_mutex_unlock(&mut);
}
int main(int argc, char const *argv[]) 
{
    void *arg = NULL; //  1.初始化锁
    pthread_mutex_init(&mut, NULL); 
    pthread_t tid, tid2, tid3, tid4;
    pthread_create(&tid, NULL, print_word, "11111111"); 
    pthread_create(&tid2, NULL-, print_word, "22222222"); 
    pthread_create(&tid3, NULL, print_ word, "33333333"); 
    pthread_create(&tid4, NULL, print_word, "44444444"); 
    pthread_join(tid, &arg); // 等待,阻塞
    pthread_join(tid2, &arg); // 等待,阻塞 
    pthread_join(tid3, &arg); // 等待,阻塞 
    pthread_join(tid4, &arg); // 等待,阻塞 
    return 0;
} 

阻塞互斥锁 VS 自旋锁

阻塞互斥锁基于自旋锁实现,操作等待队列,执行的时间非常短。

  • 如果临界区执行的时间非常短,选择自旋锁

  • 如果临界区执行的时间比较长,选择阻塞互斥锁

阻塞时,需要发生系统调用,以及线程切换,需要开销。如果临界区执行的时间非常短,使用阻塞互斥锁,得不偿失。不管用什么锁,都需要系统开销,安全和性能需要权衡。

真实的互斥锁实现:先使用自旋锁,过了一段时间还没有拿到锁,此时再进行阻塞。 自旋+互斥启发式的锁。

总结:

  • CAS 实现自旋锁:使用一个atomic类型的全局变量flag表示临界区是否有线程在执行,

    ① 当flag==1时,说明临界区有一个线程在执行,此时while执行空转等待;
    ② 若flag==0时,说明临界区没有线程在执行,跳出while继续往下执行,并将flag设置为1,在退出临界区时解锁将flag置为0

  • CAS 自旋锁的饥饿问题:由于 CPU 调度不能保证先申请锁的一定先获得执行权,可能存在某个线程很久无法获得锁。

    解决方法:使用 FIFO 自旋锁,给每个线程编号,先叫到号的先执行

  • CAS 自旋锁的浪费 CPU 问题:由于自旋锁会导致每个线程都在不停的执行while空转操作,会占用 CPU,假如临界区的线程执行时间过长,那么其他等待的线程将会一直占用着 CPU,导致浪费。

    解决方法:基于自旋锁实现阻塞互斥锁,将没有拿到锁的线程加入等待队列中阻塞等待,让出 CPU 执行权,在临界区的线程执行完后,从等待队列中唤醒一个线程来执行。在实现时,需要一个标志位来标识临界区是否有 1 个线程在执行以及一个队列数据结构,另外需要一把自旋锁来保证锁内这两个全局变量的安全性。

  • 自旋锁 和 阻塞互斥锁该如何选择:根据临界区的执行时间长短来决定,时间短的使用自旋锁,时间长的使用阻塞互斥锁,因为阻塞时,需要发生系统调用 CPU 上下文切换需要开销,时间短的话再用阻塞得不偿失。

  • 真实的锁实现:先使用自旋锁,过了一段时间还没有拿到锁,此时再进行阻塞。

公平锁、非公平锁以及读写锁

公平锁 VS 非公平锁

  • 公平锁多个线程按照顺序去申请锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁

    优点:所有的线程都有机会得到资源,不会饿死在队列中
    缺点:吞吐量会下降很多,只有队首的线程会执行,其他的线程排队阻塞,CPU 唤醒阻塞线程的开销会很大。

  • 非公平锁多个线程去获取锁的时候,会直接去尝试获取,获取不到进入等待队列,如果能获取到就执行。

    优点:减少了唤起线程的数量,从而可以减少 CPU 唤醒线程的开销,CPU 不必取唤醒所有线程,整体的吞吐效率会高点。
    缺点:可能某个线程一直获取不到锁,导致饿死。

读写锁(read-write lock)

主要是针对读多写少的场景:

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

如果是读多写少的情况下,使用互斥锁就太浪费了。因为读的话,可以多线程同时读取资源,没有线程安全问题。

  • 线程的时候,获取【读锁】,这个时候写线程不能进临界区。
  • 线程的时候,获取【写锁】,这个时候读/写线程都不能进临界区。

读锁是共享的,写锁是独占互斥的,如果是读,可以多个读线程同时读,如果是写,只能一个线程写,其他的读写线程都不能访问。

各个语言都会有自己实现的读写锁:

pthread_rwlock_t rwlock; // 声明一个读写锁

pthread_rwlock_rdlock(&rwlock); // 在读之前加读锁
...共享资源的读操作
pthread_rwlock_unlock(&rwlock); // 读完释放锁

pthread_rwlock_wrlock(&rwlock); // 在写之前加写锁 
...共享资源的写操作
pthread_rwlock_unlock(&rwlock); // 写完释放锁

pthread_rwlock_destroy(&rwlock); // 销毁读写锁

细化锁的粒度

在这里插入图片描述

上面代码中使用一把锁来保护两个资源,一个线程在取钱,而另一个线程却不能登录。说明锁的粒度太大,可以并发的两个操作变成串行了。

其实这两个操作可以并发执行,细化锁的粒度,可以提高并发能力:

在这里插入图片描述

例如下面代码,两个账户需要转账的时候,要拿到两个账户的锁后,才可以开始转账,提高了并发能力

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

死锁

发生死锁的四个条件:

  • 互斥在一个时间点上,需要保证一个线程访问共享资源

  • 占有且等待线程 1 已经取得共享资源 A 的锁,在等待另一个共享资源 B 的锁的时候,不会释放共享资源 A 的锁

  • 不可抢占如果线程 1 已经取得共享资源 A 的锁,其他的线程不能强行抢占线程 1 已经取得的锁

  • 循环等待线程 1 等待线程 2 占用的资源的锁,线程 2 等待线程 1 占有的资源的锁,这就是循环等待了

例如下面代码展示了出现死锁的情况,其中:

  • 线程 1 拿到了 A 账户的转账锁,现在需要 B 账户的转账锁
  • 线程 2 已经拿到了 B 账户的转账锁,现在需要 A 账户的转账锁

在这里插入图片描述

破坏【占有且等待】

在这里插入图片描述

破坏【占有且等待】的方法:线程一次申请需要的所有资源锁,这样这可以避免发生死锁了

转账的例子,假如一个线程执行 A 账户转账 100 元给 B 账户,只有当这个线程拿到了 A 和 B 的锁,才开始进行转账操作,线程要么拿到 A 和 B 的锁,要么 A 和 B 的锁都没拿到,不会出现拿到一个锁,而等待另一个锁的情况,从而避免了死锁。

在这里插入图片描述

思考:在使用 ResourceManager 破坏【占用且等待】时,下面的代码 ① 和代码 ② 是否多余?

在这里插入图片描述

如果只有转账功能的话,那么代码 ① 和代码 ② 是多余的,可以省略的。

这是因为,当 srctarget 有一把锁申请不到,或者两把锁都申请不到的时候,线程会在 while 代码处自旋,不会进入临界区,这样就可以保证临界区只会有一个线程执行,只有这个线程拿到了两把锁。

但是,当不仅仅只有转账功能,可能还有取钱、存钱等业务的时候,这些业务也会去操作账户中的 balance 了,那么上面的代码 ① 和代码 ② 就很有必要了,因为转账的同时,可能还会有其他的线程对转账的账户执行取钱,或者存钱。

也就是存在多线程同时更新一个账户的 balance ,所以,转账的时候还是得把两把锁锁上,才能保证 balance 的线程安全。

破坏【不可抢占】

在这里插入图片描述

破坏【不可抢占】的方法:已经占有部分资源的线程在进一步申请其他资源时,如果申请不到的话,可以主动释放它所占有的资源,这样不可抢占这个条件就破坏掉了。

可以通过设计一个带超时的锁来实现,也就是当线程获取不到锁,如果超过了指定的时间后,就主动放弃。

在这里插入图片描述

破坏【循环等待】

在这里插入图片描述

破坏【循环等待】的方法:如果我们先对资源排序,然后按照顺序来申请资源的话,就可以破坏【循环等待】的条件了。

假设每个账户都有一个序号,来标志唯一标志这个账号,我们可以使用 seqNo 来表示

在这里插入图片描述

总结

  • 破坏占用且等待 —— 先一次性申请到所有需要的资源,再进行操作,线程要么都拿到了 A 和 B 的锁,要么都没有拿到,不会出现拿到一个 A 锁,而等待另一个 B 锁的情况
  • 破坏不可抢占 —— 设计带超时的锁,一直获取不到锁时,超时自动放弃当前占用资源
  • 破坏循环等待 —— 先对资源进行排序,然后按顺序申请资源,释放时,按顺序释放锁

信号量

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

信号量的主要作用:

  • 1:限流(控制并发度)
  • 2:相当于互斥锁(将信号量初始值设置为1)

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

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

信号量实现生产者消费者模型的参考代码:

int main() {
    sem_init(&empty, 0,2); // 有两个食物窗口 
    sem_init(&full, 0, 0); // 一开始食物窗口中没有任何菜,所以这里初始化为 0

    pthread_t chefs[3]; 
    pthread_t waiters[2]; 

    // 3个厨师
    for (int i = 0; i< 3; i++) {
        pthread_create(&chefs[i], NULL, chef_thread, &i); 
    }
    // 2个服务员
    for (int i = 0; i<2; i++) {
        pthread_create(&waiters[i], NULL, waiter_thread, &i);
    }
    
    sleep(30);
    
    sem_destroy(&empty); 
    sem_destroy(&full); 
}
void *chef_thread(void *args) { 
    int chef id = *((int *)args); 
    while (1) {
        // 模拟做菜
        sleep(rand() % 5);
        produce(chef_id); 
    }
}
void *waiter_thread(void *args) { 
    int waiter id = *((int *)args); 
    while (1) {
        sleep(rand( ) % 3); 
        consume(waiter_id); 
    }
}

sem_t empty;
sem_t full;

// 厨师这个线程通过这个函数,不断的往食物窗口放置菜
void produce(int chef_id) {
    // 这里会判断窗口是否为空,如果不为空则等待
    sem_wait(&empty);
    
    // 临界区
    printf("chef %d add food to food window\n", chef_id); 
    fflush(stdout);

    // 厨师将菜放入窗口,则通知服务员来取菜
    sem_post (&full);
}
// 服务员这个线程通过这个函数,不断的从食物窗口中取菜
void consume(int waiter_id) {
    // 这里服务员判断窗口是否满的,如果不满,也就是没有食物,则等待
    sem_wait(&full);

    // 临界区
    printf("waiter %d remove food from food winddow\n", waiter_id); 
    fflush(stdout);

    // 当服务员拿完菜,则通知厨师往窗口中放菜
    sem_post(&empty);
} 

生产者用一个信号量empty表示队列是否满的资源,消费者用一个信号量full表示队列是否空的资源。empty==0时,表示没有空的资源可用,则表示队列已满,生产者等待消费者消费,消费者消费后通知信号量empty, 若empty > 0时,表示队列有空位,生产者继续,生产者生产之后,通知信号量fullfull == 0时,表示队列为空,消费者等待生产者生产,full > 0时,表示队列有资源,消费者从队列中取出资源消费,然后通知信号量empty

在这里插入图片描述

苹果橙子问题参考代码:

#include <pthread.h> 
#include <semaphore.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <stdio.h> 

sem_t empty;
sem_t apple; 
sem_t orange;

void *father_thread(void *args) { 
    while (1) {
        // 如果盘子不是空的就等待
        sem_wait(&empty);
        sleep(rand() % 3);
        printf("爸爸放入一个苹果!\n")// 通知儿子可以吃苹果了
        sem_post(&apple); 
    }
}

void *mother_thread(void *args) { 
    while (1) {
        // 如果盘子不是空的就等待
        sem_wait(&empty) ;
        sleep(rand() % 3);
        printf("妈妈放入一个橙子!\n");
        // 通知女儿可以吃橙子了
        sem_post(&orange); 
    }
}

void *son_thread(void *args) {
    while (1) {
        // 如果没有苹果,则等待
        sem_wait(&apple); 
        sleep(rand() % 5);
        printf("儿子吃了一个苹果!\n");
        // 通知爸爸妈妈可以放水果了
        sem_post(&empty); 
    }
}

void *daughter_thread(void *args) { 
    while (1) {
        // 如果没有橙子,则等待
        sem_wait(&orange); 
        sleep(rand() % 5);
        printf("女儿吃了一个橙子!\n");
        // 通知爸爸妈妈可以放水果了
        sem_post(&empty) ; 
    }
}

int main() {
    pthread_t father; // 定义线程
    pthread_t mother;
    pthread_t son; 
    pthread_t daughter;

    sem_init(&empty, 0, 3); //信号量初始化 
    sem_init(&apple, 0, 0);
    sem_init(&orange, 0, 0);

    pthread_create(&father, NULL, father_thread, NULL); // 创建线程 
    pthread_create(&mother, NULL, mother_thread, NULL);
    pthread_create(&daughter, NULL, daughter_thread, NULL); 
    pthread_create(&son, NULL, son_thread, NULL);

    sleep(100); 
    return 1; 
}

信号量总结:

  • 信号量:表示临界区的可用资源数量,进入临界区可用资源数量 - 1,退出临界区可用资源数量 + 1,临界区可以同时有多个线程执行,只要能获取到资源。可用资源数量 ≤ 0 时,线程等待,直到可用资源数量≥ 0 时,才能进入。

  • 信号量的值设置为 > 0时,可以用于限流,控制并发数,信号量的值设置为1时,可以当成互斥锁来使用。

  • 信号量可以用于线程同步控制,如生产者消费者模型

管程 (monitor)

管程和信号量是等价的,一般信号量能实现的功能,也是可以使用管程来实现的。信号量一开始提出来的时候,就是应用于操作系统中多线程同步互斥的实现。管程一开始提出来的时候,是用在编程语言这个层面上的,比如 Java 语言、C++ 语言等,这些语言通过设计实现的管程机制,可以简化它们实现多线程同步互斥的操作。

管程是包含一系列的共享变量,以及针对这些变量的操作函数的一个组合,在具体的设计中,管程包含:

  • 一个锁,这个锁是用来确保互斥的,也就是确保只能有一个线程可以进入管程执行操作
  • ② 0 个或者多个条件变量,用于实现条件同步

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

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

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

相关文章

Qt使用I.MX6U开发板上的按键(原理:将电脑键盘方向键↓在Qt中的枚举值与开发板中按键定义的枚举值一致,这样电脑端测试效果就与开发板的一致)

在上篇介绍了Qt点亮I.MX6U开发板的一个LED&#xff0c;对于Qt控制I.MX6U开发板的一个蜂鸣器原理也是一样的&#xff0c;就不做详细介绍&#xff0c;具体可参考Qt控制I.MX6U开发板的一个蜂鸣器&#xff0c;本篇介绍Qt使用I.MX6U开发板上的按键的相关内容。 文章目录 1. 开发板硬…

第一个 Go 程序“hello,world“ 与 main 函数

第一个 Go 程序"hello&#xff0c;world" 与 main 函数 文章目录 第一个 Go 程序"hello&#xff0c;world" 与 main 函数一.创建“hello&#xff0c;world”示例程序二. “hello&#xff0c;world” 程序结构拆解三、main 函数四、Go 语言中程序是怎么编译…

selenium+python实现基本自动化测试

安装selenium 打开命令控制符输入&#xff1a;pip install -U selenium 火狐浏览器安装firebug&#xff1a;www.firebug.com&#xff0c;调试所有网站语言&#xff0c;调试功能 Selenium IDE 是嵌入到Firefox 浏览器中的一个插件&#xff0c;实现简单的浏览器操 作的录制与回…

坚鹏:浙江农商联合银行同业核心产品解读与差异化分析培训第7期

浙江农商联合银行同业核心产品解读与差异化分析培训第7期 1952年&#xff0c;浙江第一家农村信用社成立。2004年4月18日&#xff0c;浙江省农信联社成立&#xff0c;承担对全省农信社的管理、指导、协调和服务职能。2021年10月&#xff0c;经国务院批准同意、银保监会批复&…

多目标优化算法:基于非支配排序的鱼鹰优化算法(NSOOA)MATLAB

一、鱼鹰优化算法 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕食行为。 Python&#xff1a;鱼鹰优化算法&#xff08;Osprey optimization algorithm&a…

自动驾驶中的决策规划

参考: 【干货篇】轻舟智航&#xff1a;自动驾驶中的决策规划技术&#xff08;附视频回放 PPT 下载&#xff09; - AIQ 如图所示, 各模块介绍 定位模块主要负责解答的问题是“车现在在哪里”&#xff0c;是在道路上还是在路口&#xff0c;是在高架桥上还是在停车场里。 感知…

图像锐化,求图像锐化后的图像(数字图像处理大题复习 P6)

文章目录 1. 梯度差分方法 & 罗伯特差分法梯度差分方法罗伯特差分法使用梯度差分法解决本题 2. 有阈值 (T4) 的二值图像输出 用不同图像输出方法求图像锐化后的图像 g(x, y) 梯度图像直接输出设阈值 T4&#xff0c;求二值图像输出 1. 梯度差分方法 & 罗伯特差分法 梯度…

C++ Qt零基础入门进阶与企业级项目实战教程与学习方法分享

Qt是一个卓越的客户端跨平台开发框架&#xff0c;可以在Windows、Linux、macOS进行客户端开发&#xff0c;无缝切换&#xff0c;一统三端&#xff1b;当然除了桌面端&#xff0c;在移动端的早期&#xff0c;Qt也展现了其多才多艺&#xff0c;在Android和ios也可以使用Qt编写app…

外部打开微信小程序支付,H5 、APP

手机浏览器H5打开微信小程序支付&#xff0c;自定义传参_h5调起微信小程序支付_我是小木木的博客-CSDN博客H5网站打开小程序&#xff0c;调用小程序支付功能_h5调起微信小程序支付https://blog.csdn.net/chen_mumu119/article/details/132104048

**20.迭代器模式(Iterator)

意图&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不需要暴露该对象的内部表示。 上下文&#xff1a;集合对象内部结构常常变化各异。对于这些集合对象&#xff0c;能否在不暴露其内部结构的同时&#xff0c;让外部Client透明地访问其中包含的元素…

大数据+大模型的尽头——数据分析师的未来会怎样?

大数据大模型的尽头一定是干掉数据分析师吗&#xff1f; | 近匠

12:STM32---RTC实时时钟

目录 一:时间相关 1:Unix时间戳 2: UTC/GMT 3:时间戳转化 二:BKP 1:简历 2:基本结构 三: RTC 1:简历 2: 框图 3:RTC基本结构 4:RTC操作注意 四:案例 A:读写备份寄存器 1:连接图 2: 步骤 3: 代码 B:实时时钟 1:连接图 2:函数介绍 3:代码 一:时间相关 1:Un…

如何与Linamar Corp 建立EDI连接?

Linamar Corp&#xff08;以下简称Linamar&#xff09;是一家全球领先的汽车零部件制造商&#xff0c;总部位于加拿大。随着业务的不断扩展&#xff0c;Linamar 需要与其供应商、分销商和合作伙伴之间实现更高效的业务交流和数据共享。为了提高业务流程的自动化水平&#xff0c…

1.虚拟机无法连接网络,且无法ping通的问题解决

1.介绍 今天操作Jedis连接虚拟机的redis数据库时&#xff0c;连接不上&#xff0c;找了很多解决方案&#xff0c;都解决不了&#xff0c;最后发现是虚拟机的配置问题&#xff0c;虚拟机无法连接网络&#xff0c;且没有设置本机ip地址&#xff0c;所以ifconfig的根本就查不出ip…

C++之智能指针类型转换应用总结(二百二十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

数据结构:线性表之-队列

目录 什么是队列&#xff1f; 详解&#xff1a; 功能介绍 代码实现 定义队列基本结构 1&#xff0c;初始化 2, 销毁 3,尾入数据 4,头出数据 5,取队头的数据 6,取队尾的数据 7,判断是否为空 8,计算队列中的元素 成品 Queue.h Queue.c test.c 队列的讲解将建立在…

Oracle使用遇到的问题

一、Navicat 连接Orcle提示 Oracle library is not loaded 1.前往官网下载客户端 http://www.oracle.com/technetwork/database/database-technologies/instant-client/downloads/index.html 2.选择与系统匹配的“Instant Client”和sqlpus。

App的专项测试

文章目录 App的专项测试都从哪个方面测试安装和卸载版本升级消息推送弱网测试兼容性测试交互性测试 App的专项测试都从哪个方面测试 在APP测试过程中&#xff0c;除了功能测试外&#xff0c;还需要进行一些专项测试来发现更为深层的问题&#xff0c;这些问题主要是针对某个特殊…

时空智友企业流程化管控系统文件存在任意文件上传漏洞 附POC

、 文章目录 时空智友企业流程化管控系统文件存在任意文件上传漏洞 附POC1. 时空智友企业流程化管控系统文件简介2.漏洞描述3.影响版本4.fofa查询语句5.漏洞复现6.POC&EXP7.整改意见8.往期回顾 时空智友企业流程化管控系统文件存在任意文件上传漏洞 附POC 免责声明&#…

【Redis】深入探索 Redis 的哨兵(Sentinel)机制原理,基于 Docker 模拟搭建 Redis 主从结构和哨兵分布式架构

文章目录 一、对 Redis Sentinel 的认识1.1 什么是 Redis Sentinel1.2 为什么要使用 Redis Sentinel1.2.1 主从复制问题1.2.2 人工恢复主节点故障 二、Redis Sentinel 原理剖析2.1 Redis Sentinel 架构2.2 Raft 算法和领袖节点2.3 哨兵节点2.4 故障检测2.5 故障切换2.6 监控和通…