Linux —— 线程同步

Linux —— 线程同步

  • 死锁
  • 线程同步
    • 条件变量
    • pthread_cond_wait
    • pthread_cond_signal
      • 初始状态
      • 为什么之后会“阻塞”
      • 如何修改以持续运行
    • pthread_cond_broadcast
  • 条件变量的接口
  • 抢票模拟

我们今天接着来了解线程:

死锁

死锁(Deadlock)是计算机科学中多任务处理和并发控制时的一种现象,特别是在操作系统、数据库系统和网络系统中较为常见。它指的是两个或两个以上的进程(或线程)在执行过程中,因为互相等待对方持有的资源而无法继续执行的情况,从而导致所有的进程都无法向前推进,系统状态僵死

死锁发生的四个必要条件为:

  1. 互斥条件:资源不能被多个进程同时使用,只能由一个进程占有。
  2. 请求与保持条件:已经持有至少一个资源的进程可以再请求新的资源。
  3. 不剥夺条件:已分配给一个进程的资源在该进程未明确释放之前,不能被其他进程强行夺走。
  4. 循环等待条件:存在一种进程资源的循环等待链,链中的每个进程已获得的资源同时被链中下一个进程所请求。

避免死锁的策略通常包括:

  • 破坏四个必要条件之一:例如,通过实施资源分配的排序策略来破坏循环等待条件,或者允许进程在等待新资源时释放已持有的资源以破坏请求与保持条件。
  • 银行家算法:这是一种避免死锁的著名算法,通过预判资源分配请求是否会导致系统进入不安全状态来决定是否分配资源。
  • 死锁检测与恢复:系统定期检查是否存在死锁,一旦检测到死锁,则采取相应措施恢复,比如终止一部分进程,收回资源重新分配。
  • 超时重试:为避免长时间阻塞,可以为资源请求设置超时,超时后回滚并重试请求。

理解并有效管理这些机制对于设计高效、稳定的多线程和分布式系统至关重要。

以我们现在的水平,死锁这个现象并不常见,大家了解一下即可。

线程同步

线程同步是指在多线程编程中,为了防止多个线程访问同一资源造成数据不一致、数据竞争等问题,确保线程按照预定的顺序执行而采取的一系列协调机制。其目的是确保在任一时刻,只有一个线程能够访问共享资源,其他线程则需要等待,直到该资源被释放。

线程同步的方法和机制多种多样,以下是一些常见的同步技术:

  1. 互斥锁(Mutex):最基本的同步原语,用于保护临界区(即访问共享资源的代码段)。当一个线程获得了互斥锁后,其他试图获取同一锁的线程将被阻塞,直到第一个线程释放锁。
  2. 信号量(Semaphore):信号量是一个更通用的同步工具,不仅可以实现互斥锁的功能,还能控制对有限资源的访问数量。信号量维护一个计数器,线程可以增加或减少这个计数器的值,当计数器为非正时,试图减少它的线程会被阻塞。
  3. 条件变量(Condition Variable):条件变量用于线程间的同步,允许一个或多个线程等待某个特定条件发生。当条件不满足时,线程可以阻塞自己,直到另一个线程通知条件已满足。
  4. 读写锁(Read-Write Lock):适用于读操作远多于写操作的场景。允许多个读者同时访问资源,但写者访问时会排斥所有其他读写者。这提高了并发性能。
  5. 原子操作(Atomic Operations):提供了一种方法来执行简单操作(如增加、减少、交换等),这些操作在多线程环境中被视为不可分割的,即操作过程中不会被其他线程打断。
  6. 屏障(Barrier):屏障是一个同步点,所有线程到达这个点后才会继续执行。这对于需要所有线程完成某阶段工作后再一起进入下一阶段的场景非常有用。

正确地使用线程同步机制是编写并发程序的关键,能够有效地避免竞态条件、死锁等问题,保证程序的正确性和一致性。

我们实现同步,一般会用条件变量

条件变量

条件变量是多线程编程中用于线程同步的一种机制,它允许线程在某些条件未满足时等待,直到其他线程改变了这个条件后再唤醒它们。条件变量通常与互斥锁一起使用,以防止多个线程同时修改共享数据,并且确保在条件不满足时线程能够安全地等待

