线程详解(接上篇博客)

news2024/10/5 20:20:05

目录

1.生产者消费者模型;

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

3.线程池;

4.STL, 智能指针, 线程安全;

5.读者写者问题.


前言:

        本篇博客博主五一假期都在肝的一篇, 希望xdm点点三连, 博主感谢了 onz !!!

1.生产者消费者模型

321原则:(便于记忆)

3是指3种关系: 生产者和生产者; 消费者和消费者;生产者和消费者.

2是2种角色: 生产者和消费者;

1是指一个交易场所: 内存空间.

 1.1为什么要使用生产者消费者模型?

生产者和消费者模型是通过一个容器来解决生产者和消费者强解耦问题, 生产者和消费者通过阻塞队列进行通讯, 阻塞队列就相当于一个缓冲区, 消费者是从阻塞队列里面拿数据, 生产者生产的数据不用给消费者,直接给到阻塞队列里面, 这样可以平衡生产者和消费者的能力.(忙闲不均)

1.2生产者消费者模型的优点

(1)解耦; (2)支持并发; (3)支持忙闲不均.

 1.3基于阻塞队列的生产消费模型

阻塞队列当队列为空的时候, 消费者无法取数据而是被阻塞,直到生产新数据.当队列为满的时,生产者也不会再生产数据而是被阻塞, 直到消费数据.

1.4代码

这里封装四个模块进行编写.对每个模块进行详细解答.

首先的是BlockQueue.hpp, 封装一个阻塞队列.

条件变量_p_cond和_c_cond都是用来阻塞队列生产消费关系处理的.

#pragma once

#include<iostream>
#include<queue>
#include"LockGuard.hpp"
using namespace std;

const int defaultcap = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap)
        :_capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

    bool IsFull()
    {
        return _q.size() == _capacity;
    }

    bool IsEmpty()
    {
        return _q.size() == 0;
    }

    void Push(const T& in)//生产者生产数据
    {
        LockGuard lockguard(&_mutex);
        while(IsFull())
        {
            pthread_cond_wait(&_p_cond, &_mutex);
        }
       
       _q.push(in);
       pthread_cond_signal(&_c_cond);
    }

    void Pop(T* out)//消费者消费数据
    {
        LockGuard lockguard(&_mutex);
        while(IsEmpty())
        {
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_p_cond);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

private:
    queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; //生产者的条件变量
    pthread_cond_t _c_cond; /* 消费者的条件变量 */
};

上面BlockQueue还需要封装了一个锁LockGuard; 

这里就是封装了一个互斥量(锁)

#pragma once

#include<pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock)
        :_lock(lock)
    {}

    void Lock()
    {
        pthread_mutex_lock(_lock);
    }

    void unlock()
    {
        pthread_mutex_unlock(_lock);
    }

    ~ Mutex()
    {}
private:
    pthread_mutex_t* _lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock)
        :_mutex(lock)
    {
        _mutex.Lock();
    }

    ~LockGuard()
    {
        _mutex.unlock();
    }
private:
    Mutex _mutex;
};

生产者生产数据和消费者消费数据的数据, 这里我们Task.hpp来实现+-*/%的处理类.

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

using namespace std;

const int defaultvalue = 0;
const string opers = "+-*/%)(|";

enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unkown
};

class Task
{
public:
    Task()
    {}
    
    Task(int x, int y, char op)
        :data_x(x)
        ,data_y(y)
        ,oper(op)
        ,result(defaultvalue)
        ,code(ok)
    {}

