【Linux】线程安全

news2024/11/26 20:44:41

文章目录

      • 1.线程互斥
        • 1.1.线程间互斥的相关概念
        • 1.2互斥量
        • 1.3互斥量接口
        • 1.4互斥量实现原理
      • 2.可重入VS线程安全
      • 3.常见锁概念
        • 3.1死锁
        • 3.2常见死锁情况
          • 3.2.1情况一:忘记释放锁
          • 3.2.2情况二:线程重复申请锁
          • 3.2.3情况三:双线程多锁申请
        • 3.3锁的相关概念
      • 4.Linux线程同步
        • 4.1同步概念与竞态条件
        • 4.2条件变量
        • 4.3条件变量接口
        • 4.4为什么pthread_cond_wait需要互斥量

1.线程互斥

1.1.线程间互斥的相关概念

  • 临界资源:多线程执行流共享的资源叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码段,叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:一个操作要么执行完,要么不执行,不会被任何调度机制打断的操作

多线程的大部分资源都是共享的,线程之间进行通信不需要费那么大的劲去创建第三方资源。但是如果不对资源进行保护,那么就可能出现意料之外的逻辑错误。

比如下面的抢票程序

#define NUM 5
int tickets=200;    //总票数

void* routine(void* arg){
    while(1){
        if(tickets>0){
            usleep(30000);   //模拟抢票的过程
            printf("线程:%d抢票成功,票的序号是%d\n",pthread_self(),tickets);
            tickets--;
        }
        else{
            break;
        }
    }
    return nullptr;
}
int main(){
    pthread_t tid[NUM];
    for(int i=0;i<NUM;i++){
        pthread_create(&tid[i],nullptr,routine,nullptr);
    }
    for(int i=0;i<NUM;i++){
        pthread_join(tid[i],nullptr);
    }
}

逻辑上似乎没用错误,但是代码的结果却出现了票数为负数的情况。

image-20221121234524285

为什么会出现票数为负数的情况?

tickets本身是一个全局变量,是被所有线程所共享的,也就是一个临界资源。在代码的运行过程中,出现了以下的情况:

  • if语句判断条件为真以后,代码可能被切换到其他线程。
  • usleep模拟抢票的过程,在这个过程中,可能其他线程也进入该代码段。
  • tickets–不是一个原子操作

进行 - - 操作的时候,不是原子的(安全的)。它对应了三条汇编指令:

  • load:将共享变量ticket从内存加载到寄存器中
  • update: 更新寄存器里面的值,执行-1操作
  • store:将新值,从寄存器写回共享变量ticket的内存地址

image-20221122003509439

既然–操作需要三个步骤才能完成,那么thread1可能在任何一个步骤被切走。假设此时thread1读取到的值为1000,而当thread1被切走时,寄存器中的1000被保存到了thread1的上下文数据中。

image-20221122004828936

假设此时thread2被调度了,由于thread1只进行了--操作的第一步,因此thread2此时看到tickets的值还是1000,而系统给thread2的时间片可能较多,导致thread2一次性执行了100次--才被切走,最终tickets由1000减到了900。

image-20221122005334497

此时系统再把thread1恢复上来,恢复的本质就是继续执行thread1的代码,并且要将thread1曾经的硬件上下文信息恢复出来,此时寄存器当中的值是恢复出来的1000,然后thread1继续执行--操作的第二步和第三步,最终将999写回内存。

image-20221122005525838

最后的结果是”凭空多出了100张票"

所以我们可以总结:从内核态返回用户态的时候,OS中线程间进行切换,极有可能出现数据交叉操作,而导致数据不一致的问题。

1.2互斥量

要解决上面的问题,就需要保证访问临界资源的过程是原子操作。多个线程访问临界区时需要做到下面三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

image-20221122010337722

1.3互斥量接口

初始化互斥量

函数原型:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数说明:

  • mutex:需要初始化的锁
  • attr:初始化互斥量的属性,一般设置为NULL即可。

返回值说明:成功返回0,失败返回错误码

调用pthread_mutex_init函数初始化互斥量叫做动态分配,除此之外,我们还可以用下面这种方式初始化互斥量,该方式叫做静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

销毁互斥量

函数原型:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要销毁的互斥量

返回值:销毁成功返回0,失败返回错误码

销毁互斥量需要注意的是:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

互斥量加锁与解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号

调用 pthread_ lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

**改进上面的抢票系统:**为临界区加上锁

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>