在使用条件变量的经典模式中,涉及以下几个步骤:

  1. 加锁:在检查或修改共享数据之前,线程首先需要获取一个关联的互斥锁,以确保独占访问。
  2. 检查条件:线程检查某个条件是否满足。如果条件已经满足,线程可以继续执行。如果没有满足,则进入下一步。
  3. 等待:如果条件没有满足,线程调用条件变量的等待函数(如pthread_cond_wait在POSIX线程中)并释放互斥锁。这样做的目的是让出CPU给其他线程,并且让自己处于等待状态,直到被其他线程通过条件变量的信号函数(如pthread_cond_signalpthread_cond_broadcast)唤醒。
  4. 被唤醒后重新检查条件:当线程被唤醒时,它会重新获取互斥锁并再次检查条件是否满足。这是必要的,因为可能存在“虚假唤醒”情况,即线程被唤醒并不是因为预期的条件改变,而是其他原因。
  5. 执行或再次等待:如果条件满足,线程可以继续执行其后续逻辑。如果不满足,线程可能需要再次调用等待函数,重复上述过程。

条件变量的主要目的是提供一种优雅的方式,让线程能够高效地等待某个条件,并且只有当条件变为真时才继续执行,从而实现了线程间的协调和同步。在多线程编程中,正确使用条件变量可以避免数据竞争和竞态条件,提高程序的健壮性。

我们这里举个简单的例子,我们创建三个线程,向屏幕循环打印信息:

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

void* threadRouite(void* args)
{
    const char* name = static_cast<const char*>(args);

    while(true)
    {
        std::cout << "I am running my name is: "<< name << std::endl;
        sleep(1);
    }

    return nullptr;
}


int main()
{
    //创建线程
    pthread_t tid1,tid2,tid3; //线程id

    pthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");
    pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");
    pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
}

在这里插入图片描述
这个时候会有线程竞争的问题,这个时候我们可以上锁,保证在一个时间段内,只有一个线程往屏幕打印。

在这里插入图片描述
这个时候会有一个问题,很长的一段时间都是thread-1运行,线程2,线程3都没有机会执行,为了让三个线程相对均匀的被调动,我们这个时候就要使用条件变量

上完锁之后,我们让所有线程等待:

pthread_cond_wait

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


//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //

void* threadRouite(void* args)
{
    const char* name = static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex); //让所有线程等待
        std::cout << "I am running my name is: "<< name << std::endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}


int main()
{
    //创建线程
    pthread_t tid1,tid2,tid3; //线程id

    pthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");
    pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");
    pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
}

在这里插入图片描述我们可以等3秒就唤醒一个线程:

pthread_cond_signal

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


//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //

void* threadRouite(void* args)
{
    const char* name = static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex); //让所有线程等待
        std::cout << "I am running my name is: "<< name << std::endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}


int main()
{
    //创建线程
    pthread_t tid1,tid2,tid3; //线程id

    pthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");
    pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");
    pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");

    //3秒后,唤醒一个线程
    sleep(1);
    pthread_cond_signal(&cond);

    //3秒后,唤醒一个线程
    sleep(1);
    pthread_cond_signal(&cond);

    //3秒后,唤醒一个线程
    sleep(1);
    pthread_cond_signal(&cond);

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
}

在这里插入图片描述
打印三次之后,程序就像是被阻塞了一样,我们来分析一下:

初始状态

  • 创建了三个线程(tid1, tid2, tid3),每个线程启动时会立刻调用pthread_cond_wait(&cond, &mutex),这意味着它们一启动就会因为条件变量cond而阻塞,等待被其他线程通过pthread_cond_signalpthread_cond_broadcast唤醒。
  • 主线程随后执行,它首先等待1秒,然后发送一次信号(pthread_cond_signal(&cond))。这个信号会唤醒一个处于等待状态的线程(假设是tid1),tid1线程开始执行并打印消息,完成后再次调用pthread_cond_wait进入等待状态。
  • 主线程再次等待1秒后,发送第二个信号,唤醒第二个线程(比如tid2)。
  • 同理,第三个信号唤醒了最后一个线程tid3

为什么之后会“阻塞”

  • 在每个线程被唤醒并执行后,它们会再次调用pthread_cond_wait(&cond, &mutex)。此时,由于主线程的循环已经完成,没有更多的pthread_cond_signal调用来唤醒它们。因此,这三个线程都再次进入等待状态,期望着未来某个时刻收到信号。
  • 由于主线程没有进一步的信号发送动作,并且没有设计退出条件或循环来持续发送信号,这三个线程就这样一直等待下去,看起来就像“阻塞”了。

如何修改以持续运行

如果想要线程持续运行并周期性地被唤醒,一种方式是在主线程中加入循环,持续发送信号给条件变量。但是,直接无限制地发送信号可能导致不必要的频繁唤醒和资源消耗,因此应该根据具体需求来设计合理的唤醒逻辑。

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


