学习记录——day25 多线程编程 临界资源 临界区 竞态 线程的同步互斥机制(用于解决竟态)

news2024/12/23 17:57:37

目录

​编辑

一、多进程与多线程对比

二、 临界资源   临界区   竞态

 例1:临界资源 实现 输入输出

 例2:对临界资源 进行 减减 

例子3:临界资源抢占使用

三、线程的同步互斥机制(用于解决竟态)

3.1基本概念

3.2线程互斥

1、在C语言中,线程的互斥通过互斥锁来完成

2、互斥锁:本质上是一种临界资源,但互斥锁在同一时刻只能被一个线程使用,当一个线程试图去锁定另一个线程锁定的互斥所时,该线程会阻塞等待,直到使用互斥锁的线程解锁了该互斥锁

3、互斥锁的相关API

 4、对例3进行  互斥

3.3死锁

 3.4线程同步        

3.5线程同步 无名信号量

3.6 线程同步 条件变量


一、多进程与多线程对比

二、 临界资源   临界区   竞态

 例1:临界资源 实现 输入输出

#include <myhead.h>
#define N 2

char buf[128] = "";//临界资源

// 定义线程体1  使用临界资源的代码 称为临界区
void *task1(void *arg)
{
    while (1) // 输入信息
    {
        printf("输入:");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;
    }
}

// 定义线程体2  使用临界资源的代码 称为临界区
void *task2(void *arg)
{
    while (1) // 循环输出信息
    {
        usleep(500000); // 500000us  0.5s  执行一次
        printf("buf = %s\n", buf);
        bzero(buf, sizeof(buf));
    }
}
int main(int argc, char const *argv[])
{
    // 创建两个任务
    pthread_t tid1, tid2;

    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }

    // 主程序
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);

    // 阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

 例2:对临界资源 进行 减减 

#include <myhead.h>
#define N 2

int num  = 520;//临界资源

// 定义线程体1  使用临界资源的代码 称为临界区
void *task1(void *arg)
{
    while (1) // 输入信息
    {
        if (num > 0)
        {
            num--;
            printf("task1 执行一次,剩余%d次\n",num);
            usleep(1000);
        }else
        {
            printf("task1 执行结束\n");
            break;
        }
        
    }
    pthread_exit(EXIT_SUCCESS);
}

// 定义线程体2  使用临界资源的代码 称为临界区
void *task2(void *arg)
{
    while (1) // 输入信息
    {
        if (num > 0)
        {
            num--;
            printf("task2 执行一次,剩余%d次\n",num);
            usleep(1000);
        }else
        {
            printf("task2 执行结束\n");
            break;
        }
        
    }
    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    // 创建两个任务
    pthread_t tid1, tid2;

    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }

    // 主程序
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);

    // 阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

例子3:临界资源抢占使用

#include <myhead.h>
#define N 2

int num  = 520;//临界资源

// 定义线程体1  使用临界资源的函数 称为临界区
void *task1(void *arg)
{
    while (1) // 输入信息
    {
        if (num > 0)
        {
            num--;
            printf("task1 执行一次,剩余%d次\n",num);
            usleep(1000);
        }else
        {
            printf("task1 执行结束\n");
            break;
        }
        
    }
    pthread_exit(EXIT_SUCCESS);
}

// 定义线程体2  使用临界资源的函数 称为临界区
void *task2(void *arg)
{
    while (1) // 输入信息
    {
        if (num > 0)
        {
            num--;
            printf("task2 执行一次,剩余%d次\n",num);
            usleep(1000);
        }else
        {
            printf("task2 执行结束\n");
            break;
        }
        
    }
    pthread_exit(EXIT_SUCCESS);
}

// 定义线程体1  使用临界资源的函数 称为临界区
void *task3(void *arg)
{
    while (1) // 输入信息
    {
        if (num > 0)
        {
            num--;
            printf("task3 执行一次,剩余%d次\n",num);
            usleep(1000);
        }else
        {
            printf("task3 执行结束\n");
            break;
        }
        
    }
    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    // 创建三个任务
    pthread_t tid1, tid2,tid3;

    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }
    if (pthread_create(&tid3, NULL, task3, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }

    // 主程序
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);

    // 阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    return 0;
}

        由于多个线程共同使用进程的资源,导致,线程在操作上容易出现不安全的状态

        对于例3 减减 含有多步操作 相对耗时 在多个线程对临界资源抢占使用时,会出现某一个线程的 减减 未结束另一个线程的 开始执行,就会导致数据出错。

        该现象称为 竞态