#define NUM 5
int tickets=200;    //总票数
pthread_mutex_t mutex;
void* routine(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        if(tickets>0){
            usleep(30000);   //模拟抢票的过程
            printf("线程:%d抢票成功,票的序号是%d\n",pthread_self(),tickets);
            tickets--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    return nullptr;
}
int main(){
    pthread_t tid[NUM];
    for(int i=0;i<NUM;i++){
        pthread_create(&tid[i],nullptr,routine,nullptr);
    }
    for(int i=0;i<NUM;i++){
        pthread_join(tid[i],nullptr);
    }
}

image-20221122012201658

1.4互斥量实现原理

首先的一个问题:在加锁的临界区,线程可以被切换吗?

需要理解的是:加锁!=不能被切换;

在加锁的临界区,线程可以被切换。加锁解锁等操作对应的也是代码。线程在任意代码段都可能被切换。

但是线程加锁是原子的,即使线程被切走后,锁也保证了绝对不会有其他线程进入临界区。想要访问临界区,线程就必须要抱锁。一旦一个线程抱锁,该线程就不会担心该锁对应的资源因线程切换而出问题。

**总结:**加锁的线程可以被切换,但是其他线程无法进入临界区。

锁是否需要被保护?

被多个执行流共享的资源叫做临界资源,访问临界资源的代码叫做临界区。锁需要被多个线程访问,因此锁也是临界资源。

锁是临界资源,那么锁也需要被保护,但锁本身就是用来保护临界资源的,那锁又由谁来保护的呢?

锁实际是自己保护自己,有关锁的操作都是原子的,那么锁就是安全的。

加锁和解锁具有原子性,如何实现?

  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换。
  • 由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,
    一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

下面是lock和unlock的伪代码

image-20221122014029387

lock的执行过程

mutex的初始值为1,al是计算机中的一个寄存器,当线程申请锁时,需要执行以下步骤:

  • 先将al寄存器中的数据设置为0
  • 交换内存mutex和al中的数据,xchgb是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换。
  • 最后判断寄存器al的值是否大于0,如果大于0表示申请锁成功;否则就挂起等待。

如果当前thread1申请锁。xchb交换内存mutex和al寄存器的数据后,寄存器的数据为1,mutex的数据为0。此时thread1申请锁成功。

image-20221122015235419

如果thread2申请锁。由于当前内存mutex的值是0,和al寄存器交换后,两者的数据都是0。因此thread2申请锁失败,线程挂起。

image-20221122015629681

当在申请锁的过程中,线程被切换,如何保证锁的原子性?

首先需要有以下的认识:

  • 凡是在寄存器中的数据,都是线程的上下文数据。
  • 当某个线程需要使用寄存器时,必须等待正式使用寄存器的线程被CPU剥离,并保存寄存器中的上下文数据到线程栈中。

比如:如果thread1刚刚交换完内存mutex和寄存器al的数据,但是还没有来得及判断是否申请锁成功就被切走。

image-20221122020458906

此时thread2再去申请锁失败。

image-20221122015629681

当thread1被切回来时,上下文数据恢复到寄存器中,此时al寄存器的数据为1。thread1申请锁成功。

image-20221122020827948

解锁的过程和加锁的过程相同,不同的是初始向al寄存器填充的数据是1

2.可重入VS线程安全

基本概念

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题 。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数

常见线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没用写入的权限
  • 类或者接口对于线程来说都是原子操作
  • 多线程之间的切换不会导致该接口的执行结果存在二义性

常见的不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

常见的可重入的情况

  • 不使用全局变量或静态变量。
  • 不使用malloc或者new开辟出的空间。
  • 不调用不可重入函数。
  • 不返回静态或全局数据,所有数据都由函数的调用者提供。
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

可重入与线程安全联系

  • 函数是可重入的,那么就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

  • 可重入函数是线程安全函数的一种。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

3.常见锁概念

3.1死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态 。

常见死锁情况

3.2常见死锁情况

	定义:死锁是值两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象。		若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,而这些永远在互相等待的进程称为死锁进程。

例如,如果线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

3.2.1情况一:忘记释放锁

函数调用先于释放锁,锁还没来得及释放进程就退出。

std::mutex m;
void func()
{
    //进程报锁
    m.lock();
    if(1)
    {
        return ;
    }
    m.unlock();
}
3.2.2情况二:线程重复申请锁
mutex _mutex;
void func()
{
	_mutex.lock();
	//do somrthing....
    //重复申请锁,第二次申请处于阻塞等待状态
    _mutex.lock();
    
	_mutex.unlock();
}
3.2.3情况三:双线程多锁申请

下面的例子中,process1和process2先对_mutex1和_mutex2上锁。

然后由于 _ mutex2已经上锁,process1会一直阻塞等待 _ mutex2;同样,由于 _ mutex1上锁,process2会一直阻塞等待 _mutex1

mutex _mutex1;
mutex _mutex2;
 
void process1() {
	_mutex1.lock();
	_mutex2.lock();
	//do something1...
	_mutex2.unlock();
	_mutex1.unlock();
}
 
void process2() {
	_mutex2.lock();
	_mutex1.lock();
	//do something2...
	_mutex1.unlock();
	_mutex2.unlock();
}

3.3锁的相关概念

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用。
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

注意:四个条件都要满足才会出现死锁的情况

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致(比如上面的情况三)
  • 避免锁未释放的情况
  • 资源一次性分配,减少锁的使用。

避免死锁的算法

  • 死锁检测算法
  • 银行家算法

4.Linux线程同步

4.1同步概念与竞态条件

同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步。
竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。

  • 首先需要明确的是,单纯的加锁是会存在某些问题的,如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题。
  • 为了让每个线程都有机会申请到锁,现在我们增加一个规则,当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。
  • 增加这个规则之后,下一个获取到锁的资源的线程就一定是在资源等待队列首部的线程

线程互斥,在一定的场景下是不合理的。可能出现饥饿现象:一个执行流长时间得不到某种资源。

例如:在互斥量的时候,我们解决了共享变量的问题,但是我们在创建锁的过程中,可能会存在一个优先级高的线程,每次都是它优先申请到锁。该线程一直在申请锁、检测(进行抢票)、释放锁,导致其他线程没有机会得到这把锁(俗称饥饿问题)。这样错了嘛?显然没有,但是这样安排是不合理的

排队的本质:让线程在获取锁安全的前提下,按照某种顺序进行申请和释放锁,让每个线程都有机会申请到锁,这就叫做同步。

4.2条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述。

条件变量主要有两个动作

  • 一个线程等待条件变量的就绪而被挂起
  • 另一个线程使条件成立后唤醒等待的线程。

4.3条件变量接口

初始化条件变量

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

参数说明:

  • cond:要初始化的条件变量
  • attr:NULL

返回值:条件变量初始化成功返回0,失败返回错误码。

调用pthread_cond_init函数初始化条件变量叫做动态分配,除此之外,我们还可以用下面这种方式初始化条件变量,该方式叫做静态分配:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond)

