信号量线程池读者写者模型

前言

大家好,我是jiantaoyab,本篇文章接着给大家介绍线程有关的信号量及线程池的基本理解。

信号量

在计算机中,信号量就是个 0 以上的整数值,当为 0 时表示己无可用信号 ,或者说条件不再允许,因此它表示某种信号的累积“ 量飞故称为信号量。

信号量是种同步机制。同步一般是指合作单位之间为协作完成某项工作而共同遵守的工作步调,强调的是配合时序,就像十字路口的红绿灯,只有在绿灯亮起的情况下司机才能踩油门把车往前开,这就是一种同步,同步简单来说就是不能随时随意工作,工作必须在某种条件具备的情况下才能开始,工作条件具备的时间顺序就是时序。

信号量就是个计数器,它的计数值是自然数,用来记录所积累信号的数量。

既然信号量是计数也必然要有对计数增减的方法。P、V 操作来表示信号量的减、增,这两个都是荷兰语中的单词的缩写。P是指Proberen表示减少, V是指单词 Verhogen,表示增加。

V操作

  1. 将信号量的值加 1
  2. 唤醒在此信号量上等待的线程

P操作:

  1. 判断信号量是否大于 0 。
  2. 若信号量大于 0,则将信号量减 1 。
  3. 若信号量等于 0,当前线程将自己阻塞,以在此信号量上等待。

信号量是个全局共享变量,up 和 down 又都是读写这个全局变量的操作,而且它们都包含一系列的子操作,因此它们必须都是原子操作。
信号量的初值代表是信号资源的累积量,也就是剩余量,若初值为1的话,它的取值就只能为0和1,这便称为二元信号量,我们可以利用二元信号量来实现锁。

在二元信号量中,down 操作就是获得锁,up操作就是释放锁。我们可以让线程通过锁进入临界区,可以借此保证只有一个线程可以进入临界区,从而做到互斥。

大致流程为:

  1. 线程 A 进入临界区前先通过 down 操作获得锁(我们有强制通过锁进入临界区的手段),此时信号量的值便为 0 。
  2. 后续线程 B 再进入临界区时也通过 down 操作获得锁,由于信号量为 0,线程 B 便在此信号量上等待,也就是相当于线程 B 进入了睡眠态
  3. 当线程 A 从临界区出来后执行 up 操作释放锁,此时信号量的值重新变成 1 ,之后线程 A 将线程 B唤醒 。
  4. 线程 B 醒来后获得了锁,进入临界区 。

信号量接口

初始化

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享 value:信号量初始值

基本操作

//销毁
int sem_destroy(sem_t *sem);
//等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
//发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

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

ring_queue.hpp

#pragma once
#include <vector>
#include <pthread.h>
#include <iostream>
#include <semaphore.h>

using namespace std;
namespace ns_ring_queue
{
    template <class T>
    class RingQueue
    {
    private:
        vector<T> _ring_queue; // 环形队列
        int _cap;              // 容量
        sem_t _black_sem;      // 生产者关系空位置资源
        sem_t _data_sem;       // 消费者关心数据资源
        int _c_step;           // 消费者走到哪
        int _p_step;
        pthread_mutex_t _c_mtx;
        pthread_mutex_t _p_mtx;

    public:
        RingQueue(int cap = 3)
            : _ring_queue(cap), _cap(cap)
        {
            sem_init(&_black_sem, 0, cap);
            sem_init(&_data_sem, 0, 0);
            _c_step = _p_step = 0; // 从0开始走

            pthread_mutex_init(&_c_mtx, nullptr);
            pthread_mutex_init(&_p_mtx, nullptr);
        }
        ~RingQueue()
        {
            sem_destroy(&_black_sem);
            sem_destroy(&_data_sem);
            pthread_mutex_destroy(&_c_mtx);
            pthread_mutex_destroy(&_p_mtx);

        }
    public:
        void Push(const T& in)
        {
            //生产接口
            sem_wait(&_black_sem); //并行的先分配好信号量

            pthread_mutex_lock(&_p_mtx);//再分配锁

            _ring_queue[_p_step]=in;//生产到p_step位置上
            _p_step++;
            _p_step%=_cap;

            pthread_mutex_unlock(&_p_mtx);

            sem_post(&_data_sem);
        }

