【Linux下】 线程同步 生产者与消费者模型

news2024/11/22 15:31:37

文章目录

  • 【Linux下】 线程同步 生产者与消费者模型
    • 线程同步
      • 同步概念与竞态条件
      • 条件变量
        • 条件变量本质
      • 操作条件变量
        • 初始化和销毁条件变量
        • 等待
        • 唤醒
      • 通过条件变量实现的简单线程同步例子
        • 为什么pthread_cond_wait需要互斥锁
        • 条件变量使用规范
    • 生产者与消费者模型
      • 生活中的生产者与消费者模型: 消费者 -- 超市--厂商
      • 321 理解生产者与消费者模型
      • 基于堵塞队列实现生产者与消费者模型

【Linux下】 线程同步 生产者与消费者模型

线程同步

同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步;同步也可以理解为解决线程饥饿问题的一种机制

**竞态条件:**因为时序问题,而导致程序异常,我们称之为竞态条件。


条件变量

理解:

条件变量就是某种临界资源所处状态的一种数据化表示 ,通过条件变量,就可知道该临界资源是否就绪

举个例子帮助理解条件变量:

例:

小王知道最新的iphone手机出来了,就打算去iphone手机店看看有没有货;于是小王就跑到手机店去问柜员,而柜员告诉他没有到货,小王很想要新出的iphone,所以小王要想知道iphone什么时候到货,很明显有俩种方法:1. 每天跑到手机店去询问柜员 2. 将自己的电话留给柜员,等货到了,就让柜员通知小王

显而易见,肯定是第二种方法更加省时省力;而第一种情况就像只有互斥锁的情况下,线程获取临界区资源状态的方法(即,每次线程都得获取互斥锁,然后进入临界区,访问临界资源,查看临界资源是否就绪,不就绪又将互斥锁释放,离开临界区);第二种方法就是,使用条件变量后的线程获取临界资源状态的方法,即线程进入临界区后,检测到条件未就绪时,就直接在对应的条件变量处进行等待(本质是将自己挂起),而当临界区资源就绪了即条件满足了,该线程就会被唤醒–而临界资源肯定不可能是自己就绪的,一定是其他线程对临界资源进行操作,使得条件满足了,而后以某种方式唤醒了等待的线程

我们再将例子扩张一下,我们知道想要iphone手机的肯定不止小王一人,所以肯定有很多人给柜台留了“电话”–(也就是会有很多线程在条件变量下等待),而柜台为了每个人都能买到iphone手机,所以就不可能在手机到货时只给一个人打电话,而是尽量给每个人打电话,让每个人都买得到iphone手机

而从上面我们大概能知晓,条件变量实现线程间同步的原理–通过条件变量,一次通知一个线程,让每个线程都能享受到临界资源,而不是让一个线程或者几个线程霸占临界资源–也即,不管线程的竞争锁能力强还是弱,先进入临界区访问临界资源的线程竞争锁能力强),检测到临界资源状态不就绪,就会先到对应的条件变量下等待,后进入临界区的线程,也是如此;即从一开始通过竞争锁能力来争取资源,变成了集体在条件变量下等待资源,也即形成了线程间同步

注:查询临界资源状态本质上也是一种访问临界资源的行为 ,因为只有进入临界区之后,我们才能知道临界资源的状态,

条件变量本质

条件变量底层简化理解,其实可以理解为c语言中的一个结构体

抽象理解图

status:为1 则表示临界资源状态就绪 ,为0即表示临界资源状态为就绪(逻辑理解)

即信号变量里面实际上维护了一个等待队列的,等待该条件变量就绪的线程的控制块就会在该队列里;所以通过条件变量实现线程间同步的本质就是,线程通过在条件变量中等待资源,而不是通过竞争锁能力强获取资源

所以条件变量是有俩种行为的:1. 等待 – 即线程等待指定条件变量(实质是在该条件变量中的等待队列里) 2. 唤醒 --(条件就绪,等待队列里的线程被唤醒)

小结:

  • 通过条件变量可以得知临界资源的就绪状态
  • 条件变量是有俩种行为的:等待,和通知(唤醒)
  • 条件变量也是临界资源 ,线程只有进入了临界区才能访问到

操作条件变量


初始化和销毁条件变量

初始化和销毁

phread_cond_init

int pthread_cond_init(pthread_cond_t *__restrict__ __cond, const pthread_condattr_t *__restrict__ __cond_attr)