三、线程的同步互斥机制(用于解决竟态)

        如上例3所示,当其中一个线程正在访问全局变量时,可能会出现时间片用完的情况,另一个线程将数据进行更改后,再执行 该线程时,导致数据的不一致。这种现象称竞态,为了解决竞态,引入了同步互斥机制

3.1基本概念

1)竟态:同一个进程的多个线程在访问临界资源时,会出现抢占的现象,导致线程中的数据错误的现象称为竞态

2)临界资源:多个线程共同访问的数据称为临界资源,可以是全局变量、堆区数据、外部文件

3)临界区:访问临界资源的代码段称为临界区

4)粒度:临界区的大小

5)同步:表示多个任务有先后顺序的执行

6)互斥:表示在访问临界区时,同一时刻只能有一个任务,当有任务在访问临界区时,其他任务只能等待

3.2线程互斥

1、在C语言中,线程的互斥通过互斥锁来完成

2、互斥锁:本质上是一种临界资源,但互斥锁在同一时刻只能被一个线程使用,当一个线程试图去锁定另一个线程锁定的互斥所时,该线程会阻塞等待,直到使用互斥锁的线程解锁了该互斥锁

3、互斥锁的相关API

       #include <pthread.h>
       1、创建互斥锁:只需要定义一个pthread_mutex_t类型的变量,就创建了一个互斥锁

       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;           //静态初始化一个互斥锁

       2、初始化互斥锁

       int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
       功能:初始化一个互斥锁
       参数1:互斥锁变量的地址
       参数2:互斥锁的属性,一般填NULL,使用系统默认提供的属性
       返回值:总是成功,不会失败(成功返回0)
        
       3、获取锁资源

       int pthread_mutex_lock(pthread_mutex_t *mutex);
       功能:某个线程调用该函数表示获取锁资源(阻塞等待)
       参数:互斥锁的地址
       返回值:成功返回0,失败返回一个错误码
       
       4、释放锁资源

        int pthread_mutex_unlock(pthread_mutex_t *mutex);
       功能:将线程中的锁资源释放
       参数:互斥锁的地址
       返回值:成功返回0,失败返回一个错误码
       
        5、销毁锁资源
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       功能:销毁程序中的锁资源
       参数:互斥锁地址        

 4、对例3进行  互斥

#include <myhead.h>
#define N 2

int num = 520; // 临界资源

// 创建一个互斥锁
pthread_mutex_t mutex;

// 定义线程体1  使用临界资源的代码 称为临界区
void *task1(void *arg)
{
    while (1) // 输入信息
    {
        // 上锁  获取锁资源
        pthread_mutex_lock(&mutex);

        if (num > 0)
        {
            num--;
            printf("task1 执行一次,剩余%d次\n", num);
            usleep(1000);
        }
        else
        {
            printf("task1 执行结束\n");
            // 解锁 释放锁资源
            pthread_mutex_unlock(&mutex); // 结束前解锁 避免死锁
            break;
        }
        // 解锁 释放锁资源
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(EXIT_SUCCESS);
}

// 定义线程体2  使用临界资源的代码 称为临界区
void *task2(void *arg)
{
    while (1) // 输入信息
    {
        // 上锁  获取锁资源
        pthread_mutex_lock(&mutex);

        if (num > 0)
        {
            num--;
            printf("task2 执行一次,剩余%d次\n", num);
            usleep(1000);
        }
        else
        {
            printf("task2 执行结束\n");
            // 解锁 释放锁资源
            pthread_mutex_unlock(&mutex); // 避免死锁
            break;
        }
        // 解锁 释放锁资源
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(EXIT_SUCCESS);
}

// 定义线程体3  使用临界资源的函数 称为临界区
void *task3(void *arg)
{
    while (1) // 输入信息
    {
        // 上锁  获取锁资源
        pthread_mutex_lock(&mutex);

        if (num > 0)
        {
            num--;
            printf("task3 执行一次,剩余%d次\n", num);
            usleep(1000);
        }
        else
        {
            printf("task3 执行结束\n");
            // 解锁 释放锁资源
            pthread_mutex_unlock(&mutex); // 避免死锁
            break;
        }
        // 解锁 释放锁资源
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    //初始化锁
    pthread_mutex_init(&mutex, NULL);

    // 创建三个任务
    pthread_t tid1, tid2, tid3;

    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }
    if (pthread_create(&tid3, NULL, task3, NULL) != 0)
    {
        printf("task2 error");
        return -1;
    }

    // 主程序
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);

    // 阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    // 销毁锁资源
    pthread_mutex_destroy(&mutex);
    return 0;
}

3.3死锁

1、概念:在多线程编程中,死锁是一种情况,其中两个或多个线程被永久阻塞,因为每个线程都在等待其他线程释放它们需要的资源。在C语言中,这通常涉及互斥锁(mutexes),当多个互斥锁被不同的线程以不同的顺序获取时,很容易发生死锁。

2、产生条件:

1) 互斥条件:资源不能被多个线程共享,只能被一个线程在任一时刻所使用。
2) 持有和等待条件:一个线程至少持有一个资源,并且正在等待获取一个当前被其他线程持有的资源。
3.)不可抢占条件:资源不能被强制从一个线程抢占到另一个线程,线程必须自愿释放它的资源。
4.)循环等待条件:存在一个线程(或多个线程)的集合{P1, P2, ..., Pn},其中P1正在等待P2持有的资源,P2正在等待P3持有的资源,依此类推,直至Pn正在等待P1持有的资源。

