[Linux]线程同步

news2025/1/10 21:07:56

[Linux]线程同步

文章目录

  • [Linux]线程同步
    • 线程同步
      • 线程饥饿问题
      • 概念
    • 线程同步控制--条件变量
      • pthread_cond_init函数
      • pthread_cond_destroy函数
      • pthread_cond_wait函数
      • pthread_cond_signal函数
      • pthread_cond_broadcast函数
      • 条件变量相关函数的使用示例
      • 生产者消费者模型
      • 基于BlockingQueue的生产者消费者模型
    • 线程同步控制--POSIX信号量
      • 概念
      • sem_init函数
      • sem_wait函数
      • sem_post函数
      • sem_destroy函数
      • 基于环形队列的生产消费模型

线程同步

线程饥饿问题

线程饥饿是指一个或多个线程无法获取到所需的资源或执行时间片,从而长时间处于阻塞或等待状态,无法继续执行的情况。

举一个简单的示例来理解线程饥饿问题:多个线程通过加锁的方式完成了线程互斥控制,但是由于第一个申请到锁的线程在释放锁后,立刻又申请锁,导致其他的线程一直无法申请到锁。

概念

线程同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

以前面提到的线程饥饿问题中所举的例子理解线程同步:同样通过加锁的方式让多个线程互斥,第一个申请到锁的线程在释放锁后,按照顺序,下一个申请锁的是其他线程,从而完成对线程饥饿问题的解决。

线程同步控制–条件变量

条件变量是Linux操作系统原生线程库中提供的pthread_cond_t数据类型,通过对条件变量的使用能够完成线程的同步控制。

条件变量内部维护着一个循环队列,将线程交给条件变量后,条件变量就可以通过自身的循环队列结构让线程按照顺序运行。

pthread_cond_init函数

Linux操作系统下提供了pthread_cond_init函数用于初始化条件变量。

//pthread_cond_init所在的头文件和函数声明
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • cond参数: 要初始化的条件变量。
  • attr参数: 给条件变量设置的属性,默认传入空指针。
  • 全局条件变量可以采用初始化时pthread_cond_t cond = PTHREAD_COND_INITIALIZER的方式进行初始化。

pthread_cond_destroy函数

Linux操作系统下提供了pthread_cond_destroy函数用于销毁锁。

//pthread_mutex_destroy所在的头文件和函数声明
#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • pthread_cond_destroy函数用于局部条件变量的销毁。
  • cond参数: 要销毁的条件变量。

pthread_cond_wait函数

//pthread_cond_wait所在的头文件和函数声明
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • pthread_cond_wait函数用于让线程等待条件变量准备就绪,当线程开始等待条件变量后,只有条件变量准备就绪线程才能继续运行。
  • cond参数: 要使用的条件变量。
  • mutex参数: 要使用的锁。
  • pthread_cond_wait函数首先会对传入的锁进行释放,以便于其他线程访问临界区,然后阻塞等待条件变量就绪,条件变量就绪后,会再次对传入的锁申请,保证线程安全。

pthread_cond_signal函数

//pthread_cond_signal所在的头文件和函数声明
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_signal函数用于唤醒等待条件变量的线程中处于第一个顺位。该函数就是告诉等待条件变量的线程条件变量已经准备就绪,线程被唤醒后就会继续运行。
  • cond参数: 要使用的条件变量。

pthread_cond_broadcast函数

//pthread_cond_broadcast所在的头文件和函数声明
#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
  • pthread_cond_broadcast函数用于唤醒等待条件变量的所有线程。该函数就是告诉所有等待条件变量的线程条件变量已经准备就绪,线程都被唤醒后会按照顺序继续运行。
  • cond参数: 要使用的条件变量。

条件变量相关函数的使用示例

为了验证条件变量能让线程按顺序执行,编写代码验证,具体代码如下:

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

#define NUM 5

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//锁的初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//条件变量的初始化