//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //

void* threadRouite(void* args)
{
    const char* name = static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex); //让所有线程等待
        std::cout << "I am running my name is: "<< name << std::endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}


int main()
{
    //创建线程
    pthread_t tid1,tid2,tid3; //线程id

    pthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");
    pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");
    pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");

    while(true)
    {
        sleep(1);
        pthread_cond_signal(&cond); //一直发送信号
    }

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
}

在这里插入图片描述
这样每个线程都会被均匀的调度。

pthread_cond_broadcast

pthread_cond_broadcast会一次唤醒所有线程,至于谁能抢到运行的权利,就看各个线程的本事了:

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

//上锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //

void* threadRouite(void* args)
{
    const char* name = static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex); //让所有线程等待
        std::cout << "I am running my name is: "<< name << std::endl;
        sleep(1);
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}


int main()
{
    //创建线程
    pthread_t tid1,tid2,tid3; //线程id

    pthread_create(&tid1,nullptr,threadRouite,(void*)"thread-1");
    pthread_create(&tid2,nullptr,threadRouite,(void*)"thread-2");
    pthread_create(&tid3,nullptr,threadRouite,(void*)"thread-3");

    while(true)
    {
        sleep(1);
        pthread_cond_broadcast(&cond); //一直发送信号,唤醒所有线程
    }

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    pthread_join(tid3,nullptr);
}

在这里插入图片描述

条件变量的接口

使用一些接口之后,我们来看看条件变量的接口:
在POSIX线程(pthread)库中,条件变量提供了以下主要接口用于线程间的同步:

  1. 初始化和销毁:

    • 初始化条件变量:
      int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *attr);
      
      cv 是指向条件变量的指针,attr 是可选的条件变量属性(一般传NULL使用默认属性)。
    • 销毁条件变量:
      int pthread_cond_destroy(pthread_cond_t *cv);
      
      释放与条件变量关联的资源。
  2. 等待和唤醒:

    • 等待条件变量:
      int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
      
      当前线程会释放互斥锁mutex并进入等待状态,直到其他线程通过pthread_cond_signalpthread_cond_broadcast唤醒它。被唤醒后,会重新获取锁。
    • 信号通知:
      int pthread_cond_signal(pthread_cond_t *cv);
      
      唤醒一个(至少一个)在条件变量cv上等待的线程。具体唤醒哪个线程由实现决定。
    • 广播通知:
      int pthread_cond_broadcast(pthread_cond_t *cv);
      
      唤醒所有在条件变量cv上等待的线程。这通常用于需要唤醒所有线程的情况。
  3. 属性操作:

    • 初始化属性对象:
      int pthread_condattr_init(pthread_condattr_t *attr);
      
      初始化条件变量属性对象。
    • 设置属性:
      int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
      
      设置条件变量是否在进程间共享。pshared参数为PTHREAD_PROCESS_SHARED表示跨进程共享,PTHREAD_PROCESS_PRIVATE表示仅在同一进程中共享(默认)。
    • 获取属性:
      int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared);
      
      获取条件变量属性中的进程共享状态。
    • 销毁属性对象:
      int pthread_condattr_destroy(pthread_condattr_t *attr);
      
      释放条件变量属性对象占用的资源。

这些接口共同构成了条件变量的核心功能,允许线程之间基于某些条件进行复杂的同步控制,是构建多线程程序中不可或缺的一部分。

抢票模拟

我们这里加上条件变量,实现一个补票的逻辑:

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

// 初始化互斥锁,用于保护共享资源tickets
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 初始化条件变量,用于线程间通信,通知票已补充
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 共享资源:剩余票数
int tickets = 100; // 初始有100张票

// 线程函数,模拟售票操作
void* threadRouite(void* args) {
    const char* name = static_cast<const char*>(args); // 获取线程名称

    while(true) { // 无限循环,直到手动中断
        // 加锁,确保接下来的操作原子性
        pthread_mutex_lock(&mutex);

        // 检查是否有票可售
        if(tickets > 0) {
            tickets--; // 卖出一张票
            std::cout << "Left tickets: " << tickets << " thread name: " << name << std::endl; // 打印剩余票数和售票线程名
        } else {
            std::cout << "no tickets" << std::endl; // 如果没票,打印提示
            // 没有票时,线程等待,释放锁,直到被通知
            pthread_cond_wait(&cond, &mutex);
        }

        // 操作完成后解锁
        pthread_mutex_unlock(&mutex);
    }

    return nullptr; // 线程返回指针类型为空指针
}