3、例4:死锁示例

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock1, lock2;

void* thread1(void* arg) {
    pthread_mutex_lock(&lock1);
    sleep(1); // 确保线程2能锁住lock2
    pthread_mutex_lock(&lock2);
    printf("Thread 1\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}

void* thread2(void* arg) {
    pthread_mutex_lock(&lock2);
    sleep(1); // 确保线程1能锁住lock1
    pthread_mutex_lock(&lock1);
    printf("Thread 2\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
    return 0;
}

4、避免死锁的策略:

1.)避免持有和等待:尽可能让线程在开始执行前一次性获取所有必需的资源。
2.)资源排序:规定一个全局顺序来获取资源,并且强制所有线程按这个顺序获取资源。
3.)使用超时:在尝试获取资源时使用超时机制,这样线程在等待过长时间后可以放弃,回退,并重新尝试。
4.)检测死锁并恢复:运行时检测死锁的存在,一旦检测到死锁,采取措施(如终止线程或回滚操作)来解决。

 3.4线程同步        

1、多个线程任务有顺序的执行,由于多个任务有顺序的执行了,那么在同一时刻,对临界资源的访问就只一个线程

2、线程同步文件的经典问题是:生产者消费者问题

        对于该问题而言,需要先执行生产者任务,然后执行消费者任务

3、对于线程同步问题,有两种机制来完成:无名信号量、条件变量

3.5线程同步 无名信号量

1、无名信号量本质上也是一个特殊的临界资源

2、无名信号量中维护了一个value值,该值表示能够申请的资源个数,当该值为0时,申请资源的线程将处于阻塞,直到其他线程将该无名信号量中的value值增加到大于0时即可

3、无名信号量API

1、创建无名信号量:只需要定义一个 sem_t 类型的变量,就创建了一个无名信号量
    sem_t  sem;
2、初始化无名信号量
           #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);
       功能:完成对无名信号量的初始化工作
       参数1:要被初始化的无名信号量地址
       参数2:无名信号量适用情况
           0:表示多线程之间
           非0:表示多进程间同步
        参数3:无名信号量的资源初始值
        返回值:成功返回0,失败返回-1并置位错误码

3、申请资源:P操作,将资源减1操作
           #include <semaphore.h>

       int sem_wait(sem_t *sem);
       功能:申请无名信号量中的资源,使得该信号量中的value-1
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码

4、释放资源:V操作,将资源加1操作
               #include <semaphore.h>

       int sem_post(sem_t *sem);
       功能:将无名信号量中的资源加1操作
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码

5、销毁无名信号量
       #include <semaphore.h>

       int sem_destroy(sem_t *sem);
       功能:销毁一个无名信号量
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码

 4、同步  无名信号量 示例

未 使用 无名信号量

#include <myhead.h>

