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

用户态抢占和内核态抢占

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

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

  • ① 当前运行的进程陷阱程序(系统调用)故障程序(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)); // spinif (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)); // spinif (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); // 一开始食物窗口中没有任何菜,所以这里初始化为 0pthread_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.xdnf.cn/news/141845.html

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

相关文章

Spring面试题11:什么是Spring的依赖注入

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的依赖注入 依赖注入(Dependency Injection)是Spring框架的一个核心特性,它是指通过外部容器将对象的依赖关系注入到对象中,从而…

论文笔记:ST2Vec: Spatio-Temporal Trajectory SimilarityLearning in Road Networks

2022 KDD 1 intro 现有的轨迹相似性学习方案强调空间相似性而忽视了时空轨迹的时间维度&#xff0c;这使得它们在有时间感知的场景中效率低下 如上图&#xff0c;在拼车过程中&#xff0c;T1表示司机计划的行程&#xff0c;T2和T3是两个想要搭车的人。T1和T2在空间上更接近&am…

怒刷LeetCode的第15天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;哈希表双向链表 方法二&#xff1a;TreeMap 方法三&#xff1a;双哈希表 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;二分查找 方法二&#xff1a;线性搜索 方法三&#xff1a;Arrays类的b…

【MySql】2- 基础篇(下)

文章目录 1. MySQL锁1. 1 全局锁1. 2 表级锁1. 3 行锁1. 3 .1 两阶段锁1. 3 .2 死锁和死锁检测 2. 事务是否是隔离的?2.1 快照在MVCC中如何工作 1. MySQL锁 数据库锁设计的初衷是处理并发问题。作为多用户共享的资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合…

入门级制作电子期刊的网站推荐

随着数字化时代的到来&#xff0c;越来越多的人开始尝试制作自己的电子期刊。如果你也是其中的一员&#xff0c;那么这篇文章可以帮助你制作电子期刊。无论是初学者还是有一定经验的制作者&#xff0c;都能快速完成高质量的电子期刊制作 小编经常使用的工具是-----FLBOOK在线制…