**作用:**初始化条件变量

参数:

  • cond: 条件变量名

  • attr: 基础属性,一般设为空即可

pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *__cond)

**作用:**销毁信号变量

参数

  • cond:所需要销毁的信号量名

等待

pthread_cond_wait

int pthread_cond_wait(pthread_cond_t *__restrict__ __cond, pthread_mutex_t *__restrict__ __mutex)

作用:

1.调用该函数会自动将锁释放掉,而后将自己挂起,允许其他线程抢夺锁,等待被其他线程唤醒(当条件满足时)–自动释放锁的原因是:如果不释放锁将自己挂起,就会造成死锁问题,导致其他线程一直在等不可能得到的资源

2.重新抢夺到锁之后,该函数才返回–线程本身是在临界区中被挂起的,所以醒来时就在临界区里,所以该线程不持有互斥锁显然是不合理的

参数:

  • cond: 所需等待的条件变量
  • mutex: 访问临界资源的互斥锁

返回值:

成功返回0,失败返回错误码


唤醒

pthread_cond_signal

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

作用: pthread_cond_signal的作用是唤醒正在对应条件变量的等待队列里的第一个线程;pthread_cond_broadcast的作用是唤醒等待队列里的所有线程 --本质都是将对应的信号量中的状态由0置1

参数

  • cond: 就绪状态的条件变量

返回值:

成功返回0,失败返回错误码


通过条件变量实现的简单线程同步例子

例:实现一个boss控制3个员工工作的代码,员工等待老板的发号进行工作

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

pthread_cond_t cond;
pthread_mutex_t mtx;

#define NUM 3

void * ctlworker(void * args)
{
    while(1)
    {
        cout<<"worker begin working"<<endl;
        pthread_cond_signal(&cond);
        sleep(1);
    }
}
void * work(void *args)
{
    int id=*(int *)args;
    delete (int*)args;
    while(1)
    {
        pthread_cond_wait(&cond,&mtx);
        cout<<"worker["<<id<<"] are working ..."<<endl;
        sleep(1);
    }
}

int main()
{
    pthread_cond_init(&cond,nullptr);
    pthread_mutex_init(&mtx,nullptr);

    pthread_t boss;
    pthread_t worker[NUM];
    pthread_create(&boss,nullptr,ctlworker,nullptr);
    for(int i=0; i< NUM ; i++)
    {
        int* id=new int (i);
        pthread_create(&worker[i],nullptr,work,(void*)id);
    }

    //线程等待
    pthread_join(boss,nullptr);
    for (int i=0 ;i< NUM ;i++)
    {
        pthread_join(worker[i],nullptr);
    }

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mtx);

    return 0;
}

运行结果:

我们发现员工工作的次序是固定的即0-2-1 ;本质上就是因为条件变量中的等待对列,而一个员工完成一次打印之后,是会继续到该队列的队尾继续进行等待的

为什么pthread_cond_wait需要互斥锁

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作**,改变共享变量,使原先不满足的条件变得满足**,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据
  • 线程进入临界区是需要获取互斥量的,进入到临界区之后,才能查询临界区资源就绪状态,而查询到该资源未就绪,就会在该条件变量下挂起等待,而此时我们的线程是带着锁的,如果不将锁释放掉就会造成死锁问题
  • 所以phread_cond_wait的作用还有,调用该函数的线程在被挂起之前,会自动将锁释放掉;
  • 而当该函数返回时,因为线程是在临界区被挂起的,所以线程被唤醒时是需要携带锁的,因此pthread_cond_wait函数只有在获得锁之后才会返回

总结:

  • 条件变量是用来完成线程间同步的,而互斥量是完成线程间互斥的,条件变量需要配合互斥量来使用

条件变量使用规范

错误示范

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后,pthread_ cond_ wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_ cond_ wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_ cond_ wait。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量释放掉,允许其他线程申请互斥量,而后将当前线程挂起,直到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);

小结:

  • 条件变量的”等待“设置在临界区(加锁和解锁的代码之间)之外,可能会导致线程错过条件变量的就绪情况,错过这么一次被**”唤醒“的机会**
  • 条件变量需要配合互斥锁使用,“唤醒”和“等待”的代码逻辑都应该在临界区内

生产者与消费者模型

生活中的生产者与消费者模型: 消费者 – 超市–厂商