    void Run()
    {
        switch(oper)
        {
        case'+':
            result = data_x + data_y;
            break;
        case'-':
            result = data_x - data_y;
            break;
        case'*':
            result = data_x * data_y;
            break;
        case'/':
          {
            if(data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
          }
        break;
        case'%':
          {
            if(data_y == 0)
                code = mod_zero;
            else
                result = data_x % data_y;
          }
        break;
        default:
            code = unkown;
            break;
        }
    }

    void operator()()
    {
        Run();
        sleep(2);
    }

    string PrintTask()
    {
        string s;
        s = to_string(data_x);
        s += oper;
        s += to_string(data_y);
        s += "=?";

        return s;
    }

    string PrintResult()
    {
        string s;
        s = to_string(data_x);
        s += oper;
        s += to_string(data_y);
        s += "=";
        s += to_string(result);
        s += "[";
        s += to_string(code);
        s += "]";

        return s;
    }

    ~Task()
    {}
private:
    int data_x;
    int data_y;
    char oper;  //+-/*%
    int result;
    int code;  /* 结果码 0: 可信, !0 不可信 */
};

最后一个main.cc进行测试;

这里p表示生产者, c表示消费者; ThreadData是封装了阻塞队列以及线程名称.

消费者对于数据是消费那么就是pop数据, 并且还要的到数据结果; 生产者是push数据, 并且还要更新数据的信息.

#include<iostream>
#include<pthread.h>
#include<time.h>
#include<unistd.h>
#include"Blockqueue.hpp"
#include"Task.hpp"
using namespace std;

class ThreadData
{
public:
    BlockQueue<Task>* bq;
    string name;
};

void* consumer(void* args)
{
    ThreadData* td = (ThreadData*)args;

    while(true)
    {
        Task t;
        td->bq->Pop(&t);

        t.Run();
        cout << "consumer data:" << t.PrintResult() << "," << td->name << endl; 
    }

    return nullptr;
}

void* productor(void* args)
{
    BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(args);

    while(true)
    {
        int data1 = rand() % 10;
        usleep(rand()%123);

        int data2 = rand() % 10;
        usleep(rand()%123);

        char oper = opers[rand() % opers.size()];
        Task t(data1, data2, oper);

        cout << "productor task:" << t.PrintTask() << endl;
        bq->Push(t);
        sleep(1);
    }
    return nullptr;
}

int main()
{
    /* 形成随机数 */
    srand((uint16_t)time(nullptr) ^ getpid() ^ pthread_self());
    BlockQueue<Task>* bq = new BlockQueue<Task>();
    pthread_t c[3], p[2];/* consumer productor */

    ThreadData* td = new ThreadData();
    td->bq = bq;
    td->name = "thread-1";
    /* 消费者1 */
    pthread_create(&c[0], nullptr, consumer, td);

    ThreadData* td1 = new ThreadData();
    td1->bq = bq;
    td1->name = "thread-2";
    /* 消费者2 */
    pthread_create(&c[1], nullptr, consumer, td1);

    ThreadData* td2 = new ThreadData();
    td2->bq = bq;
    td2->name = "thread-3";
    /* 消费者3 */
    pthread_create(&c[2], nullptr, consumer, td2);

    pthread_create(&p[0], nullptr, productor, bq);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(c[2], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);

    return 0;
}

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

先穿插讲一个日志, 如何写日志.

va_list: 宏定义, 是一个指向变长参数列表的指针.

va-start: 可变参数的起始地址, ap是参数va_list, last是最后固定参数的地址.

va_end: 结束使用变长参数列表, 用于清空va_list和va_start

#pragma once
#include<iostream>
#include<stdarg.h>
#include<time.h>
using namespace std;

enum
{
    Debug = 0,
    Info,
    warning,
    Error,
    Fatal
};


string LevelToString(int level)
{
    switch(level)
    {
        case Debug:
            return "Debug";
        case Info:
            return "Info";
        case warning:
            return "warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "unkown";
    }
}

class Log
{
public:
    Log(){}

    void LogMessage(int level, const char* format, ...)
    {
        char content[1024];
        va_list args;//可变参数
        va_start(args, format);
        vsnprintf(content, sizeof(content), format, args);
        va_end(args);
        uint64_t currtime = time(nullptr);
        printf("[%s][%s]%s\n", LevelToString(level).c_str(), 
                to_string(currtime).c_str(), content);
    }

    ~Log(){}
private:    
};

#include<iostream>
#include"Log.hpp"
#include"RingQueue.hpp"
#include"Task.hpp"
#include<pthread.h>
#include<ctime>
using namespace std;

void* Productor(void* args)
{
    RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);

    while(true)
    {
        int data1 = rand() % 10;
        usleep(rand() % 123);

        int data2 = rand() % 10;
        usleep(rand() % 123); 

        char oper = opers[rand() % (opers.size())];
        Task t(data1, data2, oper);
        cout << "productor task: " << t.PrintTask() << endl;

        rq->Push(t);
    }
    return nullptr;
}

void* consumer(void* args)
{
     RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);
     while(true)
     {
        Task t;
        rq->Pop(&t);

        t.Run();
        cout << "consumer done, data is : " << t.PrintResult() << endl;
     }
}