void *active(void *args)
{
    const char* tname = static_cast<const char*>(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);//等待函数内会自动释放锁
        cout << tname << " active" << endl;
        pthread_mutex_unlock(&mutex);
    }
    return nullptr;
}

int main()
{
    pthread_t tids[NUM];
    for (int i = 0; i < NUM; i++)//线程创建
    {
        char* name = new char[64];
        snprintf(name, 64, "thread-%d", i+1);
        pthread_create(tids+i, nullptr, active, name);
    }

    sleep(2);

    while(true)//唤醒线程
    {
        pthread_cond_signal(&cond);
        sleep(1);
    }

    for (int i = 0; i < NUM; i++)//线程回收
    {
        pthread_join(tids[i], nullptr);
    }
    return 0;
}

编译代码运行并查看结果:

cond

在上面的验证代码中,线程加锁进入临界区后首先会等待条件变量就绪,并且根据线程的调用顺序在该条件变量的循环队列中等待,条件变量会根据循环队列中的线程顺序进行唤醒,因此线程会按照一定的顺序进行运行。

生产者消费者模型

为了理解生产者消费者模型,我们举一个生活中的例子,以超市作为载体形成的生产者消费者模型:

image-20230930173651179

供货商作为生产者生产商品并派送到超市,顾客作为消费者从超市消费商品。供货商作为生产者大量生产商品交给超市销售,顾客作为消费者集中到超市购买商品,既提高了生产效率,也提高了消费效率。供货商作为生产者可以大量生产商品交给超市,顾客作为消费者只需要关心超市的商品,因此即使消费者暂时不消费,供货商也可以继续生产,即使供货商暂时不生产商品了,消费者也可以继续消费,这也允许了生产和消费的步调不一致,生产工作和消费消费能够忙闲不均,以至于生产和消费双方不耽误。

对应到线程概念中:

  • 超市是一种缓冲区,以某种结构存储数据。
  • 供货商是生产者线程,产生某种结构化的数据并送到缓冲区中。
  • 顾客是消费者线程,从缓冲区中取数据并对数据进行相应的处理。

利用缓冲区作为交易场所,让生产者和消费者不必关心对方,因此生产者消费者模型优点:

  • 解耦

  • 支持并发

  • 支持忙闲不均

生产者消费者模型也可以说是一种线程线程通信,它就类似于进程间通信所使用的管道。该模型中的缓冲区作为作为线程通信场所,要被生产者线程和消费者线程看到,因此该缓冲区是一种共享资源,作为共享资源就要对其进行保护,避免线程安全问题,要对生产者消费者模型进行保护。

生产者消费者模型特点总结:

  • 三种关系
    • 生产者和生产者:互斥关系(缓冲区空间有限,只能存储生产者送来的有限数据)
    • 生产者和消费者:同步关系(缓冲区要满了要让消费者优先消费,缓冲区要空了要让生产者优先生产,如果不以某种同步关系处理,生产者和消费者就要频繁并且不必要地查询缓冲区的状态,导致效率低下)和互斥关系(同一时刻不能让消费者和生产者同时访问缓冲区)
    • 消费者和消费者:互斥关系(缓冲区内部的数据有限,消费者只能从缓冲区中获取有限的数据)
  • 两种角色
    • 生产者
    • 消费者
  • 一个交易场所
    • 缓冲区

基于BlockingQueue的生产者消费者模型

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

image-20231001164158555

编写代码体会基于BlockingQueue的生产者消费者模型和条件变量的运用:

blockqueue.hpp:实现阻塞队列的文件

#include <queue>
#include <pthread.h>

const int gcap = 5;