         void Pop(T* out)
        {
            //消费接口
            sem_wait(&_data_sem);
            pthread_mutex_lock(&_c_mtx);

            *out=_ring_queue[_c_step]; //拿c_step上的数据
            _c_step++;
            _c_step%=_cap;

            pthread_mutex_unlock(&_c_mtx);

            sem_post(&_black_sem);
        }

    };
}

ring_cp.cc

#include"ring_queue.hpp"
#include<pthread.h>
#include<time.h>
#include<unistd.h>

using namespace ns_ring_queue;

void* consumer(void* args)
{
     RingQueue<int>* rq = (RingQueue<int>*)args;
     while(true){
         int data = 0;
         rq->Pop(&data);
         std::cout << "消费数据是: " << data << std::endl;
        //  sleep(1);
     }
}

void* producter(void* args)
{
     RingQueue<int>* rq = (RingQueue<int>*)args;
     while(true){
         int data = rand()%20 + 1;
         std::cout << "生产数据是:  " << data << std::endl;
         rq->Push(data);
        sleep(1);
     }
}
int main()
{
    srand((long long)time(nullptr));
    RingQueue<int>* rq = new RingQueue<int>();

    pthread_t c0,c1,c2,c3,p0,p1,p2;

    pthread_create(&c0, nullptr, consumer, (void*)rq);
    pthread_create(&c1, nullptr, consumer, (void*)rq);
    pthread_create(&c2, nullptr, consumer, (void*)rq);
    pthread_create(&c3, nullptr, consumer, (void*)rq);
    pthread_create(&p0, nullptr, producter, (void*)rq);
    pthread_create(&p1, nullptr, producter, (void*)rq);
    pthread_create(&p2, nullptr, producter, (void*)rq);

    pthread_join(c0, nullptr);
    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p0, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    return 0;
}

线程的阻塞和唤醒

线程能运行是因为调度器将线程从就绪队列中摘出来放到处理器上,如果不让线程在就绪队列出现就能实现线程的阻塞。阻塞是线程自己发出的动作,也就是线程自己阻塞自己,并不是被别人阻塞的,阻塞是线程主动的行为。己阻塞的钱程是由别人来唤醒的,唤醒是被动的。

当线程被换上处理器运行后,在其时间片内,线程将主宰自己的命运。阻塞是一种意愿,表达的是线程运行中发生了一些事情,这些事情通常是由于缺乏了某些运行条件造成的,以至于线程不得不暂时停下来,必须等到运行的条件再次具备时才能上处理器继续运行。因此,阻塞发生的时间是在线程自己的运行过程中,是线程自己阻塞自己,并不是被谁阻塞。

己被阻塞的线程是无法运行的,属于睡梦中,因此它只能让别人唤醒它,否则它永远没有运行的机会。这个别人便是锁的持有者,它释放了锁之后便去唤醒在它后面因获取该锁而阻塞的线程。

因此唤醒己阻塞的线程是由别的线程,通常是锁的持有者来做的。值得注意的是线程阻塞是线程执行时的“动作”,因此线程的时间片还没用完,在唤醒之后,线程会继续在剩余的时间片内运行,调度器并不会将该线程的时间片“充满”,也就是不会再用线程的优先级priority 为时间片 ticks 赋磕。

因为阻塞是线程主动的意愿,它也是“迫于无奈”才“慷慨”地让出处理器资源给其他线程,所以调度器没必要为其“大方”而“赏赐”它完整的时间片。

线程池

线程池就是事先创建若干个可执行的线程放入一个池中,需要的时候从池中取线程而不用自行创建,使用完毕后不用销毁线程而是返回池中,从而减少线程对象创建和销毁的开销。这种做法可以大大提高服务器的性能,因为线程的创建和销毁成本相对较高,而线程池通过复用线程来降低这种开销。