int main() {
    // 创建三个售票线程
    pthread_t tid1, tid2, tid3;

    pthread_create(&tid1, nullptr, threadRouite, (void*)"thread-1"); // 创建并启动线程1
    pthread_create(&tid2, nullptr, threadRouite, (void*)"thread-2"); // 创建并启动线程2
    pthread_create(&tid3, nullptr, threadRouite, (void*)"thread-3"); // 创建并启动线程3

    // 主线程循环,每三秒检查一次是否需要补充票
    while(true) {
        sleep(3); // 等待3秒

        // 加锁,保护票数的修改
        pthread_mutex_lock(&mutex);

        // 检查是否需要补充票
        if(tickets == 0) {
            tickets += 100; // 补充100张票
        }

        // 解锁
        pthread_mutex_unlock(&mutex);

        // 通知所有等待的线程,票已补充
        pthread_cond_broadcast(&cond); // 使用广播,唤醒所有等待线程
    }

     pthread_join(tid1, nullptr);
     pthread_join(tid2, nullptr);
     pthread_join(tid3, nullptr);

    return 0; // 程序正常结束
}

这段代码展示了一种简单的线程同步机制,其中售票线程会尝试减少票数,如果没有票则等待;主线程定期检查并补充票数,然后通过条件变量通知等待的线程。然而,正如注释中指出的,pthread_join调用在当前代码结构中无法执行,因为main函数中的无限循环没有提供退出机制。在实际应用中,应当设计合适的逻辑来终止循环,并确保所有线程能够正确地结束。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

配置旁挂二层组网直接转发示例(命令行)

业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 组网需求 AC组网方式&#xff1a;旁挂二层组网。DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。汇聚交换机SwitchB作…

《Effective Objective-C 2.0》读书笔记——熟悉Objective-C

目录 第一章&#xff1a;熟悉Objective-C第1条&#xff1a;了解Objective-C语言的起源第2条&#xff1a;在类的头文件中尽量少引入其他头文件第3条&#xff1a;多用字面量语法&#xff0c;少用与之等价的方法第4条&#xff1a;多用类型常量&#xff0c;少用#define预处理指令第…

记录docker ps查找指定容器的几个命令

1.docker ps | grep registry 查询包含registry的容器 2.docker ps | grep -E "reigistry\s" 开启正则匹配模式&#xff0c;匹配registry后面为空格的容器&#xff0c;若是匹配一整行可以这样写docker ps | grep -E "^([0-9a-f]{12})\sregistry\s.*" 这…

Nacos 2.x 系列【2】单机部署

文章目录 1. 准备工作2. Windows2.1 下载2.2 目录 & 文件2.3 启动2.4 控制台 3. Linux&#xff08;CentOS&#xff09; 1. 准备工作 Nacos服务端支持三种部署模式&#xff1a; 单机模式&#xff1a;用于测试和单机试用。集群模式&#xff1a;用于生产环境&#xff0c;确保…

Elasticsearch集群和Logstash、Kibana部署

1、 Elasticsearch集群部署 服务器 安装软件主机名IP地址系统版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.56centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 软件版本&#xff1a;elasticsearch-7.13.2.tar.gz 示…

Little Snitch for Mac(小飞贼防火墙软件)v5.7.6注册激活版

Little Snitch for Mac&#xff0c;也被称为“小飞贼”防火墙软件&#xff0c;是一款专为Mac用户设计的网络安全工具。以下是关于Little Snitch for Mac的一些主要特点&#xff1a; Little Snitch for Mac(小飞贼防火墙软件)v5.7.6注册激活版下载 强大的监控能力&#xff1a;Li…

Spring框架中获取方法参数名称:DefaultParameterNameDiscoverer

DefaultParameterNameDiscoverer 是Spring框架中用于获取方法参数名称的一个类。在Java中&#xff0c;方法的参数名称通常在编译时会丢失&#xff0c;因为Java字节码并不强制要求保留这些信息。Spring提供了一种机制来恢复这些参数名称&#xff0c;这就是通过DefaultParameterN…

【C++】 单例设计模式的讲解

前言 在我们的学习中不免会遇到一些要设计一些特殊的类&#xff0c;要求这些类只能在内存中特定的位置创建对象&#xff0c;这就需要我们对类进行一些特殊的处理&#xff0c;那我们该如何解决呢&#xff1f; 目录 1. 特殊类的设计1.1 设计一个类&#xff0c;不能被拷贝&#xf…