int main()
{
   /*  Log log;
    log.LogMessage(Debug, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(warning, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(Error, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(Info, "hello %d, %s, %f", 10, "study", 3.14);
     */
    

    srand((uint64_t)time(nullptr) ^ pthread_self());
    pthread_t c[3], p[2];

    RingQueue<Task>* rq = new RingQueue<Task>();

    pthread_create(&p[0], nullptr, Productor, rq);
    pthread_create(&p[1], nullptr, Productor, rq);

    pthread_create(&c[0], nullptr, consumer, rq);
    pthread_create(&c[1], nullptr, consumer, rq);
    pthread_create(&c[2], nullptr, consumer, rq);

    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(c[2], nullptr);

    return 0;
}

 RingQueue是采用信号量进行条件变量以及互斥量的控制.

首先认识一下信号量的接口:

int sem_wait(sem_t* sem): 相当于条件变量里面的pthread_cond_wait

int sem_post(sem_t* sem): 相当于mutex_unlock;

int sem_init(sem* sem): 相当于pthread_cond_init;

int sem_destroy(sem* sem): 相当于pthread_cond_destroy

 下图更好的理解环形队列生产消费模型;

生产者不能将消费者包个圈, 因为消费者没有消费, 那么空间资源就不会释放, 并且消费者也不能越过生产者, 因为消费者消费完生产者生产的数据, 那么还需要的数据并还没有生产.

这样就有两个变量需要控制一个是空间资源_space_sem, _data_sem; 以及生产者消费者的位置也需要记录_p_step, _c_step.

#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#include "LockGuard.hpp"

const int defaultsize = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }

public:
    RingQueue(int size = defaultsize)
        : _ringqueue(size), _size(size), _p_step(0), _c_step(0)
    {
        sem_init(&_space_sem, 0, size);
        sem_init(&_data_sem, 0, 0);

        pthread_mutex_init(&_p_mutex, nullptr);
        pthread_mutex_init(&_c_mutex, nullptr);
    }
    void Push(const T &in)
    {
        P(_space_sem);
        {
            LockGuard lockGuard(&_p_mutex);
            _ringqueue[_p_step] = in;
            _p_step++;
            _p_step %= _size;
        }
        V(_data_sem);
    }
    void Pop(T *out)
    {
        // 消费
        P(_data_sem);
        {
            LockGuard lockGuard(&_c_mutex);
            *out = _ringqueue[_c_step];
            _c_step++;
            _c_step %= _size;
        }
        V(_space_sem);
    }
    ~RingQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);

        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

private:
    std::vector<T> _ringqueue;
    int _size;

    int _p_step; // 生产者的生产位置
    int _c_step; // 消费位置

    sem_t _space_sem; // 生产者
    sem_t _data_sem;  // 消费者

    pthread_mutex_t _p_mutex;
    pthread_mutex_t _c_mutex;
};

常见生产者消费者线程, 借助上次使用过的Task类, 模拟生产这个数据, 然后进行push数据.

消费者就是popTask里面的数据结果.

注意: 创建的线程一定要join.

#include<iostream>
#include"Log.hpp"
#include"RingQueue.hpp"
#include"Task.hpp"
#include<pthread.h>
#include<ctime>
using namespace std;

void* Productor(void* args)
{
    RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);

    while(true)
    {
        int data1 = rand() % 10;
        usleep(rand() % 123);

        int data2 = rand() % 10;
        usleep(rand() % 123); 

        char oper = opers[rand() % (opers.size())];
        Task t(data1, data2, oper);
        cout << "productor task: " << t.PrintTask() << endl;

        rq->Push(t);
    }
    return nullptr;
}

void* consumer(void* args)
{
     RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);
     while(true)
     {
        Task t;
        rq->Pop(&t);

        t.Run();
        cout << "consumer done, data is : " << t.PrintResult() << endl;
     }
}

int main()
{
   /*  Log log;
    log.LogMessage(Debug, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(warning, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(Error, "hello %d, %s, %f", 10, "study", 3.14);
    log.LogMessage(Info, "hello %d, %s, %f", 10, "study", 3.14);
     */
    

    srand((uint64_t)time(nullptr) ^ pthread_self());
    pthread_t c[3], p[2];

    RingQueue<Task>* rq = new RingQueue<Task>();

    pthread_create(&p[0], nullptr, Productor, rq);
    pthread_create(&p[1], nullptr, Productor, rq);

    pthread_create(&c[0], nullptr, consumer, rq);
    pthread_create(&c[1], nullptr, consumer, rq);
    pthread_create(&c[2], nullptr, consumer, rq);

    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(c[2], nullptr);

    return 0;
}