线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池的种类

  1. FixedThreadPool(定长线程池)
    • 线程数量固定,不会随着任务的增加而增加。
    • 任务队列为链表结果的有界队列。
    • 适用于任务量较稳定、预计执行时间较长的情况。
  2. CachedThreadPool(可缓存线程池)
    • 线程数量不固定,根据任务的数量和执行时间自动调整。
    • 任务队列为不存储元素的阻塞队列。
    • 适用于执行大量、耗时少的任务。
    • 线程闲置超过60秒会被回收。
  3. SingleThreadExecutor(单线程化线程池)
    • 只有一个核心线程,无非核心线程。
    • 任务队列为链表结果的有界队列。
    • 适用于需要保证任务顺序执行的场景。
  4. ScheduledThreadPool(定时线程池)
    • 核心线程数量固定,非核心线程数量无限。
    • 任务队列为延时阻塞队列。
    • 执行定时或周期性任务。
    • 线程闲置超过10秒会被回收。
  5. ForkJoinPool(分治任务线程池)
    • 将任务拆分成更小的子任务,并行执行,并合并结果。
    • 适用于处理大规模的计算任务。
  6. WorkStealingPool(工作窃取线程池)
    • 每个线程都有自己的工作队列。
    • 当某个线程完成自己的任务后,会从其他线程的队列中偷取任务来执行。
    • 这种机制可以提高任务执行效率。

普通版本的线程池

thread_pool.hpp

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

namespace ns_threadpool
{
    template<class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue; //临界资源
        pthread_mutex_t _mtx;
        pthread_cond_t _cond;

    public:

        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&_cond,&_mtx);
        }
        void Wakeup()
        {
            pthread_cond_signal(&_cond);
        }
        bool IsEmpty()
        {
            return _task_queue.empty();
        }

    public:

        ThreadPool(int num=3)
            :_num(num)
            {
                pthread_mutex_init(&_mtx,nullptr);
                pthread_cond_init(&_cond,nullptr);
            } 
        
        //想要线程在类中执行类中的函数是不可以行的,因为只能传一个参数
        //要设置成static,让线程执行静态方法
        static void* Rountine(void* args)
        {
            pthread_detach(pthread_self());//分离不用等
            ThreadPool<T> *tp=(ThreadPool<T> *)args; //this指针
            while(true)
            {
                tp->Lock();
                while(tp->IsEmpty())
                {
                    tp->Wait(); //没任务等待
                }
                
                int data = 0;
                tp->PopTask(&data);

                tp->Unlock();
                //处理任务
                std::cout << "消费数据是: " << data << std::endl;

               
            }
        }

        void InitThreadPool()
        {
            pthread_t id;
            for(int i=0;i<3;i++)
            {
                pthread_create(&id,nullptr,Rountine,(void*)this);
            }
        }

        void PushTask(const T& in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();
            Wakeup();
        }

        void PopTask(T* out)
        {
          
            *out=_task_queue.front();
            _task_queue.pop(); 

        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        }
    };
}

main.cc

#include"thread_pool.hpp"
#include<ctime>
#include<cstdlib>

using namespace ns_threadpool;


int main()
{
    ThreadPool<int>* tp= new ThreadPool<int>(3);
    tp->InitThreadPool();
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);
        tp->PushTask(rand()%5);
    }
}

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一般我们有2种方法实现单例模式,饿汉模式和懒汉模式。

饿汉式:全局的单例实例在类装载时构建,急切实例化。这种方式比较简单,因为单例的实例被声明为static和final变量,第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

template <typename T>
class Singleton {
	static T data;
public:
	static T* GetInstance()
    {
		return &data;
	}
};

懒汉式:懒汉式是类加载进内存的时候,并不立即初始化这个单例,只有在第一次调用getInstance()方法时才初始化。需要加上双重检查锁定保证线程安全。