//创建生产者线程
void * task1(void *arg)
{
    int num = 5;
    while (num--)
    {
        printf("%#lx:执行一次生产\n",pthread_self());
        sleep(1);
    }
    pthread_exit(EXIT_SUCCESS);
}

//创建消费者线程
void * task2(void *arg)
{
    int num = 5;
    while (num--)
    {
        printf("%#lx:执行一次消费\n",pthread_self());
    }
    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }

    pthread_join(tid2, NULL);
    pthread_join(tid1, NULL);
    return 0;
}

 

 使用 无名信号量

#include <myhead.h>

//1、创建无名学号量
sem_t sem;

//创建生产者线程
void * task1(void *arg)
{
    int num = 5;
    while (num--)
    {
        
        printf("%#lx:执行一次生产\n",pthread_self());
        //4、释放资源 无名信号量资源加1 允许消费者a线程执行
        sem_post(&sem);
    }
    pthread_exit(EXIT_SUCCESS);
}

//创建消费者线程
void * task2(void *arg)
{
    int num = 5;
    while (num--)
    {
        //3、申请资源
        sem_wait(&sem); //阻塞等待  生产者线程释放 无名信号量资源
        //当无名信号量资源 为1时 执行 减1
        //因为 无名信号量 初始资源为0  即便先执行消费者线程也会被阻塞
        printf("%#lx:执行一次消费\n",pthread_self());
    }
    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    //2、初始化无名信号量
    sem_init(&sem,0,0);
    //第一个0   表示同步用于多线程间
    //第二e个0  表示无名信号量初始资源为0

    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }

    pthread_join(tid2, NULL);
    pthread_join(tid1, NULL);

    //5、销毁无名信号量
    sem_destroy(&sem);
    return 0;
}

3.6 线程同步 条件变量

1、条件变量的本质也是一个特殊的临界资源

2、条件变量中维护了一个队列,想要执行的消费者线程,需要先进入等待队列中,等生产者线程进行唤醒后,依次执行。这样就可以做到一个生产者和多个消费者之间的同步,但是消费者和消费者之间在进入等待队列这件事上是互斥的。

3、条件变量的API

1)创建条件变量
    只需要定义一个pthread_cond_t类型的变量,就创建了一个条件变量
    pthread_cond_t  cond;

2)初始化条件变量
       int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
       功能:初始化一个条件变量
       参数1:条件变量的地址
       参数2:条件变量的属性,一般填NULL
       返回值:     成功返回0,失败返回非0错误码
       

3)将消费者线程放入到等待队列中       

       int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
       功能:消费者线程进入等待队列中
       参数1:条件变量的地址
       参数2:互斥锁的地址:因为多个消费者线程在进入等待队列上是竞态的
       返回值:     成功返回0,失败返回非0错误码

4)唤醒消费者线程                                

       int pthread_cond_signal(pthread_cond_t *cond);
       功能:唤醒等待队列中的第一个消费者线程
       参数:条件变量的地址
       返回值: 成功返回0,失败返回非0错误码       

       

       int pthread_cond_broadcast(pthread_cond_t *cond);
       功能:唤醒所有等待队列中的消费者线程
       参数:条件变量的地址
       返回值: 成功返回0,失败返回非0错误码
       

    
5)销毁条件变量           
        int pthread_cond_destroy(pthread_cond_t *cond);
        功能:销毁一个条件变量
        参数:条件变量的地址
        返回值: 成功返回0,失败返回非0错误码

 4、条件变量 示例

#include <myhead.h>

//1、定义一个条件变量
pthread_cond_t cond;

//11、创建互斥锁
pthread_mutex_t mutex;

// 创建生产者线程
void *task1(void *arg)
{
    // int num = 5;
    // while (num--)
    // {
    //     sleep(1);
    //     printf("%#lx:执行一次生产\n", pthread_self());

    //     //4、唤醒消费者线程(单个)
    //     pthread_cond_signal(&cond);
    // }

    sleep(3);

    printf("%#lx:执行5次生产\n", pthread_self());
    pthread_cond_broadcast(&cond);//4、唤醒消费者线程(全部)
    pthread_exit(EXIT_SUCCESS);
}