在现实生活中,我们普通消费者购买商品都是去超市和商店(或者是网上超市);而为什么不是跑到生产商品的工厂去购买商品呢? --这是人类社会的进步,早期的主流肯定是消费者和生产商直接进行交互的;而消费者和生产商直接交互的效率太低了(例如:我们现在为了买几根火腿肠然后跑到生产火腿肠的工厂去购买,这合理吗,这显然是不合理的,再者,工厂一般都分布在比较偏远的地方,消费者直接跑到工厂的成本太大),于是商店就诞生了;商店可以更高效的服务消费者,更好协调生产商和消费者之间的关系(商店收集用户需求,然后向对应的厂商拿货,并且一般的商店都会有存货,即使生产商的生产某些步骤出了问题,商店短时间内也不会受到影响,消费者短时间内的消费需求也不会受到影响);

也就是说超市实际上是社会发展的产物,是用来提高消费者和生产商之间交互的效率的…;

超市的好处:

  • 将生产和消费的过程进行解耦:即消费者并不需要关系生产者的生产情况,只需购物就好了,生产者并不需要关心消费者的消费情况,因为有超市做了隔离层(理想情况下)
  • 收集消费者的需求,为消费者提供更好的服务

321 理解生产者与消费者模型

而我们也知道,计算机上的代码实际上就是我们现实生活中的映射,计算机就是为了提高人们的生活质量而在不断发展的;

计算机世界里的生产者与消费者模型

人的角色转化到计算机世界里,可以看作是一个一个线程(CPU的最小调度单位),所以在计算机世界里的生产者和消费者实际上就是一个个线程

逻辑图理解:

转化到计算机世界的角度

消费者的角色就由一个个消费资源的线程来充当,生产者的角色就由一个个生产“某些资源”的线程充当,而超市实际上就是磁盘中的一段内存(而内存呈现的方式不一(以不同的数据结构呈现),就形成不同场景下的生产消费模型例如:以队列的方式:堵塞队列,环形队列,以链表的方式呈现…)

而我们知道该**“超市”一定是能被所有的生产者和消费者所看到的,所以“超市”就一定是临界资源**

而我们也知道线程和线程之间是存在互斥和同步…等关系,所以计算机世界中的消费者和消费者生产者和生产者,以及生产者和消费者之间肯定是存在一定的关系的

  • 消费者和消费者的关系:(现实当中的竞争关系)–线程之间的互斥关系; 解释:因为超市是临界资源,为了保证线程安全问题,一次只能有一个消费线程到“超市”进行消费,所以消费线程之间是互斥的
  • 生产者与生产者的关系:(现实当中的竞争关系)–线程之间的互斥关系;解释:同上面一样,每次只允许一个生产线程进入临界区放置商品,所以生产线程之间是互斥的
  • 生产者与消费者的关系线程之间的同步与互斥关系 ;解释:互斥–生产者和消费者本质上都是线程,在访问临界资源时,俩者肯定时存在互斥关系(如果一个消费线程在临界区进行消费的同时,另一个生产线程正在临界区进行生产的工作,就可能会出现线程安全问题);同步– 只有生产线程生产出资源了,消费线程才有资源消费;而通常“超市”都是有大小限制的,即生产线程将“超市”塞满了,生产线程继续生产就没有意义了,所以需要生产线程停下来等待消费线程进行消费,只有消费者线程将资源消费了,生产线程继续生产才有意义,不然就是资源浪费,所以生产线程与消费线程之间需要同步关系。

而将生产者与消费者模型抽象出来,其实可以使用‘321’去帮我们理解和记忆它:

  • 3种关系:
    • 消费者与消费者:互斥
    • 生产者与生产者:互斥
    • 生产者与消费者:互斥与同步
  • 俩种角色:
    • 生产者
    • 消费者
  • 一个“交易场所”(“超市”)
    • 通常是以一段内存的形式的呈现

基于堵塞队列实现生产者与消费者模型

实现思路:

  • 使用互斥锁和信号变量维护三种关系

注:下面只展示了单生产者与单消费者模型,多生产者与多消费者模型只需增加生产和消费线程即可

运行主逻辑代码:

#include "BlockQueue.hpp"
#include <unistd.h>
#include <ctime>
#include <iostream>
using namespace Lsh_CP;