[RF学习记录][ssh library][execute Command】关键字的返回值

有时候需要判断通过ssh在远程机器上执行的命令是否正常&#xff0c;使用关键字Execute Command可以在远程机器上运行命令&#xff0c;但是默认不加任何参数的话&#xff0c;没有看到范返回值&#xff0c;而这个关键字是带了几个参数的&#xff0c;简单的试验了下这几个参数&…

SAP Oracle表空间扩展技术手册

1、DBACOCKPIT下查看表空间 当表空间不足(达到99%)时,需要按以下步骤扩充表空间(每次扩充20000M,20G): (也可以通过DB13,DB02查看表空间) 新浪博客 Tablespace PSAPSR3 is 100% used | SAP Community Oracle是通过增加数据文件的方式来为表空间扩容。为指定表空间增…

Mock.js之Element-ui搭建首页导航与左侧菜单

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《springMvc使用》 ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 1、Mock.js的使用 1.1.什么是Mock.js Mock.js是一个模拟数据的生成器&#xff0c;用来帮助前…

【LeetCode热题100】--189.轮转数组

189.轮转数组 数组翻转&#xff1a; 当我们将数组的元素向右移动k次后&#xff0c;尾部k mod n个元素会移动至数组 头部&#xff0c;其余元素向后移动k mod n个位置 该方法为数组的翻转&#xff1a;我们可以先将所有元素翻转&#xff0c;这样尾部k mod n个元素就被移至数组头…

Jenkins Job的Migrate之旅

场景 使用Jenkins 做为应用的定时任务处理&#xff0c; 在上面建立的800个左右的Job, 这个环境运行了很多年&#xff0c; 当初安装的最新版本是Jenkins 1.642.3&#xff0c; 现在因为OS需要升级等原因&#xff0c; 驻在上面的Jenkins 服务器也需要一并升级&#xff0c;在新的服…

[LLM+AIGC] 01.应用篇之中文ChatGPT初探及利用ChatGPT润色论文对比浅析(文心一言 | 讯飞星火)

近年来&#xff0c;人工智能技术火热发展&#xff0c;尤其是OpenAI在2022年11月30日发布ChatGPT聊天机器人程序&#xff0c;其使用了Transformer神经网络架构&#xff08;GPT-3.5&#xff09;&#xff0c;能够基于在预训练阶段所见的模式、统计规律和知识来生成回答&#xff0c…

Requests项目如何使用pytest进行单元测试

前言 Pytest是python的单元测试框架&#xff0c;简单易用&#xff0c;在很多知名项目中应用。requests是python知名的http爬虫库&#xff0c;同样简单易用&#xff0c;是python开源项目的TOP10。关于这2个项目&#xff0c;之前都有过介绍&#xff0c;本文主要介绍requests项目…

LLM-TAP随笔——有监督微调【深度学习】【PyTorch】【LLM】

文章目录 5、 有监督微调5.1、提示学习&语境学习5.2、高效微调5.3、模型上下文窗口扩展5.4、指令数据构建5.5、开源指令数据集 5、 有监督微调 5.1、提示学习&语境学习 提示学习 完成预测的三个阶段&#xff1a;提示添加、答案搜索、答案映射 提示添加 “[X] 我感到…

苹果Vision Pro头显内置AI芯片

苹果首席执行官蒂姆库克近日在接受采访时确认&#xff0c;备受瞩目的Vision Pro头显将按计划于明年初在美国上市。这款头显被认为是苹果自iPhone以来最重要的产品之一&#xff0c;售价高达3499美元。 蒂姆库克在接受CBS Sunday Morning的采访时透露&#xff0c;他的团队对Visi…

二叉树进阶

目录 1. 二叉搜索树的概念及结构 1.1. 二叉搜索树的概念 1.2. 二叉搜索树的结构样例&#xff1a; 2. 二叉搜索树实现 2.1. insert的非递归实现 2.2. find的非递归实现 2.3. erase的非递归实现 2.3.1. 第一种情况&#xff1a;所删除的节点的左孩子为空 2.3.1.1. 错误的代…

PTE深度了解(一)

目录 PTE模板开始大审查吗&#xff1f;我的模板还能用吗&#xff1f; 使用模版&#xff0c;不会额外扣你分 类型一&#xff08;前20秒说模版&#xff09; 类型二&#xff08;老实巴交&#xff09; 类型三&#xff08;就是都说简单句&#xff09; 1.查重复 2.增加内容分识…

设备自动化系统EAP在晶圆厂的关键作用

在现代晶圆厂中&#xff0c;设备自动化系统&#xff08;Equipment Automation Programming&#xff0c;EAP&#xff09;已经成为了不可或缺的关键要素。EAP系统不仅是生产自动化的重要组成部分&#xff0c;更是连接制造执行系统&#xff08;MES&#xff09;与设备之间的桥梁&am…

XUI - 一个简洁而优雅的Android原生UI框架

官网 GitHub - xuexiangjys/XUI: &#x1f48d;A simple and elegant Android native UI framework, free your hands! (一个简洁而优雅的Android原生UI框架&#xff0c;解放你的双手&#xff01;) XUI | &#x1f48d;A simple and elegant Android native UI framework, fre…

OpenCV项目开发实战--主成分分析(PCA)的特征脸应用(附C++/Python实现源码)

什么是主成分分析? 这是理解这篇文章的先决条件。 图 1:使用蓝线和绿线显示 2D 数据的主要组成部分(红点)。 快速回顾一下,我们了解到第一个主成分是数据中最大方差的方向。第二主成分是空间中与第一主成分垂直(正交)的最大方差方向,依此类推。第一和第二主成分红点(2…

robotframework在Jenkins执行踩坑

1. Groovy Template file [robot_results.groovy] was not found in $JENKINS_HOME/email_template 1.需要在managed files 添加robot_results.groovy。这个名字需要和配置在构建项目里default content一致&#xff08;Extended E-mail Notification默认设置里Default Content…