阿木实验室联合openEuler开源社区-Embedded SlG组(海思项目)参加第五届「开源之夏」,参赛学生火热招募中...

开源之夏是中国科学院软件研究所发起的“开源软件供应链点亮计划”系列暑期活动&#xff0c;旨在鼓励高校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区&#xff0c;针对重要开源软件的开发与维护提供项目开发任务&#xf…

bugku 网络安全事件应急响应

开启靶场&#xff1a; 开始实验&#xff1a; 使用Xshell登录服务器&#xff0c;账号及密码如上图。 1、提交攻击者的IP地址 WP: 找到服务器日志路径&#xff0c;通常是在/var/log/&#xff0c;使用cd /var/log/&#xff0c;ls查看此路径下的文件. 找到nginx文件夹。 进入ng…

LabVIEW超高温高压流变仪测试系统

LabVIEW超高温高压流变仪测试系统 超高温高压流变仪广泛应用于石油、天然气、化工等行业&#xff0c;用于测量材料在极端条件下的流变特性。随着计算机技术、测试技术和电子仪器技术的快速发展&#xff0c;传统的流变仪测试方式已无法满足现代工业的需求。因此&#xff0c;开发…

Java——通过方法交换实参值

想写一个方法来交换main函数中的两个变量值&#xff0c;代码如下&#xff1a; public class Test {public static void swap(int x,int y) {int tmp x;x y;y tmp;}public static void main(String[] args) {int a 10;int b 20;System.out.println("交换前&#xff1…

有没有软件可以监控电脑软件?监控电脑软件的系统

有没有软件可以监控电脑软件&#xff1f;监控电脑软件的系统 电脑软件如果不合规也会给企业带来安全危害&#xff0c;比如盗版软件&#xff0c;比如游戏软件耽误工作等&#xff0c;所以需要对电脑软件的监控。下面我将详细介绍几款代表性的电脑监控软件及其功能&#xff0c;帮…

【JAVA基础之内部类】匿名内部类

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;小林同学的专栏&#xff1a;JAVA之基础专栏 目录 1.内部类 1.1 概述 1.1.1 什么是内部类 1.1.2 什么时候使用内部类 1.2 内部类的分类 1.3 成员内部类 1.3.1 获取成员内部类对象的两种方式 1.3.2 经典面试…

深入pandas:导入数据表

目录 前言 第一点&#xff1a;导入模块 第二点&#xff1a;创建excel表 第三点&#xff1a;读取数据表 总结 前言 数据分析和处理过程中&#xff0c;我们经常需要从外部文件中读取数据。本文将介绍如何使用Python中的Pandas库来读取CSV和Excel文件&#xff0c;以及提取纯数…

v-cloak 用于在 Vue 实例渲染完成之前隐藏绑定的元素

如果你是后端开发者&#xff08;php&#xff09;&#xff0c;在接触一些vue2开发的后台时&#xff0c;会发现有这段代码&#xff1a; # CDN <script src"https://cdn.jsdelivr.net/npm/vue2/dist/vue.js"></script> # 或 <script src"https://cd…

Nacos启动报错:[db-load-error]load jdbc.properties error

在学习Nacos中间件时&#xff0c;出现了一个错误&#xff0c;竟然启动报错&#xff01;&#xff01;&#xff01;! 这个错误第一次遇见&#xff0c;当时我感觉大体就是--数据库连接方面的错误。 可是&#xff0c;对于初学者的我来说一脸懵啊&#xff1f;&#xff1f;&#xff…

微信小程序仿胖东来轮播和背景效果(有效果图)

效果图 .wxml <view class"swiper-index" style"--width--:{{windowWidth}}px;"><image src"{{swiperList[(cardCur bgIndex -1?swiperList.length - 1:cardCur bgIndex > swiperList.length -1?0:cardCur bgIndex)]}}" clas…

代码随想录Day 49|Leetcode|Python|● 647. 回文子串 ● 516.最长回文子序列● 动态规划总结篇

647. 回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 解题思路&#xff1a; 确认dp含义&#xff1a;dp[i][j] s[i:j]是否为回文串…

k8s 1.24.x之后如果rest 访问apiserver

1.由于 在 1.24 &#xff08;还是 1.20 不清楚了&#xff09;之后&#xff0c;下面这两个apiserver的配置已经被弃用 了&#xff0c;简单的说就是想不安全的访问k8s是不可能了&#xff0c;所以只能走安全的访问方式也就是 https://xx:6443了&#xff0c;所以需要证书。 - --ins…