void *comsumer(void *bq)
{
    //comsumer producer 处必须使用指针 才能保证俩个函数访问的队列是同一对列
    BlockQueue<int>* c=(BlockQueue<int>*)bq;
    while(1)
    {
        int data;
        c->Pop(&data);
        std::cout<<"买家正在消费商品"<<data<<std::endl;
        //sleep(1);
    }
}
void *producer(void *bq)
{
    BlockQueue<int>* p=(BlockQueue<int>*)bq; 
    while(1)
    {
        int good=rand()%20+1;
        p->Push(good);
        std::cout<<"生产者生产了商品"<<good<<std::endl;
        sleep(1);
    }
}
int main()
{
    srand((unsigned)time(nullptr));
    BlockQueue<int>* queue=new BlockQueue<int>;
    pthread_t c,p;

    //创建线程
    pthread_create(&c,nullptr,comsumer,(void*)queue);
    pthread_create(&p,nullptr,producer,(void*)queue);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);

    delete queue;
    return 0;
}

堵塞队列实现代码:

#pragma once 
#include <queue>
#include <iostream>
#include <pthread.h>

namespace Lsh_CP
{
    //const T& 输入型参数 T* 输出型参数
    const int de_cap=5;

    template<class T>
    class BlockQueue
    {
    private:
        std::queue<T> _bq;
        pthread_cond_t is_full; //容量为满时,就是消费者苏醒的信号
        pthread_cond_t is_empty; //容量为空时,就是生产者苏醒工作的时候

        pthread_mutex_t mutex; //互斥量,保证临界资源的原子性,
        int _cap; //当前容量
        
    private:
        bool Is_full()
        {
            return _bq.size()==_cap;
        }
        bool Is_empty()
        {
            return _bq.size()==0;
        }
        void wake_producer()
        {
            pthread_cond_signal(&is_full);
        }
        void wake_comsumer()
        {
            pthread_cond_signal(&is_empty);
        }
    public:
        BlockQueue(int capacity=de_cap):_cap(capacity)
        {
            pthread_mutex_init(&mutex,nullptr);  
            pthread_cond_init(&is_full,nullptr);
            pthread_cond_init(&is_empty,nullptr);
                  
        }
        ~BlockQueue()
        {
            pthread_mutex_destroy(&mutex);
            pthread_cond_destroy(&is_full);
            pthread_cond_destroy(&is_empty);
           
        }
        
        void Push(const T& in)
        {
            //临界区
            pthread_mutex_lock(&mutex);
            if(Is_full())  //判断是否需要生产者生产
            {
                pthread_cond_wait(&is_full,&mutex);
            }
            _bq.push(in);

            //唤醒消费者
            if(_bq.size()>_cap/2) wake_comsumer();
            pthread_mutex_unlock(&mutex);
        }
        void Pop(T *out)
        {
            pthread_mutex_lock(&mutex);
            if(Is_empty())
            {
                pthread_cond_wait(&is_empty,&mutex);
            }
            *out=_bq.front();
            _bq.pop();
            
            //唤醒生产者
            if(_bq.size()<_cap/2) wake_producer();
            pthread_mutex_unlock(&mutex);
        }
    };
}

运行结果:

几点说明:

  • 唤醒消费者或生产者的代码,放在临界区外和临界区内都可

    • 放在unlock()之前,被唤醒的线程也得先竞争锁,即需要等当前线程退出临界区将锁释放后,被唤醒的线程重新竞争到锁,才会真正地在临界区中苏醒,所以不会有线程安全问题
    • 放在unlock()之后,即当前线程是先退出临界区将锁释放后,才唤醒的线程,但被唤醒的线程依旧需要重新竞争到锁,才会真正地在临界区中苏醒,所以也不会有线程安全问题
    • 即俩者的区别实际上就是:被唤醒的线程是在等待锁释放后去竞争锁,还是锁已经被释放了再去竞争锁;就效率而言,第一种方法可能效率会更高
  • 控制消费者和生产者之间的协同关系,只需控制主逻辑中的comsumer和producer方法即可:

    • 生产者快,消费者慢:comsumer代码逻辑中不用使用sleep() ,producer代码逻辑中使用sleep();就会出现,生产者在等待消费者消费的这样一种协同机制

    其他场景类似,只需要控制comsumer和producer中的代码逻辑即可

而其实我们之前所学进程间通信的管道其实就是典型的生产者与消费者模型;