注意:使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁。

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

参数:

  • cond:要在这个条件变量上等待
  • mutex:需要释放的锁

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

两个接口的区别:

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程

实例:使用pthread_cond_signal依次唤醒线程

#include <functional>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;
void* waitcommand(void* arg){
    char* s=(char*)arg;
    while(1){
        pthread_cond_wait(&cond,&mutex);
        int cnt=3;
        while (cnt--)
        {
            printf("%s is running...\n",s);
            sleep(1);
        }
    }
    return nullptr;
}
int main(){
    pthread_cond_init(&cond,nullptr);
    pthread_mutex_init(&mutex,nullptr);
    pthread_t tid[3];
    for(int i=0;i<3;i++)
    {
        char* s=(char*)malloc(32);
        sprintf(s,"thread %d",i);
        pthread_create(&tid[i],nullptr,waitcommand,(void*)s);
    }
    //循环的唤醒执行线程
    while(1){
        pthread_cond_signal(&cond);
        sleep(3);
    }
    for(int i=0;i<3;i++)
    {
        pthread_join(tid[i],nullptr);
    }
    return 0;
}

image-20221122151119664

使用pthread_cond_broadcast唤醒所有线程

#include <functional>
#include <unistd.h>
#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;
void* waitcommand(void* arg){
    char* s=(char*)arg;
    while(1){
        pthread_cond_wait(&cond,&mutex);
        int cnt=3;
        while (cnt--)
        {
            printf("%s is running...\n",s);
            sleep(1);
        }
    }
    return nullptr;
}
int main(){
    pthread_cond_init(&cond,nullptr);
    pthread_mutex_init(&mutex,nullptr);
    pthread_t tid[3];
    for(int i=0;i<3;i++)
    {
        char* s=(char*)malloc(32);
        sprintf(s,"thread %d",i);
        pthread_create(&tid[i],nullptr,waitcommand,(void*)s);
    }
    //循环的唤醒执行线程
    while(1){
        pthread_cond_broadcast(&cond);
        sleep(3);
    }
    for(int i=0;i<3;i++)
    {
        pthread_join(tid[i],nullptr);
    }
    return 0;
}