//存在线程安全问题
template <typename T>
class Singleton {
	static T* inst;
public:
	static T* GetInstance() {
	if (inst == NULL) 
    {
		inst = new T();
	}
	return inst;
	}
};

懒汉下单例模式线程池

thread_pool.hpp

#pragma once

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

namespace ns_threadpool
{
    const int g_num = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int num_;
        std::queue<T> task_queue_; //该成员是一个临界资源

        pthread_mutex_t mtx_;
        pthread_cond_t cond_;

        static ThreadPool<T> *ins;

    private:
        // 构造函数必须得实现,但是必须的私有化
        ThreadPool(int num = g_num) : num_(num)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&cond_, nullptr);
        }
        ThreadPool(const ThreadPool<T> &tp) = delete;
        //赋值语句
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;

    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            // 当前单例对象还没有被创建
            if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!
            {
                pthread_mutex_lock(&lock);
                if (ins == nullptr)
                {
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
                pthread_mutex_unlock(&lock);
            }

            return ins;
        }

        void Lock()
        {
            pthread_mutex_lock(&mtx_);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&mtx_);
        }
        void Wait()
        {
            pthread_cond_wait(&cond_, &mtx_);
        }
        void Wakeup()
        {
            pthread_cond_signal(&cond_);
        }
        bool IsEmpey()
        {
            return task_queue_.empty();
        }

    public:
        // 在类中要让线程执行类内成员方法,是不可行的!
        // 必须让线程执行静态方法
        static void *Rountine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;

            while (true)
            {
                tp->Lock();
                while (tp->IsEmpey())
                {
                    tp->Wait();
                }
                //该任务队列中一定有任务了
                T t;
                tp->PopTask(&t);
                tp->Unlock();

                t();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            task_queue_.push(in);
            Unlock();
            Wakeup();
        }
        void PopTask(T *out)
        {
            *out = task_queue_.front();
            task_queue_.pop();
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&cond_);
        }
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::ins = nullptr;
} 

main.cc

#include "thread_pool.hpp"
#include "Task.hpp"

#include <ctime>
#include <cstdlib>

using namespace ns_threadpool;
using namespace ns_task;

int main()
{
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;
    std::cout << "当前正在运行我的进程其他代码..." << std::endl;

    sleep(5);
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);

        //网络
        Task t(rand()%20+1, rand()%10+1, "+-*/%"[rand()%5]);
        ThreadPool<Task>::GetInstance()->PushTask(t);
        //单例本身会在任何场景,任何环境下被调用
        //GetInstance():被多线程重入,进而导致线程安全的问题
        std::cout << ThreadPool<Task>::GetInstance() << std::endl;
    }

    return 0;
}

读者写者模型

读者写者模型是操作系统中的一种同步与互斥机制,它与消费者和生产者模型有相似之处,但也有其独特的特点。在读者写者模型中,主要涉及到两种角色:读者和写者。

读者:在读者写者模型中,读者是指那些只需要读取数据的角色。多个读者之间可以同时读取数据,不会发生冲突,因此读者之间是并行的关系。

写者:写者是指那些需要修改数据的角色。由于数据在修改时不能被其他写者或读者访问,因此写者之间、以及写者与读者之间是互斥的关系。

读者写者模型的特点

  1. 多读少写:在多数应用中,读者的数量通常远多于写者。读者写者模型适用于这种多读少写的情况,能够有效地提高数据的并发访问性能。
  2. 读者并行:多个读者可以同时访问数据,实现并行读取,提高了数据的访问效率。
  3. 写者互斥:当有写者需要修改数据时,其他写者和读者都不能访问数据,保证了数据的一致性和完整性。
  4. 优先级策略:根据不同的应用场景,可以设定不同的优先级策略,如读者优先、写者优先或公平策略等。

读写锁接口

设置读写优先

默认是读锁优先级高

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);

//销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

//加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

和生产者消费者的区别

读者写者模型

读者:只读取数据,不进行写操作。

写者:对数据进行修改或写入。

生产者消费者模型

  • 生产者:负责生成数据并放入缓冲区。
  • 消费者:从缓冲区取出数据进行处理。