区外和临界区内都可

  • 放在unlock()之前,被唤醒的线程也得先竞争锁,即需要等当前线程退出临界区将锁释放后,被唤醒的线程重新竞争到锁,才会真正地在临界区中苏醒,所以不会有线程安全问题

  • 放在unlock()之后,即当前线程是先退出临界区将锁释放后,才唤醒的线程,但被唤醒的线程依旧需要重新竞争到锁,才会真正地在临界区中苏醒,所以也不会有线程安全问题

  • 即俩者的区别实际上就是:被唤醒的线程是在等待锁释放后去竞争锁,还是锁已经被释放了再去竞争锁;就效率而言,第一种方法可能效率会更高

  • 控制消费者和生产者之间的协同关系,只需控制主逻辑中的comsumer和producer方法即可:

    • 生产者快,消费者慢:comsumer代码逻辑中不用使用sleep() ,producer代码逻辑中使用sleep();就会出现,生产者在等待消费者消费的这样一种协同机制

    其他场景类似,只需要控制comsumer和producer中的代码逻辑即可

而其实我们之前所学进程间通信的管道其实就是典型的生产者与消费者模型;

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

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

相关文章

jQuery学习记录--jQuery语法,选择器,事件及hide(),show(), toggle()

jQuery学习记录–jQuery语法&#xff0c;选择器&#xff0c;事件及hide&#xff08;&#xff09;&#xff0c;show()&#xff0c;toggle() jQuery 简介 jQuery 是一个 JavaScript 库。jQuery 极大地简化了 JavaScript 编程。 jQuery 库包含以下功能&#xff1a; HTML 元素选…

AMBER分子动力学模拟之结果分析(构象分析)-- HIV蛋白酶-抑制剂复合物(6)

AMBER分子动力学模拟之结果分析(构象分析)-- HIV蛋白酶-抑制剂复合物(6) RMSD RMSF b-facto计算 RMSD RMSD measures the deviation of a target set of coordinates (i.e. a structure) to a reference set of coordinates, with R M S D 0.0 \mathrm{RMSD}0.0 RMSD0.0 i…

【分享】又找到几个免费使用gpt4的方法!

哈喽&#xff0c;大家好&#xff0c;我是木易巷~ GPT-4是OpenAI推出的最新人工智能语言模型&#xff0c;拥有惊人的1750亿个参数&#xff0c;是目前最大、最强大的语言模型之一&#xff0c;能够根据给定的文本或关键词生成高质量的文本&#xff0c;可以处理多模态数据&#xf…

nodejs进阶(3)—路由处理

1. url.parse(url)解析 该方法将一个URL字符串转换成对象并返回。 url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) 接收参数&#xff1a; urlStr url字符串 parseQueryString 为true时将使用查询模…

基于Vue框架的思源新闻发布平台设计与实现(论文+源码)_kaic

摘 要 经过针对全校随机抽取的100名学生进行的研究发现&#xff0c;有约69&#xff05;的学生&#xff0c;并不关心思源新闻&#xff0c;一些学生表示思源每天发生的大小事与他们无关。这项调查突显了需要提供一个能激发学生对思源校园新闻感兴趣的平台。因此本文为思源学院全…

今天面了个字节拿38K出来的,真是砂纸擦屁股,给我露一手

今年的春招已经结束&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#xff0c;他山之石…

H264宏块包含的各种语法信息详解

H264宏块包含的各种语法信息详解 本文将以CABAC编码需要编码的语法元素的程序为例&#xff0c;分析H264宏块中所包含的各种语法元素的含义。 以上三种变量的其他情况分支&#xff0c;本文进行了省略处理&#xff0c;完整的&#xff0c;全分支的CABAC编码见参考资料【1】 1、 s…

vscode使用插件remote-ssh远程连接服务器

vscode使用插件remote-ssh远程连接服务器 0.引言1.配置密钥对1.1.本地端1.2.服务器端 2.服务器端配置3.vscode连接4.调试 0.引言 用上了公司百万级的服务器 &#x1f606; &#x1f606; &#x1f606; &#x1f606; 参考1参考2 1.配置密钥对 生成密钥对&#xff0c;是为了…

[离散数学]谓词逻辑与推理演算

文章目录 谓词逻辑辖域变元的约束---换自由变元 不容易出错枚举前束范式量词例子练习题特殊例子如果明天下雨&#xff0c;则某些人将被淋湿如果人都爱美,则漂亮的衣服有销路 谓词推理量词相关规则 (去量词 加量词) 谓词逻辑 辖域 变元的约束—换自由变元 不容易出错 枚举 前束范…