template <class T>
class blockqueue
{
private:
    bool isFull() { return _q.size() == _cap; }
    bool isEmpty() { return _q.empty(); }

public:
    blockqueue(int cap = gcap) : _cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_consumercond, nullptr);
        pthread_cond_init(&_productorcond, nullptr);
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while (isFull()) // 队列已满,采用循环判断是为了避免多个生产者都被消费者唤醒后造成并发问题,比如队列只剩一个空间,但多个生产者线程被唤醒后进行数据操作
        {
            pthread_cond_wait(&_productorcond, &_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_consumercond);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while (isEmpty())
        {
            pthread_cond_wait(&_consumercond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_productorcond);
        pthread_mutex_unlock(&_mutex);
    }
    ~blockqueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumercond);
        pthread_cond_destroy(&_productorcond);
    }

private:
    std::queue<T> _q;
    int _cap;                      // 记录容量
    pthread_mutex_t _mutex;        // 控制线程互斥
    pthread_cond_t _consumercond;  // 控制消费者线程
    pthread_cond_t _productorcond; // 控制生产者线程
};

Task.hpp:实现生产者和消费者处理的数据的类。

#include <cstdlib>
#include <iostream>

class Task
{
public:
    Task()
    {}

    Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitcode(0)
    {}

    void operator()()//对传入数据进行操作
    {
        switch (_op)
        {
        case '+':
            _result = _x + _y;
            break;
        case '-':
            _result = _x - _y;
            break;
        case '*':
            _result = _x * _y;
            break;
        case '/':
            if (_y == 0) _exitcode = -1;
            else
                _result = _x / _y;
            break;
        case '%':
            if (_y == 0) _exitcode = -2;
            else
                _result = _x % _y;
            break;
        default:
            break;
        }
    }

    const std::string operatorArgs()//打印要处理的数据
    {
        return "(" + std::to_string(_x) + _op + std::to_string(_y) + ")" + "(" + std::to_string(_exitcode) + ")"; 
    }

    const std::string operatorRes()//打印数据处理结果
    {
        return  std::to_string(_x) + _op +  std::to_string(_y) + "=" + std::to_string(_result); 
    }

private:
    int _x;//左操作数
    int _y;//右操作数
    char _op;//操作符
    int _result;//算数结果
    int _exitcode;//退出码
};

main.cc:实现生产者消费者模型的文件。

#include "blockqueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <unistd.h>
#include <pthread.h>

void *consumer(void *args)//消费者方法
{
    blockqueue<Task> *bq = static_cast<blockqueue<Task>*>(args);
    while(true)
    {
        Task t;
        bq->pop(&t);//从阻塞队列中取出数据
        t();//对获取的数据进行处理
        std::cout << "consumer: " << t.operatorRes() << std::endl;
    }
}

void *productor(void* args)//生产者方法
{
    blockqueue<Task> *bq = static_cast<blockqueue<Task>*>(args);
    std::string ops = "+-*/%";
    while(true)
    {
        int x = rand() % 20;//生成数据
        int y = rand() % 10;
        char op = ops[rand() % ops.size()];
        Task t(x, y, op);
        bq->push(t);//将数据交给阻塞队列
        std::cout << "productor: " << t.operatorArgs() << "?" << std::endl;
    }
}

int main()
{   
    srand((uint32_t)time(nullptr) ^ getpid());//设置随机数
    blockqueue<Task>* q = new blockqueue<Task>;//创建阻塞队列
    pthread_t c[2];
    pthread_t p[3];
    pthread_create(&c[0], nullptr, consumer, q);
    pthread_create(&c[1], nullptr, consumer, q);
    pthread_create(&p[0], nullptr, productor, q);
    pthread_create(&p[1], nullptr, productor, q);
    pthread_create(&p[2], nullptr, productor, q);
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);
    return 0;
}

编译代码运行并查看结果:

image-20231001171758937

上述代码的整体逻辑是生产者线程产生数据交给阻塞队列,消费者线程从阻塞队列中获取数据并且其进行处理,生产者产生算数表达式,消费者对表达式进行运算。其中所有线程因为只有一个队列共用一把锁,所有线程都是互斥的,通过条件变量的等待和唤醒完了生产者和消费者的同步。