3.线程池

3.1 线程池的概念:

线程池是一种线程使用的机制, 维护多个线程, 等待命令执行多线程并发的任务, 避免过多线程的调用带来太大开销, 导致影响性能. 好处是避免短时间的任务创建和销毁的开销, 保证内核的高效使用.

 3.2线程池使用的场景:

(1)需要大量线程完成, 并且任务时间短的;

(2)对性能要求比较高的应用;

(3)接受突发性大量请求;

3.3线程池的种类:

(1)创建固定数量的线程,池, 循环从队列里面拿任务;

(2)获取到任务之后, 执行任务对象的任务接口;

 3.4代码编写(单例模式的线程池)

#include <iostream>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>

#include"Thread.hpp"
#include"LockGuard.hpp"
#include"Log.hpp"
#include"Task.hpp"

using namespace std;

class ThreadData
{
public:
    ThreadData(const string& name)
        :threadname(name)
    {}

    ~ThreadData()
    {}

    string threadname;
};

static const int defaultnum = 5;

template<class T>
class ThreadPool
{
private:
    ThreadPool(int thread_num = defaultnum)
        :_thread_num(thread_num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        for(int i = 0; i < _thread_num; i++)
        {
            string threadname = "thread-";
            threadname += to_string(i + 1);

            ThreadData td(threadname);

            _threads.emplace_back(threadname, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td);
            log.LogMessages(Info, "%s is create...\n", threadname.c_str());
        }
    }

    ThreadPool(const ThreadPool<T>& td) = delete;
    ThreadPool<T>& operator=(const ThreadPool<T>& td) = delete;
    
public:
    static ThreadPool<T>* GetInstance()
    {
        if(instance == nullptr)
        {
            LockGuard lockguard(&sig_lock);
            if(instance == nullptr)
            {
                log.LogMessages(Info, "创建单例成功!");
                instance = new ThreadPool<T>();
            }
        }
        return instance;
    }

    bool start()/* 启动线程池 */
    {
        for(auto& thread : _threads)
        {
            thread.Start();
            log.LogMessages(Info, "%s is running ...\n", thread.ThreadName().c_str());
        }
        return true;
    }

    void ThreadWait(const ThreadData& td)
    {
        log.LogMessages(Debug, "no task, %s is sleeping...\n", td.threadname.c_str());
        pthread_cond_wait(&_cond, &_mutex);
    }

    void ThreadWakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void checkSelf()
    {
        /* 水位线检查 */
    }

    void ThreadRun(ThreadData& td) /* 模拟pop消费者消费数据 */
    {
        while(true)
        {
            checkSelf();
            T t;
            {
                LockGuard lockguard(&_mutex);
                while(_q.empty())
                {
                    ThreadWait(td);
                    log.LogMessages(Debug, "thread %s is wakeup\n", td.threadname.c_str());
                }
                t = _q.front();
                _q.pop();
            }
            
            Task t1;
            log.LogMessages(Debug, "%s handler task %s done, result is : %s\n",
                          td.threadname, t1.PrintTask().c_str(), t1.PrintResult().c_str());
        }
    }

    void Push(Task& in)
    {
        log.LogMessages(Debug, "other thread push a task, task is :%s \n", in.PrintTask().c_str());
        LockGuard lockguard(&_mutex);
        _q.push(in);
        ThreadWakeup();
    }

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

    void wait()
    {
        for(auto&  thread : _threads)
        {
            thread.Join();
        }
    }
private:
    queue<T> _q;
    vector<Thread<ThreadData>> _threads;
    int _thread_num;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T>* instance;
    static pthread_mutex_t sig_lock;
};

/* 对static变量外部初始化 */
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;

#include <iostream>
#include <memory>
#include <ctime>
#include "ThreadPool.hpp"
#include "Task.hpp"
using namespace std;