博客系统后端设计(五) - 实现登录页面功能

文章目录 约定前后端交互接口修改前段代码修改后端代码登录测试 约定前后端交互接口 这里约定请求是一个 POST 请求&#xff0c;路径是 /login&#xff0c;使用的是以下的格式&#xff1a; usernamzhangsan&password123 响应是 HTTP/1.1 302&#xff0c;因为在成功登录之…

SpringCloud(26.分布式服务框架Dubbo面试题简析)

上一篇&#xff1a;25. 简述 Seata 的原理 下一篇&#xff1a;27. Redis 和 ZK 分布式锁 文章目录 1. 为什么要将系统进行拆分&#xff1f;2. 如何进行系统拆分&#xff1f;3. 拆分后不用 dubbo 可以吗&#xff1f;4. dubbo 工作原理5. 注册中心挂了可以继续通信吗&#xff1…

【Drone】ubuntu 20.x 版本下 通过docker-compose方式部署drone的全流程 整合gitee

一、前期准备 1、ubuntu环境 确定是否具有 ssh&#xff1a; service ssh start&#xff0c;如果没有&#xff0c;使用&#xff1a;apt install openssh-server 进行安装查看 Linux 的 IP 地址&#xff1a; ifconfig&#xff0c;命令不可用时&#xff0c;通过&#xff1a;apt …

pdf太大,怎么压缩的小一点,这几个方法高效便捷

pdf太大&#xff0c;怎么压缩的小一点呢&#xff1f;我们在日常办公中&#xff0c;使用到pdf文件的情况很多。因为pdf文件可以在几乎所有操作系统和设备上打开&#xff0c;这使得它们非常方便。而且PDF 文件的内容和格式在不同设备之间始终保持一致&#xff0c;这意味着无论在哪…

PowerShell install 一键部署virtualbox

VirtualBox 前言 VirtualBox 是一款开源虚拟机软件。VirtualBox 是由德国 Innotek 公司开发&#xff0c;由Sun Microsystems公司出品的软件&#xff0c;使用Qt编写&#xff0c;在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。Innotek 以 GNU General Public Licens…

优思学院|什么是8D报告?

企业中的问题&#xff0c;就像生活中的麻烦事&#xff0c;总是层出不穷&#xff0c;让人头疼不已。有时候&#xff0c;我们可能会遇到一些棘手的问题&#xff0c;简单的方法已经无法解决了&#xff0c;这时候&#xff0c;8D问题解决法就是一把利器。 所以&#xff0c;我们不应…

档案室库房十防要求及措施

档案室“十防”措施 根据档案保护技术的要求&#xff0c;结合本公司档案实际&#xff0c;现制定“十防”措施如下&#xff1a; 一、 防火 1、 指定专人负责防火安全工作。 2、 定期检查手动灭火器&#xff0c;发现灭火器表压低于绿色区域时&#xff0c;必须及重新充装。 3、…

Consule系列:Consul实现详解

Consul 的实现 Consul 使用 Consensus 协议提供一致性(Consistency)—— CAP 定义的一致性。Consensus 协议是基于 “Raft: In search of an Understandable Consensus Algorithm” 实现的。 Consul Protocol Raft 算法 Raft 是基于 Paxos 的一致性算法。 与 Paxos 相比&#x…

Java每日一练(20230518) 移除元素、跳跃游戏II、复原IP地址

目录 1. 移除链表元素 &#x1f31f; 2. 跳跃游戏 II &#x1f31f;&#x1f31f; 3. 复原 IP 地址 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 移…

在 Mac M2 (arm64架构)上编译安装 GmSSL 工具

GmSSL是一个开源密码工具包&#xff0c;为GM/T系列标准中规定的中国国家密码算法和协议提供一级支持。作为OpenSSL项目的一个分支&#xff0c;GmSSL提供了与OpenSSL的API级兼容性&#xff0c;并维护了所有功能。现有的项目&#xff0c;如ApacheWeb服务器&#xff0c;只需稍加修…

前端面试题整理3

目录 1.不使用promise怎么实现一个异步编程? 2.this的指向有哪些&#xff1f; 3.Ref和reactive响应式的区别&#xff1f; 4.首屏加载优化。如何处理&#xff1f; 5.axios封装&#xff1f; 6.css为什么使用预处理&#xff1f; 7.从1000-9999里面取出AAAA这种数字&#xf…