// 创建消费者线程
void *task2(void *arg)
{
    //33、获取互斥锁
    pthread_mutex_lock(&mutex);

    //3、请求进入等待队列
    pthread_cond_wait(&cond,&mutex);

    printf("%#lx:执行一次消费\n", pthread_self());

    //44、释放互斥锁
    pthread_mutex_unlock(&mutex);

    pthread_exit(EXIT_SUCCESS);
}
int main(int argc, char const *argv[])
{
    //2、初始化统计变量
    pthread_cond_init(&cond,NULL);

    //22、初始化互斥锁
    pthread_mutex_init(&mutex,NULL);

    pthread_t tid1, tid2, tid3, tid4, tid5, tid6;
    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid3, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid4, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid5, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }
    if (pthread_create(&tid6, NULL, task2, NULL) != 0)
    {
        printf("task1 error");
        return -1;
    }

    //输出每个线程的线程号
    printf("tid2=%#lx tid3=%#lx tid4=%#lx tid5=%#lx tid6=%#lx \n",tid2,tid3,tid4,tid5,tid6);

    pthread_join(tid2, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);
    pthread_join(tid6, NULL);

    //5、销毁条件变量
    pthread_cond_destroy(&cond);

    //55、销毁锁资源
    pthread_mutex_destroy(&mutex);

    return 0;
}

 

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

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

相关文章

C#实现数据采集系统-系统优化服务封装

系统优化-服务封装 现在我们调用modbustcp和mqtt都直接在Program,所有加载和功能都混合在一起,比较难以维护 类似asp.net core项目的Program.cs代码如下,构建服务配置和启动 要实现的效果,Main方法中就是一个服务启动,只需要几行代码 分析代码 这里分成两部分,一…

医疗健康类应用,适合采用哪些风格?本文归纳之,并附案例。

医疗健康类应用的设计风格应该注重用户友好性、专业性和清晰易懂。以下是一些适合医疗健康类应用采用的设计风格&#xff1a; 1. 清晰简洁的界面&#xff1a; 医疗健康类应用的界面设计应该简洁清晰&#xff0c;避免过多花哨的元素&#xff0c;让用户能够快速找到他们需要的信…

JAVA-案列练习-ATM项目

在JAVA入门学习后进行的项目练习——模拟ATM系统。 目录 1.设计内容和要求 2.代码实现 &#xff08;1&#xff09;ATM账号类的定义 &#xff08;2&#xff09;ATM类中的操作 &#xff08;3&#xff09;操作说明 3.总结 1.设计内容和要求 01 系统架构搭建&#xff0c;欢…

Spring Cloud全解析:注册中心之Eureka架构介绍

Eureka架构介绍 Eureka在设计时采用的是AP原则&#xff0c;是Netflix的一个子模块&#xff0c;用于微服务的服务注册与发现 P:Partition tolerance,网络分区容错。类似多机房部署&#xff0c;保证服务稳定性A: Availability&#xff0c;可用性C:Consistency &#xff0c;一致…

【时时三省】(C语言基础)函数递归

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 什么是递归 程序调用自身的编程技巧称为递归。递归做为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法&#xff0c;它通常把一个大型复杂…

Jenkin:动态选择Git分支

Jenkin&#xff1a;动态选择Git分支 build时输入分支 构建的时候添加一个字符参数 选择设置的变量&#xff0c;需要用${}括起来 构建时的效果 build时下拉选择分支 安装Git 参数插件 添加git参数 选择设置的变量&#xff0c;需要用${}括起来 构建时的效果

如何在不同设备上检查您的 IP 地址?

IP 地址&#xff08;Internet 协议地址&#xff09;是网络上设备的唯一标识符。了解如何查找 IP 地址对于解决网络问题、设置网络设备和维护网络安全非常重要。本文将详细介绍如何在不同设备上检查 IP 地址&#xff0c;包括 Windows 计算机、Mac 计算机、智能手机&#xff08;A…

计算机网络快速入门---百年前人们不敢想象的黑科技

目录 1.引言 2.快递VS网络 3.网络模型 3.1两个模型 3.2传输过程 4.IP 4.1IP简介 4.2解决方案一 5.局域网和互联网 5.1局域网说明 5.2互联网说明 6.网络攻击 1.引言 注意&#xff0c;这个只是一个快速入门&#xff0c;不是我开始进行系统学习了&#xff0c;这个博客…