int main()
{
    sleep(1);
    ThreadPool<Task>::GetInstance()->start();
    srand((uint64_t)time(nullptr) ^ getpid());
    
    while(true)
    {
        int x = rand() % 10;
        usleep(100);

        int y = rand() % 10;
        usleep(100);

        char oper = opers[rand() % opers.size()];
        Task t1(x, y, oper);

        ThreadPool<Task>::GetInstance()->Push(t1);
        sleep(1);
    }

    ThreadPool<Task>::GetInstance()->wait();
    return 0;
}

4. STL, 智能指针, 线程安全

(1) STL不是线程安全的, 如果要在线程里面使用需要配合锁使用.

(2) 智能指针中unique_ptr, 不是线程安全的;  shared_ptr因为是涉及到引用计数所以是原子的,也就是线程安全的.

 5.读者写者问题

在编写多线程的时候, 往往会出现修改的机会少, 读取的机会多, 在读取的时候互斥量的一系列处理就效率非常慢, 然后根据这种情况特定还有一种读写锁.

这里先认识一下读写锁的接口:

int pthread_rwlock_init: 读写锁的初始化

int pthread_rwlock_destroy: 读写锁的销毁

int pthread_rwlock_rdlock: 读锁的互斥量

int pthread_rwlock_wrlock: 写锁的互斥量

int pthread_rwlock_unlock: 读写锁的解锁.

 

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

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

相关文章

Chat2DB Pro 重磅发布 !!!

在过去的几个月中&#xff0c;我们的团队一直默默耕耘&#xff0c;全心投入到Chat2DB Pro版本的开发之中。这段时间里&#xff0c;我们暂停了新动态的发布&#xff0c;以至于有趣的误解在社群中出现&#xff0c;有人调侃我们是否“倒闭”了。然而&#xff0c;我们今天携带着全新…

Java-(乘法表之后)增强for循环

这里我们先做个了解&#xff0c;之后我会在数组中进行详细介绍Java5引入了一种主要用于数组或集合的增强型for循环Java增强型for循环语法格式如下 For(声明语句&#xff1a;表达式&#xff09;{ //代码语句 } 声明语句&#xff1a;声明新的局部变量&#xff0c;该变量的类型…