说明: 生产者消费者模型提高效率的原理是生产者在获取数据时,消费者可以从缓冲区中获取数据,消费者在处理数据时,生产者可以将数据传入缓冲区;一个消费者处理数据,另外的消费者可以从缓冲区中获取数据,一个生产者获取数据,另外的消费者可以将数据传入缓冲区,所有线程都可以并发执行,不会是串行化的消费者等待数据来,生产者等待消费者处理完数据。

线程同步控制–POSIX信号量

概念

POSIX信号量用于线程间同步操作,达到无冲突的访问共享资源目的。

POSIX信号量是Linux操作系统中提供的sem_t数据类型,通过对POSIX信号量的使用能够完成线程的同步控制。

信号量的本质就是一个计数器,记录临界资源的数目。 线程要访问临界资源需要进行P操作(申请资源),使用完了临界资源后要进行V操作(释放资源),信号量就是通过线程访问临界资源需要申请的操作来控制线程的同步的。PV操作是原子的。

信号量控制线程同步的机制: 将申请锁然后判断临界资源转换成了申请信号量。 信号量申请操作都在申请锁和临界资源操作前,当多个线程要访问到同一个资源时,只有申请到了信号量的资源可以进行申请锁和临界资源操作,其他线程只能等待信号量,保证了线程按照顺序执行,不会导致申请不到锁和申请到锁没有临界资源的饥饿问题。

sem_init函数

//sem_init函数所在的头文件和函数声明
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
  • sem_init函数用于初始化信号量。
  • sem参数: 要初始化的信号量。
  • pshared参数: pshared:0表示线程间共享,非零表示进程间共享。
  • value参数: 信号量初始值,即资源数目。

sem_wait函数

//sem_wait函数所在的头文件和函数声明
#include <semaphore.h>

int sem_wait(sem_t *sem);
  • sem_wait函数用于信号量的等待申请,也就是P操作。
  • sem参数: 要申请的信号量。

sem_post函数

//sem_post函数所在的头文件和函数声明
#include <semaphore.h>

int sem_post(sem_t *sem);
  • sem_post函数用于信号量的释放,也就是V操作。
  • sem参数: 要释放的信号量。

sem_destroy函数

//sem_destroy函数所在的头文件和函数声明
#include <semaphore.h>

int sem_destroy(sem_t *sem);
  • sem_destroy函数用于信号量销毁。
  • sem参数: 要销毁的信号量。

基于环形队列的生产消费模型

利用信号量做同步控制,并以环形队列作为缓冲区形成的生产者消费者模型中有如下特点:

  • 生产者关系的是空间资源,消费者关心的是数据资源。
  • 只要信号量不为0,表示资源可用,表示线程可以访问缓冲区资源。
  • 环形队列中只要生产者和消费者不访问同样的位置,生产者和消费者就可以同时操作。
  • 当环形队列为空或为满时,生产者消费者会访问同一区域,使用信号量控制具有可操作资源的线程先操作。

image-20231002152006068

编写代码体会基于环形队列的生产消费模型和信号量的运用:

ringqueue.hpp:实现循环队列文件:


#include <vector>
#include <semaphore.h>
#include <pthread.h>

using namespace std;

const int N = 5;