读者和消费者最大的区别就是读者只是读并不会对数据进行取走处理。读者写者模型主要关注于如何协调读者和写者对共享数据的并发访问,而生产者消费者模型则主要解决生产者与消费者之间的数据传递和同步问题。

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

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

相关文章

面试笔记——类加载器

基础 类加载器&#xff1a;用于装载字节码文件(.class文件)运行时数据区&#xff1a;用于分配存储空间执行引擎&#xff1a;执行字节码文件或本地方法垃圾回收器&#xff1a;用于对JVM中的垃圾内容进行回收 类加载器 &#xff1a;JVM只会运行二进制文件&#xff0c;类加载器的…

LLM——用于微调预训练大型语言模型(LLM)的GPU内存优化与微调

前言 GPT-4、Bloom 和 LLaMA 等大型语言模型&#xff08;LLM&#xff09;通过扩展至数十亿参数&#xff0c;实现了卓越的性能。然而&#xff0c;这些模型因其庞大的内存需求&#xff0c;在部署进行推理或微调时面临挑战。这里将探讨关于内存的优化技术&#xff0c;旨在估计并优…

2025第23届太原煤炭(能源)工业技术与装备展览会

第二十三届太原煤炭&#xff08;能源&#xff09;工业技术与装备展览会 邀 请 函 指导单位&#xff1a; 中国煤炭工业协会 主办单位&#xff1a;山西省煤炭工业协会 承办单位&#xff1a;太原奇新展览有限公司 展览时间&#xff1a;2025年4月22-24日 展览地点&#xff1a…

jenkins+gitlab+sonar自由风格项目配置

