【线程】封装 | 安全 | 互斥

线程封装(面向对象)

1.组件式的封装出一个线程类(像C++11线程库那样去管理线程)

  • 我们并不想暴露出线程创建,终止,等待,分离,获取线程id等POSIX线程库的接口,我们也想像C++11那样通过面向对象的方式来玩,所以接下来我们将POSIX线程库的接口做一下封装,同样能实现像C++11线程库那样去管理我们的线程,这个类就像一个小组件似的,包含对应的.hpp文件就可以使用,使用起来很舒服。

在这里插入图片描述

//hpp文件
#pragma once
#include<iostream>
#include<pthread.h>
#include<string>
#include<functional>

using namespace std;

template<class T>
using fun_c = std::function<void(T)>;

template<class T>
class Thread
{
public:
    Thread(const string& threadname, fun_c<T> func, T data)
    :_threadname(threadname), _func(func), _data(data)
    {} 

    ~Thread()
    {
        ;
    }

    //线程执行的函数
    static void* ThreadRoutine(void* args)  //成员函数默认里面有一个this指针,而静态成员函数中没有
    {
        Thread* t = static_cast<Thread*>(args);
        t->_func(t->_data);
        return nullptr;
    }

    //线程创建-》running
    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
        if(n == 0) 
        {
            _isrunning = true;
            return true;
        }
        else return false;
    }

    bool Join()
    {
        if(!_isrunning) return true;
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }

    std::string ThreadName()
    {
        return _threadname;
    }

    bool IsRunning()
    {
        return _isrunning;
    }

private:
    string _threadname;
    pthread_t _tid = 0;
    bool _isrunning = false;
    fun_c<T> _func;
    T _data;
};

在这里插入图片描述

//.cc文件
#include <iostream>
#include "mythread.hpp"
#include <cstdio>
#include<unistd.h>

string GetThreadName()
{
    static int num = 1;
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "thread-%d", num++);
    return buffer;
}

void Print(int number)
{
    while (number)
    {
        std::cout << "hello world: " << number-- <<  std::endl;
        sleep(1);
    }
}

int main()
{
    Thread<int> t(GetThreadName(), Print, 10);
    std::cout << "is thread running? " << t.IsRunning() << std::endl;
    t.Start(); 
    std::cout << "is thread running? " << t.IsRunning() << std::endl;
    t.Join();  //线程阻塞等待
    // Thread t(GetThreadName(), Print);
    // std::cout << "is thread running? " << t.IsRunning() << std::endl;

    // t.Start();

    // std::cout << "is thread running? " << t.IsRunning() << std::endl;

    // t.Join();  //线程阻塞等待
    return 0;
}

线程互斥

多线程访问共享资源是不安全的

假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这是怎么回事呢?
其实问题产生就是由于多线程被调度器调度的特性导致的。