template<class T>
class ringqueue
{
private:
    void P(sem_t& sem) { sem_wait(&sem); }//P操作
    void V(sem_t& sem) { sem_post(&sem); }//V操作
    void Lock(pthread_mutex_t& mutex) { pthread_mutex_lock(&mutex); }//申请锁操作
    void UnLock(pthread_mutex_t& mutex) { pthread_mutex_unlock(&mutex); }//释放锁操作
public:
    ringqueue(int num = N):_cap(num), _ring(num), _c_step(0), _p_step(0)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, num);
        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }

    void push(const T& in)
    {
        P(_space_sem);//申请空间信号量
        Lock(_p_mutex);//对生产者锁申请锁
        _ring[_c_step++] = in;
        _c_step %= _cap;
        UnLock(_p_mutex);//对生产者锁释放锁
        V(_data_sem);//释放数据信号量
    }

    void pop(T* out)
    {
        P(_data_sem);//申请数据信号量
        Lock(_c_mutex);//对消费者锁申请锁
        *out = _ring[_c_step++];
        _c_step %= _cap;
        UnLock(_c_mutex);//对消费者锁释放锁
        V(_space_sem);//释放空间信号量
    }

    ~ringqueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);
        pthread_mutex_destroy(&_c_mutex);
        pthread_mutex_destroy(&_p_mutex);
    }
private:
    vector<T> _ring;
    int _cap;//队列容量
    sem_t _data_sem;//数据信号量--只有消费者关心
    sem_t _space_sem;//空间信号量--只有生产者关系
    int _c_step;//消费者访问位置
    int _p_step;//生产者访问位置
    pthread_mutex_t _c_mutex;//消费者互斥控制锁
    pthread_mutex_t _p_mutex;//生产者互斥控制锁
};

Task.hpp:实现生产者和消费者处理的数据的类。

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

class Task
{
public:
    Task()
    {}

    Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitcode(0)
    {}

    void operator()()//对传入数据进行操作
    {
        switch (_op)
        {
        case '+':
            _result = _x + _y;
            break;
        case '-':
            _result = _x - _y;
            break;
        case '*':
            _result = _x * _y;
            break;
        case '/':
            if (_y == 0) _exitcode = -1;
            else
                _result = _x / _y;
            break;
        case '%':
            if (_y == 0) _exitcode = -2;
            else
                _result = _x % _y;
            break;
        default:
            break;
        }
        usleep(100000);
    }

    const std::string operatorArgs()//打印要处理的数据
    {
        return "(" + std::to_string(_x) + _op + std::to_string(_y) + ")" + "(" + std::to_string(_exitcode) + ")"; 
    }

    const std::string operatorRes()//打印数据处理结果
    {
        return  std::to_string(_x) + _op +  std::to_string(_y) + "=" + std::to_string(_result); 
    }

private:
    int _x;//左操作数
    int _y;//右操作数
    char _op;//操作符
    int _result;//算数结果
    int _exitcode;//退出码
};

main.cc:实现生产者消费者模型的文件。

#include "ringqueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <cstring>

const char* ops = "+-*/%";

void *consumerRoutine(void *args)//消费者方法
{
    ringqueue<Task>* rq = static_cast<ringqueue<Task>*>(args);
    while(true)
    {
        Task t;
        rq->pop(&t);
        t();
        cout << "consumer done, 处理完成的任务是: " << t.operatorRes() << endl;
    }
}

void *productorRoutine(void *args)//生产者方法
{
    ringqueue<Task>* rq = static_cast<ringqueue<Task>*>(args);
    while(true)
    {
        int x = rand() % 100;
        int y = rand() % 100;
        char op = ops[(x + y) % strlen(ops)];
        Task t(x, y, op);
        rq->push(t);
        cout << "productor done, 生产的任务是: " << t.operatorArgs() << endl;
    }
}

int main()
{
    ringqueue<Task>* rq= new ringqueue<Task>();
    pthread_t c[3], p[2];

    for (int i = 0; i < 3; i++)
        pthread_create(c + i, nullptr, consumerRoutine, rq);
    for (int i = 0; i < 2; i++)
        pthread_create(p + i, nullptr, productorRoutine, rq);

    for (int i = 0; i < 3; i++)
        pthread_join(c[i], nullptr);
    for (int i = 0; i < 2; i++)
        pthread_join(p[i], nullptr);

    return 0;
}

编译代码运行并查看结果:

image-20231002191054980