新建项目&基本配置 gitlab侧配置 sonar.projectKeytest_sonar sonar.projectNametest_sonar sonar.projectVersion1.0 sonar.sources. sonar.exclusionssrc/layout/** sonar.sourceEncodingUTF-8 sonar.nodejs.executable/app/nodejs/node-v16.20.2-linux-x64/bin/node配置…

cURL:命令行下的网络工具

序言 在当今互联网时代&#xff0c;我们经常需要与远程服务器通信&#xff0c;获取数据、发送请求或下载文件。在这些情况下&#xff0c;cURL 是一个强大而灵活的工具&#xff0c;它允许我们通过命令行进行各种类型的网络交互。本文将深入探讨 cURL 的基本用法以及一些高级功能…

智能AI摄像头项目

项目概要 硬件说明&#xff1a;易百纳rv1126 38板&#xff0c;易百纳GC2053摄像头&#xff0c;拓展版&#xff08;自绘&#xff09;&#xff0c;屏幕驱动板&#xff08;自绘&#xff09;&#xff0c;3.1inch mipi屏&#xff0c;FT5316触摸屏 开发环境 硬件分析 开发环境及sd…

数据结构十一:数组相关经典面试题

本篇博客详细介绍分析数组/顺序表常见的面试题&#xff0c;对于前面所学知识进行一个巩固&#xff0c;同时介绍一些力扣刷题中的一些概念&#xff1a;如&#xff1a;输出型参数等&#xff0c;在刷题中培养自己的编程思维&#xff0c;掌握常见的编程套路&#xff0c;形成题感&am…

KernelSU 如何不通过模块,直接修改系统分区

刚刚看了术哥发的视频,发现kernelSU通过挂载OverlayFS实现无需模块,即可直接修改系统分区,很是方便,并且安全性也很高,于是便有了这篇文章。 下面的教程与原视频存在差异,建议观看原视频后再结合本文章进行操作。 在未进行修改前,我们打开/system/文件夹,并在里面创建…

Junit 测试中如何对异常进行断言

本文对在 Junit 测试中如何对异常进行断言的几种方法进行说明。 使用 Junit 5 如果你使用 Junit 5 的话,你可以直接使用 assertThrows 方法来对异常进行断言。 代码如下: Exception exception = assertThrows(NumberFormatException.class, () -> {new Integer("on…

fiscobcos 3.x linux安装与java简单调用

所用环境 vmware 16 Pro centos7.6 jdk11.0.6 ideal 2022 1、安装fiscobcos # 创建操作目录 # cd ~ && mkdir -p fisco && cd fisco# 下载建链脚本 # curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v3.6.0/build_chain.sh &a…

Spring Security + JWT 实现登录认证和权限控制

Spring Security JWT 实现登录认证和权限控制 准备步骤 准备好一些常用的工具类&#xff0c;比如jwtUtil&#xff0c;redisUtil等。引入数据库&#xff0c;mybatis等&#xff0c;配置好controller&#xff0c;service&#xff0c;mapper&#xff0c;保证能够正常的数据请求。…

【python】条件语句与循环语句

目录 一.条件语句 1.定义 2.条件语句格式 &#xff08;1&#xff09;if &#xff08;2&#xff09;if-else &#xff08;3&#xff09;elif功能 &#xff08;4&#xff09;if嵌套使用 3.猜拳游戏 二.循环语句 1. while循环 2.while嵌套 3.for循环 4.break和conti…

k8s部署skywalking(helm)

官方文档 官方文档说明&#xff1a;Backend setup | Apache SkyWalking官方helm源码&#xff1a;apache/skywalking-helm官方下载&#xff08;包括agent、apm&#xff09;:Downloads | Apache SkyWalking 部署 根据官方helm提示&#xff0c;选择你自己部署的方式&#xff0c…

PyTorch深度学习框架:从入门到实战

前言 学习 PyTorch 深度学习框架之前先学会深度学习和卷积神经网络 CNN &#xff0c;这样学习起来会更香嗷。 Windows系统下PyTorch的环境配置 Anaconda是什么&#xff1a; Anaconda是一个开源的Python发行版本&#xff0c;专注于数据分析领域。它包含了conda、Python等190多…

解决python/pycharm中import导入模块时报红却能运行的问题

一、问题 导入时报红&#xff0c;如下 二、解决 右键单击项目&#xff0c;将项目Mark Directory as→Sources Root 三、效果 报红消失 学习导航&#xff1a;http://www.xqnav.top

Docker网络基础

简介 Docker 本身的技术依赖于近年来 Linux 内核虚拟化技术的发展,Docker 对 Linux 内核的特性有很强的依赖。Docker 使用到的与 Linux 网络有关的主要技术有:网络命名空间、veth 设备对、网桥、ipatables 、路由。 网络命名空间 为了支持网络协议栈的多个实例,Linux在网络栈…

使用Docker安装Jenkins

大家好&#xff0c;今天给大家分享如何使用docker安装jenkins&#xff0c;关于docker的安装和常用命令可以参考下面两篇文章&#xff0c;使用docker可以提高资源利用率&#xff0c;能够在不同的环境中轻松迁移和部署应用&#xff0c;在本文中就不过多赘述了。 Docker常用命令 …

大数据BI可视化(Echarts组件)项目开发-熟悉动画使用功能4.0

加载动画 数据源 [{ "gender": "female", "height": 161.2, "weight": 51.6 }, { "gender": "female", "height": 167.5, "weight": 59 }, { "gender": "female", &quo…

opencv基础篇 ——(十六)图形绘制与填充

OpenCV 提供了丰富的图形绘制和填充功能&#xff0c;主要通过 cv::rectangle, cv::circle, cv::line, cv::polylines, cv::fillPoly 和 cv::ellipse 等函数实现。以下是一些基本的图形绘制和填充操作的说明&#xff1a; 矩形: 函数: cv::rectangle语法: cv::rectangle(img, rec…

一文2500字Robot Framework自动化测试框架超强教程

1、Robot Framework简介 Robot Framework是一个基于Python的可扩展关键字驱动的自动化框架&#xff0c;用于验收测试&#xff0c;验收测试驱动开发&#xff08;ATDD&#xff09;&#xff0c;行为驱动开发&#xff08;BDD&#xff09;和机器人流程自动化&#xff08;RPA&#xf…
最新文章