int ticket = 1000;  //票数
void route(string arg)
{
    while (1)
    {
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", arg.c_str(), ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}

int main()
{
    Thread<string> t(GetThreadName(), route, GetThreadName());
    Thread<string> t2(GetThreadName(), route, GetThreadName());
    Thread<string> t3(GetThreadName(), route, GetThreadName());
    Thread<string> t4(GetThreadName(), route, GetThreadName());
    
    
    t.Start(); 
    t2.Start(); 
    t3.Start(); 
    t4.Start(); 

    t.Join(); 
    t2.Join(); 
    t3.Join(); 
    t4.Join(); 
    return 0;
}

在这里插入图片描述

  • 了解上面的问题需要知道线程调度的特性,实际线程在被调度时它的上下文会被加载到CPU的寄存器中,而线程在被切换的时候,线程又会带着自己的上下文被切换下去,此时要进行线程的上下文保存,以便于下次该线程被切换上来的时候能够进行上下文数据的恢复。
    除此之外,像tickets- -这样的操作,对应的汇编指令其实至少有三条:1.读取数据 2.修改数据 3.写回数据,而线程函数我们知道会在每个线程的私有栈都存在一份,在上面的例子中多个线程执行同一份线程函数,所以这个线程函数就绝对会处于被重入的状态,也就绝对会被多个线程执行!今天我们假设只有一个CPU(CPU就是核心,处理器芯片会集成多个核心)在调度当前进程中的线程,那么线程是CPU调度的基本单位,所以也就会出现一个线程可能执行一半的时候被切换下去了,并且该线程的上下文被保存起来(线程在切换的时候,会将自己的上下文数据保存到线程的线程控制块TCB),然后CPU又去调度进程中的另一个线程。

在这里插入图片描述

在知道上面的原理之后,还需要知道usleep的作用,当usleep放到if分支语句的第一行时,票数就出现了问题,出现了负数,主要是因为usleep可以将线程暂时阻塞,那么CPU就会把他切换下去,转而执行其他线程,但需要注意的是,如果被切换的线程重新调度上来时,还会从上次他执行后的语句继续向下运行。
所以会出现多个线程同时进入到分支判断语句,然后去阻塞等待的情况,假设tickets已经变成了1,然后其余的线程此时都被调度上来了,他们都开始执行tickets- -,- -之后不满足循环条件线程才会退出,那么如果我们创建出了4个线程,就会有3个线程在票数已经为0的情况下继续减减,所以就会出现票数为负数的情况。

提出解决方案:加锁(局部和静态锁的两种初始化/销毁方案)

那该如何解决上面的问题呢?多个执行流操作共享资源时,发生了数据不一致问题。
解决上面的问题实际要通过加锁来实现,但在谈论加锁的话题之前,我们需要来重新看待几个概念。

  • 多个执行流总是能够共享许多资源,共享资源我们称为临界资源。
  • 而多个执行流执行的函数体内部,对临界资源进行操作的代码称为临界区,需要注意的是临界区不是整个函数体内部的代码,而是指对共享资源进行操作的代码称为临界区。
  • 如果我们想让多个执行流串行的访问临界资源,而不是并发或并行的访问临界资源,这样的线程调度方案就是互斥式的访问临界资源!(串行就是指只要一个线程开始执行这个任务,那么他就不能中断,必须得等这个线程执行完这个任务,你才能切换其他线程执行其他的任务,这个概念等会讲完锁之后大家就明白什么是互斥了)
  • 当线程在执行一个对资源访问的操作时,要么做了这个操作,要么没有做这个操作,只要两种状态,不会出现做了一半这样的状态,我们称这样的操作是原子性的
    在这里插入图片描述

有了上面四组概念的铺垫之后,我们来谈谈如何对共享资源进行加锁和解锁?
首先锁实际就是一种数据类型,这个锁就像我们平常定义出来的变量或是对象一样,只不过这个锁的类型是系统给我们封装好的一种类型,进行重定义后为pthread_mutex_t。
在这里插入图片描述
变量或对象在定义的时候也是可以初始化的,变量初始化后,就是变量的定义,而不是声明了。变量和对象也都有自己的销毁方案,内置类型的变量或者对象销毁时,操作系统会自动回收其资源或者自动调用析构函数,而自定义对象销毁时,操作系统会调用其析构函数进行资源的回收。

锁同样也是如此,锁也有自己的初始化和销毁方案,如果你定义的是一把局部锁,就需要用pthread_mutex_init()和pthread_mutex_destroy()来进行初始化和销毁,如果你定义的是一把全局锁或静态所,则不需要用init初始化和destroy销毁,直接PTHREAD_MUTEX_INITIALIZER进行初始化即可,它有自己的初始化和销毁方案,我们无须关心静态或全局锁如何销毁。在这里插入图片描述

定义好锁之后,我们就可以对某一段代码进行加锁和解锁,加锁与解锁意味着,这段代码不是一般的代码,只有申请到锁,持有锁的线程才能访问这段代码,加锁和解锁之间的代码可以称为临界区,因为想要访问这段空间必须有锁才可以访问。pthread_mutex_lock实际就是申请锁的代码和临界区的入口,如果你申请锁成功了,那么你就可以进入临界区访问临界资源,如果你并没有申请成功,比如当前这把锁已经被别的线程申请到并持有了,其他线程正持有锁在临界区访问着呢,那么你就无法进入临界区,因为你并没有持有锁,必须得在pthread_mutex_lock阻塞等待,直到你申请到锁之后,你才能进入临界区访问临界资源,这样的线程访问实际就是互斥,指的是当一个线程正在持有锁访问临界区的时候,其他线程无法进入临界区,直到持有锁的线程释放锁之后才会有可能进入临界区,注意是有可能,因为当线程释放锁之后,这把锁还需要被竞争,哪个线程竞争到这把锁,哪个线程才能持有锁的访问临界资源!在这里插入图片描述

在这里插入图片描述

上面谈论完锁的初始化和销毁,以及如何加锁和解锁之后,我们来利用锁解决上面出现的共享资源访问不安全的问题。你不是由于多线程再进行临界资源访问时,可能由于线程切换什么的,导致非原子性式的访问临界资源吗?那我不让你这么干,我对这段临界资源进行加锁,让你当前申请到锁正在访问临界资源的线程,必须给我以原子性的访问来访问临界资源,换句话说,你必须把访问临界资源的工作做完了,才可以,要么你不要访问临界资源,要么你访问了临界资源,就必须把临界资源全部访问完了,中间不能访问一半就不访问了!所以只要对临界资源进行加锁后,临界资源就变得安全了,因为无论什么线程想要访问临界资源,都必须以原子性的方式访问完,这样的话,就不会出现在访问一半的时候,线程被切换下去了,其他线程被切换上来继续访问临界资源了,而是说如果持有锁的线程被切换下去了,这个线程会抱着他申请到的锁被切换下去,此时其他线程如果被切换上来,想要访问临界资源,那也没用,因为你没有锁啊!持有锁的线程被切换时,是抱着锁被切换的,那你现在既然访问不了临界区,CPU无法继续执行代码,那就只能等持有锁的线程重新被切换上来时,才能继续开展临界资源的访问工作,这个工作必须且只能由申请到锁的线程来完成,其他任何线程都无法完成这个工作!反过来说,这不就是原子性吗?访问临界资源的工作只要被持有锁的线程开始做了,哪怕他在做的过程中被切换下去了,也无须担心,因为别的线程做不了这个工作,所以还是得等持有锁的线程被切换上来的时候才能继续做这个工作,那是不是这个工作只要开始做了,就一定会被做完呢?会不会出现做一半,停下来了不做了,让别的线程在去访问临界资源的情况呢?当然不会!这就是锁带来的作用。

在这里插入图片描述
如果在加锁之后运行代码,实际可以发现他抢票的速度是要比没加锁之前慢的,原因也很简单。我来给大家解释一下,没加锁之前,线程之间是可以并发或并行执行的,我先大概说一下并发和并行是什么,后面会详细介绍这两者的区别和概念,并发你可以简单理解为,当线程运行一半被切换下去的时候,此时CPU还可以调度运行其他线程,也就是说,如果多个线程在运行的时候,每个线程都会被CPU跑一跑,那在一段时间内,所有的线程都可以被执行到,并且推进每个线程的执行过程。而并行就是在多个核心上面同一时刻跑不同的线程,比如两个同时访问临界资源的线程,在未加锁的时候,可能出现多个核心同时执行两个线程的代码,同时在访问临界资源,但实际这种情况并不常见,因为我们写出来的代码优先级并没有那么高,所以基本上都是在按照并发执行的。
然后加锁前是并发执行的,也就是说在一个线程被切换下去的时候,其他- -tickets的线程还能够被重新调度上来进行票数的- -,那么总体上来说,票数就会被一直- -。
而加锁之后就不是并发执行的了,因为我们上面说过,加锁之后即使持有锁的线程被切换下去,其他被调度到CPU上的线程也是无法进行票数- -的,因为他们没有锁,所以在持有锁的线程被切换下去的这段时间里,票数不会改变,因为线程在串行的访问临界资源,什么是串行呢?就是一个线程访问完之后,才能轮到另一个线程,就是我们前面说的,一个线程在完成他的工作之后,释放完锁之后,其他线程才有可能竞争到锁,才有可能访问临界资源,这样就是串行。
串行的执行效率肯定要比并发执行的效率底嘛,因为当多线程在执行任务的时候,我们进行并发执行,为的就是当前线程如果被切换下去了,那也没啥事,因为其他被调度上来的线程依旧可以执行这个任务。你现在加锁之后就会变成串行执行了,那当前持有锁的线程被切换下去时,其他被调度上来的线程是无法继续执行任务的,效率自然就会底一些。(效率底一点就底一点吧,毕竟现在共享资源就安全了嘛,下面运行结果你也可以看到,没有锁的时候,票数就为负数了,这种情况用户怎么可能容忍。)
在这里插入图片描述

局部和全局锁的两种加锁方案的代码实现

如果定义局部锁的话,我们肯定是想要将这把锁传给每个线程的,让每个线程都用这把锁来互斥式的访问共享资源,以此来保证共享资源的安全性。

//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  //初始化一把全局的锁
int ticket = 1000;  //票数
void route(pthread_mutex_t* mutex)
{
    while (1)
    {
        pthread_mutex_lock(mutex);  //上锁
        if (ticket > 0)
        {
            usleep(1000);
            printf("sells ticket:%d\n", ticket);
            ticket--;
            pthread_mutex_unlock(mutex);

        }
        else
        {
           pthread_mutex_unlock(mutex);
            break;
        }
    }
}

int main()
{
    pthread_mutex_t mutex;  
    pthread_mutex_init(&mutex, nullptr); //初始化一把局部的锁

    Thread<pthread_mutex_t*> t(GetThreadName(), route, &mutex);
    Thread<pthread_mutex_t*> t2(GetThreadName(), route, &mutex);
    Thread<pthread_mutex_t*> t3(GetThreadName(), route, &mutex);
    Thread<pthread_mutex_t*> t4(GetThreadName(), route, &mutex);
    
    t.Start(); 
    t2.Start(); 
    t3.Start(); 
    t4.Start(); 

    t.Join(); 
    t2.Join(); 
    t3.Join(); 
    t4.Join(); 
    pthread_mutex_destroy(&mutex); //释放锁
    return 0;
}

全局的锁定义并且初始化以后,每个线程都可以看得见,因此也是共享资源

如何看待锁

完成上面对于共享资源访问不安全问题的解决之后,我们来深入的理解一下锁。
我们知道,共享资源在被多线程访问时,是不安全的,所以我们需要加锁来保护共享资源。但是我们回过头来想一想,锁本身是不是共享资源呢?所有的线程都需要申请锁和释放锁,那不就是在共同的访问锁这个资源嘛?所以锁本身不就是共享资源吗?那多个线程在访问锁这个共享资源的时候,锁本身是不是需要被保护呢?当然需要!其他的共享资源可以通过加锁来进行保护,那锁怎么办呢?
实际上,加锁和解锁的过程是原子的!也就是说只要你申请了锁,并且竞争能力恰好足够,那么你就一定能够拿到这个锁,否则你就不会拿到这个锁,不会说在申请锁申请一半的时候,线程被切换下去了,其他线程去申请锁了,不会出现这种中间态的情况!既然加锁和解锁的过程是原子的,那其实访问锁就是安全的!(但加锁解锁的过程为什么是原子的呢?我该如何理解呢?这个后面会说。)
在这里插入图片描述
在这里插入图片描述

RAII风格的封装设计锁?(构造函数加锁,析构函数解锁)

如果我们想简单的封装使用锁,那我们该如何设计呢?我们也想像之前封装设计线程那样搞出来C++式的面向对象版的创建线程和销毁线程。
实际实现起来也很简单,无非就是对原生的申请锁,加锁,解锁接口的封装!我们先定义一个互斥量的类,类中实现构造函数将锁的地址进行初始化,然后定义出加锁和解锁的两个接口,这样就可以定义出来一个内部能够进行加锁和解锁的类。
然后我们再加一层封装,实现出RAII( Resource Acquisition Is Initialization)风格的加锁,即为构造函数处进行加锁,析构函数处进行解锁!
至于锁的初始化和销毁方案,是类外面的事情,使用时需要自己先初始化好一把锁,确定初始化和销毁的方案,然后利用LockGuard.hpp这个小组件来进行加锁和解锁的过程!

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

可重入函数和线程安全

在多线程并发执行代码,同时访问共享资源的时候,如果某一个共享资源由于多线程访问,发生了数据不一致,共享资源不安全,并且导致其他线程运行出问题了,那么这种情况就是线程不安全的。尤其对于没有锁保护的共享资源的多线程访问的代码,很大概率出现线程不安全的情况。
而什么是可重入呢?这个话题并不陌生,我们之前谈论进程信号的时候,进程可能由于收到信号,并且在陷入内核时检测到信号,跳转到handler方法执行信号处理函数,信号处理函数中可能会出现和main执行流中执行相同的函数体,例如当时我们所说的链表的push_back在main和handler中同时执行,可能会导致某些未知错误的产生,如果出现了问题,那么我们称这个函数是不可重入函数,如果没有出现问题这个函数就是可重入函数。值得注意的是,不可重入函数说的是这个函数的属性,而不是说这个函数叫做不可重入函数,那么他就一定不能被执行流所重入,只是说,他如果被执行流重入,极大概率是要出问题的。
在这里插入图片描述

在这里插入图片描述

死锁

死锁是指一个进程中的各个线程,都持有着锁,但同时又去申请其他线程的锁,而每个线程持有的锁都是占有不会释放的,所以大家都会等着,等对方先释放锁,但是呢,大家又都不释放锁,全都占有着锁,所以大家就会处于一种永久等待的状态,也就是永久性的阻塞状态,所有执行流都不会被运行,这样的问题就是死锁!
之前抢票的代码中,多个线程使用的是同一把锁,未来有些场景一定是要使用多把锁的,在多把锁的情况下,如果某些线程持有锁不释放,还要去申请其他线程正持有的锁,而每个线程都是这样的状态,那就是死锁问题。

产生死锁的四个必要条件
1.互斥条件:一个资源每次只能被一个执行流使用,互斥其实就是加锁之后线程的串行执行。
2.请求与保持条件:一个执行流由于请求资源而阻塞时,对自己已经获得的资源保持不放。说白了就是我自己的东西不释放,我还要你的东西,你不给我就一直等,等到你给我为止。
3.不剥夺条件:一个线程在未使用完自己获得的资源之前,是不能够强行剥夺其他线程的资源的。说白了就是你先在还有资源呢,你想要别人的自由你就得等,不能强行剥夺!当你使用完自己的资源后,你可以去等待申请别人的资源。总之就是不能强行剥夺其他线程的资源,想要就必须阻塞等待别人释放资源才可以。
4.循环等待条件:若干个执行流之间,形成一种头尾相接的互相等待对方资源的关系。我们也称这样的现象为环路等待。


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

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

相关文章

机器学习模型—K最近邻(KNN)

机器学习模型—K最近邻(KNN) K最近邻 (KNN) 算法是一种用于解决分类和回归问题的监督机器学习方法。Evelyn Fix 和 Joseph Hodges 于 1951 年开发了该算法,随后 Thomas Cover 对其进行了扩展。本文探讨了 KNN 算法的基本原理、工作原理和实现。 虽然 k近邻算法 (KNN) 可以用…

基于Pytest+Allure+Excel的接口自动化测试框架

1. Allure 简介 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具&#xff0c;它不仅以 Web 的方式展示了简介的测试结果&#xff0c;而且允许参与开发过程的每个人可以从日常执行的测试中&#xff0c;最大限度地提取有用信息。 Allure 是由 Java 语言开发…

接口自动化测试实战之pytest框架+allure讲解

一、前言 本文章主要会讲解Python中pytest框架的讲解&#xff0c;介绍什么是pytest、为何要测试、为何使用以及参考和扩展等等&#xff0c;话不多说&#xff0c;咱们直接进入主题哟。 二、pytest讲解 2.1 什么是pytest&#xff1f; pytest是一款单元测试框架&#xff0c;在…

Linux -- 线程互斥

一 线程互斥的概念 大部分情况&#xff0c;线程使用的数据都是局部变量&#xff0c;变量的地址空间在线程栈空间内&#xff0c;这种情况&#xff0c;变量归属单个线程&#xff0c;其他线程无法获得这种变量。但有时候&#xff0c;很多变量都需要在线程间共享&#xff0c;这样的…

激光打标技术:现代制造业的精准标记解决方案

随着科技的飞速进步&#xff0c;激光打标机技术已经成为现代制造业中不可或缺的一部分。作为一种快速、精确、耐用的标记解决方案&#xff0c;激光打标技术以其独特的优势&#xff0c;为现代制造业提供了精准、高效、持久的标记解决方案。 首先&#xff0c;激光打标技术以其无与…

吴恩达机器学习笔记 十七 通过偏差与方差诊断性能 正则化 偏差 方差

高偏差&#xff08;欠拟合&#xff09;&#xff1a;在训练集上表现得也不好 高方差&#xff08;过拟合&#xff09;&#xff1a;J_cv要远大于J_train 刚刚好&#xff1a;J_cv和J_train都小 J_cv和J_train与拟合多项式阶数的关系 从一阶到四阶&#xff0c;训练集的误差越来越小…

挂耳式耳机什么牌子的好?掌握六大挂耳式耳机选购秘诀

随着科技的进步&#xff0c;蓝牙耳机逐渐成为人们日常生活中的热门配件。很多人选择蓝牙耳机&#xff0c;是为了在娱乐学习时享受便捷的无线体验。这些耳机不仅设计时尚&#xff0c;佩戴起来也极为舒适。 蓝牙耳机主要分为挂耳式和入耳式。尽管入耳式耳机功能齐全&#xff0c;…

在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?

目录 一、分布式寻址算法 1. hash 算法 2. 一致性 hash 算法 3. Redis cluster 的 hash slot 算法 二、Redis cluster 的高可用与主备切换原理 1. 判断节点宕机 2. 从节点过滤 3. 从节点选举 4. 与哨兵比较 一、分布式寻址算法 hash 算法(大量缓存重建) 一致性 hash…

Python的time模块与datetime模块大揭秘!

1.time 模块 t主要用来操作时间&#xff0c;还可以用于控制程序 导入time模块 import time 2.获取从1970年1月1日0时0分0秒距今的秒数&#xff1a;time.time() print(time.time()) 3.格式化显示时间&#xff1a;time.strftime() print(time.strftime("%Y-%m-%d %H:…

某赛通电子文档安全管理系统 DecryptApplication 任意文件读取漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

使用 Python+Selenium + 第三方库实现简单的web自动化测试框架 源码

一、配置(config) 1.1 说明 设置自动化案例运行时的属性值。 安排自动化案例的执行顺序。 所在路径&#xff1a; …\Project_Selenium\config 1.2 文件 1.2.1 config.ini 目录&#xff1a; …\Project_Selenium\config\config.ini 配置字段&#xff1a; 1 [PROJECT] 1.1 bro…

【C++那些事儿】深入理解C++类与对象:从概念到实践(下)| 再谈构造函数(初始化列表)| explicit关键字 | static成员 | 友元

&#x1f4f7; 江池俊&#xff1a;个人主页 &#x1f525; 个人专栏&#xff1a;✅C那些事儿 ✅Linux技术宝典 &#x1f305; 此去关山万里&#xff0c;定不负云起之望 文章目录 1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit 关键字 2. static成员2.1 概念…

十四、Nacos源码系列:Nacos配置发布原理

目录 一、简介 二、加密处理 三、发布配置 3.1、插入或更新配置信息 3.2、发布配置数据变动事件 3.2.1、目标节点是当前节点 3.2.2、目标节点非当前节点 四、总结 一、简介 一般情况下&#xff0c;我们是通过Nacos提供的Web控制台登录&#xff0c;然后通过界面新增配置…

个人博客系列-后端项目-用户注册功能(7)

介绍 用户注册API的主要流程&#xff1a;1.前端用户提交用户名&#xff0c;密码 2. 序列化器校验用户名&#xff0c;密码是否合法。3.存入数据库。4.签发token 创建序列化器 from rest_framework import serializers from rest_framework_simplejwt.serializers import Toke…

图【数据结构】

文章目录 图的基本概念邻接矩阵邻接表图的遍历BFSDFS 图的基本概念 图是由顶点集合及顶点间的关系组成的一种数据结构 顶点和边&#xff1a;图中结点称为顶点 权值:边附带的数据信息 路径 &#xff1a; 简单路径 和 回路&#xff1a; 子图&#xff1a;设图G {V, E}和图G1…

计算机网络:关键性能指标与非性能特征解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

VR文化旅游虚拟现实介绍|虚拟现实元宇宙|VR设备购买

虚拟现实&#xff08;VR&#xff09;技术正在改变我们对文化旅游的认知和体验。通过VR技术&#xff0c;人们可以身临其境地探索世界各地的文化遗产和旅游景点&#xff0c;无需亲临现场也能感受到逼真的体验。以下是VR文化旅游虚拟现实的介绍&#xff1a; 身临其境的体验&#x…

c++之旅——第六弹

大家好啊&#xff0c;这里是c之旅第六弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一,静态成员&…

安装Mysql和Mycli插件

一、安装数据库 1.重定向生成配置文件 cat >/etc/yum.repos.d/mysql.repo <<EOF [mysql57-community] nameMySQL 5.7 Community Server baseurlhttp://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/ enabled1 gpgcheck0 EOF 2.yum安装 yum -y install mysq…

eclipse导入项目出现中文乱码

eclipse导入java项目的时候有时会出现乱码问题&#xff0c;很苦恼&#xff0c;网上找了很多方法都没用&#xff0c;所以得自己记录一下。导入项目可参考链接 eclipse中导入java项目-CSDN博客 1、点击 Windows --> Pereferences 2、依次点击下图内流程 3、看到下面的就修改成…
最新文章