利用信号量实现生产者消费者模型的意义: 多生产,多消费的时候,向队列放数据前并发构建Task,获取数据后多线程可以并发处理task,因为这些操作没有加锁!

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

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

相关文章

基于SpringBoot的体育馆场地赛事预约管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【生命周期】

生命周期 1 引出生命周期2 分析生命周期3 总结生命周期 1 引出生命周期 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta …

互联网Java工程师面试题·MyBatis 篇·第二弹

目录 16、Xml 映射文件中&#xff0c;除了常见的 select|insert|updae|delete标签之外&#xff0c;还有哪些标签&#xff1f; 17、Mybatis 的 Xml 映射文件中&#xff0c;不同的 Xml 映射文件&#xff0c;id 是否可以重复&#xff1f; 18、为什么说 Mybatis 是半自动 ORM 映射…

证书显示未受信任,生成的证书过期

此时若是导入证书后&#xff0c;证书显示未受信任&#xff0c;则说明我们缺失最新的AppleWWDRCA证书 解决方案&#xff1a; 重新下载AppleWWDRCA并安装。即下载最新的AppleWWDRCA证书&#xff0c;双击安装到“登录”项的钥匙串下&#xff1b;然后再安装你的开发证书或者发布证书…

云原生微服务 第六章 Spring Cloud Netflix Eureka集成远程调用、负载均衡组件OpenFeign

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 文章目录 系列文章目录前言1、OpenFeign的实现…

嵌入式Linux应用开发-驱动大全-第一章同步与互斥②

嵌入式Linux应用开发-驱动大全-第一章同步与互斥② 第一章 同步与互斥②1.3 原子操作的实现原理与使用1.3.1 原子变量的内核操作函数1.3.2 原子变量的内核实现1.3.2.1 ATOMIC_OP在 UP系统中的实现1.3.2.2 ATOMIC_OP在 SMP系统中的实现 1.3.3 原子变量使用案例1.3.4 原子位介绍1…

js正则的前瞻释义