image-20221122151312080

4.4为什么pthread_cond_wait需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
  • 当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题。
  • 所以,调用pthread_cond_wait需要传递互斥量。当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁。
  • 当该线程被唤醒时,该线程会接着执行临界区内的代码,pthread_cond_wait在接收到条件变量时,被唤醒时,会自动的获得锁。

image-20221122151844850

而实际进入pthread_cond_wait函数后,会先判断条件变量是否等于0,若等于0则说明不满足,此时会先将对应的互斥锁解锁,直到pthread_cond_wait函数返回时再将条件变量改为1,并将对应的互斥锁加锁。

条件变量的使用规范

等待条件

pthread_mutex_lock(&mutex);
while (条件为假)
	pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

m在VBLAST协作MIMO系统分部使用LDPC,Turbo,卷积三种信道编译码进行误码率matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法描述 从上面的结构可知&#xff0c;整个卷积编码的结构可由CRC校验&#xff0c;卷积编码&#xff0c;打孔组成&#xff0c;其中打孔的作用就是讲卷积编码后的码率变为所需要的码率进行发送。 …

一种在行末隐藏有效载荷的新供应链攻击技术研判

近期&#xff0c;Phylum检测到数十个新发布的Pypi软件包执行供应链攻击&#xff0c;在这些软件包中&#xff0c;通过隐藏的__import__将窃取程序投递到开发人员的机器上。攻击者利用代码审核者所使用IDE默认的不换行代码显示设置隐藏自身的行为与载荷&#xff0c;本文将就其中出…

栈简介、手写顺序栈、手写链栈和栈的应用

一. 简介 1. 什么是栈&#xff1f; 栈是一种只能从表的一端存取数据且遵循 "先进后出"&#xff08;"后进先出"&#xff09; 原则的线性存储结构。栈也是用来存储逻辑关系为 "一对一" 数据的线性存储结构。 C#中提供顺序栈&#xff1a;Stack&…

【MySQL基础】如何安装MySQL?如何将MySQL设置成服务?

目录 一、MySQL的安装 1、解压配置 2、步骤安装 &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、MySQL的安装 MySQL的安装有两种方式&#xff1a;解压配置和步骤安装 1、解压配置 需提前从官网直接下载压缩包&#xff0c;进…

【MySQL篇】第二篇——库的操作

目录 创建数据库 创建数据库案例 字符集和校验规则 查看系统默认字符集以及校验规则 查看数据库支持的字符集 查看数据库支持的字符集校验规则 校验规则对数据库的影响 操纵数据库 查看数据库 显示创建语句 修改数据库 数据库删除 备份和恢复 备份 还原 注意事…

常见磁盘调度算法总结

磁盘调度算法&#x1f4d6;1. 最短寻道时间优先&#xff08;SSTF&#xff09;&#x1f4d6;2. 电梯算法&#xff08;SCAN或C-SCAN&#xff09;&#x1f4d6;3. 最短定位时间优先&#xff08;SPTF&#xff09;&#x1f4d6;4. 总结由于IO的高成本&#xff0c;操作系统在决定发送…

C语言 0 —— 计算机硬件架构及信息在计算机中的表示

当前的计算机系统&#xff0c;如Window &#xff0c;Linux&#xff0c;Mac 基本都是基于冯诺依曼的驱动架构设计的。 冯诺依曼架构输入设备先输入公式&#xff0c;给运算器&#xff0c;运算器先算 先算2*5 &#xff0c;临时放在CPU内部寄存器中&#xff0c;寄存器不够用的时候会…

vscode插件开发(四)Webview(1)

上一篇详细讲解了命令&#xff0c;这回我们一起来看一下Webview。vscode的插件其实可以分为两种&#xff0c;一种是webview插件&#xff0c;另一种是非webview插件。 webview插件的自由度很高&#xff0c;可以满足开发者的各种定制化的要求&#xff1b;而非webview插件只能使用…

我悟了!Mysql事务隔离级别其实是这样!

问题描述 ​ 最近几天在忙项目&#xff0c;有个项目是将业务收集到的数据变动&#xff0c;异步同步到一张数据表中。在测试的过程时&#xff0c;收到QA的反馈&#xff0c;说有订单的数据同步时好时坏。我怀着疑惑的表情打开了那段代码&#xff0c;它的逻辑大概是这样的&#x…