【unity小技巧】unity性能优化以及如何进行性能测试

文章目录 前言GPU性能优化打包素材 CPU性能优化代码执行优化 性能测试Vector2.Distance 和 sqrMagnitude哪个好&#xff1f;动画切换优化shader属性优化 URP渲染器资产优化对象池优化删除没必要的空函数图片、音乐音效、贴图等素材压缩ScriptableObject优化参数参考完结 前言 …

数据结构与算法-15高级数据结构_树论(堆树)

堆树 1 简介 1.1 什么是堆树 定义&#xff1a;堆树是一种特殊的完全二叉树&#xff0c;其中每个节点的值都遵循一定的堆属性。具体来说&#xff0c;堆分为最大堆和最小堆。 最大堆&#xff1a;在最大堆中&#xff0c;每个父节点的值都大于或等于其任何子节点的值。这意味着…

SpringMVC02

1.拦截器 1.1基本概念 SpringMVC 中的Interceptor拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。对于SpringMVC拦截器的定义方式 有两种: 实现接口:org.springframework.web.ser…

CAD-文字、图块、多行文字,沿多段线对齐到多段线的顶点,沿直线进行均分,都可以操作

图块和文字对齐直线-均布直线-对齐多段线顶点-旋转平行 (defun c:duiqi () ;将图块与直线对齐&#xff0c;并均分。;先创建的图块排最右;先等分的坐标排最右;刚好对应了(defun MoveToPosition (Blockname p_list / ent refPoint dx dy) ;移动对象到指定坐标(prompt "\nSel…

【Git-驯化】一文学会git中对代码进行存储操作:git stash技巧

【Git-驯化】一文学会git中对代码进行存储操作&#xff1a;git stash技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内…

@change事件传参

change事件传参 change"(value)>handleChange(value, item,index)" 这样可以接收index参数区分是哪一个组件事件&#xff0c;又可以接收子组件传的value值 <div class"boxItem" v-for"(item, index) in checkPeopleList" :key"inde…

VUE实现TAB切换不同页面

VUE实现TAB切换不同页面 实现效果 资源准备 ReceiveOrderList, TodoListMulti, SignList 这三个页面就是需要切换的页面 首页代码 <template><div><el-tabs v-model"activeTab" type"card" tab-click"handleTabClick"><…

用于相位解包的卷积和空间四向 LSTM 联合网络

原文&#xff1a;A Joint Convolutional and Spatial Quad-Directional LSTM Network for Phase Unwrapping 作者&#xff1a;Malsha V. Perera 和 Ashwin De Silva 摘要&#xff1a; 相位展开是一个经典的病态问题&#xff0c;其目标是从包裹相位中恢复真实的相位。本文&…

鸿蒙(API 12 Beta2版)NDK开发【使用Node-API扩展能力接口】

简介 [扩展能力接口]进一步扩展了Node-API的功能&#xff0c;提供了一些额外的接口&#xff0c;用于在Node-API模块中与ArkTS进行更灵活的交互和定制&#xff0c;这些接口可以用于创建自定义ArkTS对象等场景。 Node-API接口开发流程参考[使用Node-API实现跨语言交互开发流程]…

非负数、0和正整数 限制最大值且保留两位小数在elementpuls表单中正则验证

一、结构 <el-form-item label"单价&#xff1a;" prop"price"><el-inputv-model.trim"formData.price"placeholder"请输入"blur"formMethod.fixTwo"><template #append>(元)</template></el-i…

基础算法:离散化(C++实现)

文章目录 1. 离散化的定义2. 离散化例题2.1 离散化二分2.2 离散化哈希表 1. 离散化的定义 离散化是一种在程序设计和算法优化中常用的技术&#xff0c;其核心思想是将无限空间中有限的个体映射到有限的空间中去&#xff0c;以此提高算法的时空效率。具体来说&#xff0c;离散化…

Docker 安装 GitLab教程

本章教程,主要介绍如何在Docker 中安装GitLab。 GitLab 是一个开源的 DevOps 平台,提供了一整套工具,用于软件开发生命周期的各个阶段,从代码管理到 CI/CD(持续集成和持续交付/部署),再到监控和安全分析。 一、拉取镜像 docker pull gitlab/gitlab-ce:latest二、创建 G…