【智能算法】PID搜索算法(PSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2023年&#xff0c;Y Gao受到PID控制理论启发&#xff0c;提出了PID搜索算法&#xff08;PID-based Search Algorithm, PSA&#xff09;。 2.算法原理 2.1算法思想 PID算法是控制领域的…

CTF(Web)中关于执行读取文件命令的相关知识与绕过技巧

在我遇到的题目中&#xff0c;想要读取文件必然是要执行cat /flag这个命令&#xff0c;但是题目当然不会这么轻松。让你直接cat出来&#xff0c;必然会有各种各样的滤过条件&#xff0c;你要做的就是尝试各种方法在cat /flag的基础上进行各种操作构建出最终的payload。 下面我…

Redis学习汇总

目录 1.Linux环境下安装redis 2.redis的数据结构及命令 3.redis.conf配置文件常用配置 3.redis的事务操作 4.redis实现乐观锁 5.通过jedis操作redis 6.Springboot集成redis 7.自定义一个RedisTemplate 8.持久化策略 RDB和AOF 9.redis集群环境搭建 10.哨兵模式 11.缓…

Java面试题:多线程2

如何停止正在运行的线程 1,使用退出标志,使线程正常退出(run方法中循环对退出标志进行判断) 2,使用stop()方法强行终止(不推荐) 3,调用interrupt()方法中断线程 打断阻塞线程(sleep,wait,join),线程会抛出InterruptedException异常 打断正常的线程,可以根据打断状态来标记…

奶爸预备 |《P.E.T.父母效能训练:让亲子沟通如此高效而简单:21世纪版》 / 托马斯·戈登——读书笔记

目录 引出致中国读者译序前言第1章 父母总是被指责&#xff0c;而非受训练第2章 父母是人&#xff0c;不是神第3章 如何听&#xff0c;孩子才会说&#xff1a;接纳性语言第4章 让积极倾听发挥作用第5章 如何倾听不会说话的婴幼儿第6章 如何听&#xff0c;孩子才肯听第8章 通过改…

【春招特供】Unity面试题总结 | Unity基础篇

物体发生碰撞的必要条件&#xff1f; 两个物体都必须带有碰撞器&#xff08;Collider&#xff09;&#xff0c;其中一个物体还必须带有Rigidbody刚体&#xff0c;而且必须是运动的物体带有Rigidbody脚本才能检测到碰撞。 2. Unity3d中的碰撞器和触发器的区别&#xff1f; 碰…

【LeetCode 121】买卖股票的最佳时机

思路 思路&#xff1a; 所谓代码的复杂性来源于业务的复杂性&#xff0c;如果能够想清楚业务实现逻辑&#xff0c;就能够轻松写出代码&#xff1b; 假设当前是第i天&#xff0c;如何在第i天赚到最多的钱&#xff1f;需要在第i天之前以最低价买入股票&#xff1b; 所以需要求…

[渗透利器]全能工具=信息收集->漏洞扫描->EXP调用

前言 hxd开发的工具&#xff0c;大致模块有&#xff08;信息收集&#xff0c;漏洞扫描&#xff0c;暴力破解&#xff0c;POC/EXP&#xff0c;常用编码&#xff09; 工具使用 下载后解压 安装环境 pip install -r requirements.txt 注意&#xff0c;该工具继承了两种不同的使…

深入了解C/C++的内存区域划分

&#x1f525;个人主页&#xff1a;北辰水墨 &#x1f525;专栏&#xff1a;C学习仓 本节我们来讲解C/C的内存区域划分&#xff0c;文末会附加一道题目来检验成果&#xff08;有参考答案&#xff09; 一、大体有哪些区域&#xff1f;分别存放什么变量开辟的空间&#xff1f; …

Django高级表单处理与验证实战

title: Django高级表单处理与验证实战 date: 2024/5/6 20:47:15 updated: 2024/5/6 20:47:15 categories: 后端开发 tags: Django表单验证逻辑模板渲染安全措施表单测试重定向管理最佳实践 引言&#xff1a; 在Web应用开发中&#xff0c;表单是用户与应用之间进行交互的重要…

工厂模式+策略模式完成多种登录模式的实现

前提 &#xff08;简单工厂不属于设计模式&#xff0c;而是一种编程思想【抽象一层出来】&#xff09;工厂方法模式、抽象工厂模式 以上都是为了解耦&#xff0c;如果考虑多个纬度&#xff08;如需要同时考虑多种电器&#xff0c;多种品牌&#xff09;则优先考虑抽象工厂。 …

iMazing下载安装不了怎么办?

iMazing是一款可用于iPhone、iPad等ios移动设备管理软件&#xff0c;但需要注意的是&#xff0c;iMazing只能安装在Windows与Mac系统中&#xff0c;不能安装在iOS移动设备上。iOS移动设备可以通过USB线或Wi-Fi连接Windows或Mac系统上的iMazing软件。 iMazing的安装失败&#x…

Linux--IIC驱动编程实验

对于 I2C 主机驱动&#xff0c;一旦编写完成就不需要再做修改&#xff0c;其他的 I2C 设备直接调用主机驱动提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想&#xff0c;因此 Linux内核也将 I2C 驱动分为两部分&#xff1a; ①、 I2C 总…

3.栈和队列(汇总版)

目录 1.栈&#xff08;一端插和删&#xff09; 2.队列&#xff08;一端插另一段删&#xff09; 2.1队列的概念及结构 2.2 队列的实现 队列的接口 1.初始化队列 2.销毁队列 3.插入元素 4.出队列&#xff08;头删&#xff09; 5.访问对头 6.访问队尾 7.判断队列是否为…

练习题(2024/5/6)

1路径总和 II 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], target…

【漏洞复现】某小日子太阳能系统DataCube3审计

漏洞描述 某小日子太阳能系统DataCube3终端测量系统 多个漏洞利用方式 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进…

一起了解开源自定义表单的优势表现

随着社会的进步和科技的发展&#xff0c;越来越多的中小企业希望采用更为先进的软件平台&#xff0c;助力企业实现高效率的流程化管理。低代码技术平台、开源自定义表单已经慢慢走入大众视野&#xff0c;成为一款灵活、高效的数字化转型工具。流辰信息专注于低代码技术平台的研…

GitOps介绍

基础设施即代码 IaC 在理解 GitOps 之前&#xff0c;需要先理解什么是基础设施即代码。 基础设施即代码&#xff08;Infrastructure as Code&#xff0c;简称IaC&#xff09;是一种软件工程实践&#xff0c;它将基础设施的管理和配置过程像管理代码一样进行版本控制、自动化和…