Zookeeper实现分布式锁的原理。

之前学习Redis时候&#xff0c;我们利用Redis实现了分布式锁。 黑马点评项目Redis实现分布式锁_兜兜转转m的博客-CSDN博客 为什么提出了分布式锁的概念呢&#xff1f; 因为在单体项目中&#xff0c;锁是基于JVM虚拟机实现的&#xff0c;在分布式情况下&#xff0c;JVM就不唯…

FullGC频繁,线程数持续增长排查

告警 线上应用fullgc频繁&#xff0c;收到告警 GC监控—堆内存不足 查看近12小时的监控&#xff0c;发现Survivor区一直处于 满状态、fullgc非常频繁、但没有内存溢出的现象&#xff0c;很明显是堆内存不足 GC日志分析—暂停时间并不长 因为fullgc相当频繁&#xff0c;抽…

项目管理(知识体系概述)

项目的定义:为创造独特的产品、服务或者成果进行的临时性工作。 项目的特性:1、独特的产品、服务、成果;2、临时性工作。 项目管理的目的(为了解决什么问题): 1、达成业务目标 2、满足相关方期望 3、提供项目的可预测性 4、提高项目的成果性。 5、在适当的时刻交付…

机器人运动学标定:基于考虑约束的指数积的运动学标定方法——只需要测量位置,避免冗余约束

文章目录写在前面为什么要消除归一化和正交化操作&#xff1f;只用位置而不是位姿去做标定的原因基于消除冗余约束步骤的参数辨识模型分析参考文献写在前面 基于指数积的运动学标定方法介绍&#xff1a; 机器人运动学标定&#xff1a;基于指数积的串联机构运动学标定 机器人运…

Vue表单修饰符:v-model.lazy、v-model.number、v-model.trim

表单修饰符有&#xff1a;lazy、number、trim&#xff1b;修饰符加在v-model后面&#xff1b; lazy修饰符&#xff1a; v-model的作用是双向绑定表单&#xff0c;能获取到input输入框的值&#xff0c;而且是实时获取的&#xff0c;就是当你输入框里的值发生改变就会获取到&…

【Shell 脚本速成】02、Shell 变量详解

目录 一、变量介绍 变量存取原理 二、变量定义 2.1 什么时候需要定义变量&#xff1f; 2.2 定义一个变量 定义变量举例&#xff1a; 定义变量演示&#xff1a; 2.3 取消变量 unset 2.4 有类型变量 declare declare 命令参数&#xff1a; 案例演示&#xff1a; 三…

向前迈进!走入GC世界:G1 GC原理深入解析

第零章&#xff1a;名词解释 mutator&#xff1a;应用线程 STW&#xff1a;Stop-The-World&#xff0c;指除了GC线程&#xff0c;其它所有线程全部暂停的一段时间 并发&#xff1a;指代GC线程与mutator在同一时刻执行任务 并行&#xff1a;指代多个GC线程在同一时刻执行任务…

一站式元数据治理平台——Datahub

一站式元数据治理平台——Datahub万字保姆级长文——Linkedin元数据管理平台Datahub离线安装指南 - 独孤风 - 博客园 (cnblogs.com)企业级数据治理工作怎么开展&#xff1f;Datahub这样做 - 独孤风 - 博客园 (cnblogs.com)【DataHub】 现代数据栈的元数据平台–如何与spark集成…

如何设计金融机构多场景关键应用下的存储架构

【摘要】银行、保险等金融机构存在多场景下的关键应用,如何选择适合各场景下的存储,如何设计适合业务的存储架构,显得尤为重要。本文从当前主流存储架构分析入手,提出金融机构业务场景分析与架构选型思路,以Glusterfs为例,分享如何根据业务场景的特点,有针对性的选取适合…

SQL优化

文章目录提升group by的效率分页查询优化覆盖索引子查询起始位置重定义检查 where,order by,group by后面的列尽量使用 varchar 代替 char。&#xff08;SQL 性能优化&#xff09;如果修改 / 更新数据过多&#xff0c;考虑批量进行提升group by的效率 select user_id,user_nam…

spring-security源码学习总结

由于SpringBoot 对 Security 的支持类均位于org.springframework.boot.autoconfigure.security包下&#xff0c;主要通过 SecurityAutoConfiguration 自动配置类和 SecurityProperties 属性配置来完成&#xff0c;所以需要下载springboot源码深入学习 SecurityAutoConfiguratio…