/^(?a)aa$/可以匹配aa,但不能匹配aaa,因为前瞻不消耗字符串 检验密码强度的正则 /^(?.*[a-z])(?.*[A-Z])(?.*\d)(?.*[$,_.])[a-zA-Z\d$,_.]{6,12}$/var reg/^(?.*[a-z])(?.*[A-Z])(?.*\d)(?.*[$,_.])[a-zA-Z\d$,_.]{6,12}$/var res reg.test(abcdefg.A6)console.log(…

SpringBoot中使用Servlet和Filter

为什么要把Servlet和Filter写在一起,因为使用方式很相似 两种方式 第一种,使用Servlet和Filter 使用Servlet 继承HttpServlet 注册Servlet 使用Filter 1.自定义过滤器 2.注册过滤器 这里注意一点 使用/**无效 至少我这2.4.5版本是这样 过滤所有请求用/* 那么其实还有…

CodeCraft-21 and Codeforces Round 711 (Div. 2)A-F

1.Problem - A - Codeforces &#xff08;1&#xff09;题意 求一个大于等于n的整数x&#xff0c;满足gcd(x,sum(dig(x)) > 1&#xff0c;dig表示x的各个数位。 &#xff08;2&#xff09;思路 考虑最差是满足gcd(x,sum(dig(x)) 2,因此不会枚举很多&#xff0c;直接暴力枚…

【Diffusion】DDPM - (2)公式推导 之 前向扩散

1、加噪过程 1、将 图像 x 0 x_0 x0​ 像素值映射到 [-1, 1] 之间 x 255 2 − 1 , w h e r e    x 为图像中的像素值 \quad \frac{x}{255} \times 2-1, \quad where \; x 为图像中的像素值 255x​2−1,wherex为图像中的像素值 \quad 2、生成一张尺寸相同的噪声图片,像…

【Kafka专题】Kafka收发消息核心参数详解

目录 前置知识课程内容一、从基础的客户端说起&#xff08;Java代码集成使用&#xff09;1.1 消息发送者源码示例1.2 消息消费者源码示例1.3 客户端使用小总结 *二、从客户端属性来梳理客户端工作机制*2.1 消费者分组消费机制2.2 生产者拦截器机制2.3 消息序列化机制2.4 消息分…

二叉搜索树 , Set 和 Map (JAVA)

二叉搜索树 二叉搜索树又称二叉排序树&#xff0c;它具有以下性质的二叉树或空树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值它的每颗子树也分别为二叉搜索树 二叉搜索树的…

北京开发APP需要多少钱

北京开发一个移动应用&#xff08;APP&#xff09;的费用因多种因素而异&#xff0c;包括项目的规模、复杂性、所需功能、设计要求、技术选择、开发团队的经验和地理位置等。一般来说&#xff0c;北京的APP开发费用通常较高&#xff0c;因为这是中国的主要技术和创新中心之一&a…

【云备份项目】:环境搭建(g++、json库、bundle库、httplib库)

文章目录 1. g 升级到 7.3 版本2. 安装 jsoncpp 库3. 下载 bundle 数据压缩库4. 下载 httplib 库从 Win 传输文件到 Linux解压缩 1. g 升级到 7.3 版本 &#x1f517;链接跳转 2. 安装 jsoncpp 库 &#x1f517;链接跳转 3. 下载 bundle 数据压缩库 安装 git 工具 sudo yum…

嵌入式Linux应用开发-驱动大全-第一章同步与互斥①

嵌入式Linux应用开发-驱动大全-第一章同步与互斥① 第一章 同步与互斥①1.1 内联汇编1.1.1 C语言实现加法1.1.2 使用汇编函数实现加法1.1.3 内联汇编语法1.1.4 编写内联汇编实现加法1.1.5 earlyclobber的例子 1.2 同步与互斥的失败例子1.2.1 失败例子11.2.2 失败例子21.2.3 失败…

IDEA中的神仙插件——Smart Input (自动切换输入法)

推荐专栏&#xff1a;开发环境配置攻略 致力于记录学习过程中各种软件的安装及环境配置操作&#xff0c;并提供详细的步骤说明和丰富的配图。涵盖了 Java、Python、IntelliJ IDEA、Tomcat、MySQL 等常见开发工具和服务器组件的配置&#xff0c;为初学者提供一个实用、全面的配置…

Sentinel-2波段合成

Sentinel-2波段合成 在上一篇博客中下载了Sentinel-2数据&#xff0c;他有13个波段的.jp2文件&#xff0c;下面选取需要使用的波段进行合成。 导入了B2&#xff08;蓝色&#xff09;、B3&#xff08;绿色&#xff09;、B4&#xff08;红色&#xff09;、B8&#xff08;近红外&…

第一章 概论

第一章 概论 引言基本概念和术语数据、数据元素和数据项数据的逻辑结构数据的存储结构&#xff08;物理结构&#xff09;运算 算法及描述算法分析时间复杂度空间复杂度 牛刀小试 引言 数据结构是指一组相互之间存在一种或多种特定关系的数据的组织方式和它们在计算机内的存储方…

【Java 进阶篇】JDBC PreparedStatement 详解

在Java中&#xff0c;与关系型数据库进行交互是非常常见的任务之一。JDBC&#xff08;Java Database Connectivity&#xff09;是Java平台的一个标准API&#xff0c;用于连接和操作各种关系型数据库。其中&#xff0c;PreparedStatement 是 JDBC 中一个重要的接口&#xff0c;用…

ARM---实现1-100求和任务

.text .globl _start_start:mov r0, #0x1mov r1, #0x1 给r1加一固定1不变mov r2, #0x64 100判断bl sumcmp r1, r2 sum:addcc r1, r1,#0x1 r1自增addcc r0, r0, r1 r0求和movcc pc,lrstop:b stop.end