Linux线程同步实例

news2024/11/23 2:35:26

线程同步实例

  • 1. 生产消费者模型基本概念
  • 2. 基于BlockingQueue的生产者消费者模型
  • 3. 基于环形队列的生产消费模型
  • 4. 线程池

1. 生产消费者模型基本概念

生产者消费者模型是一种常用的并发设计模式,它可以解决生产者和消费者之间的速度不匹配、解耦、异步等问题。生产者消费者模型的应用场景有很多,例如Excutor任务执行框架、消息中间件activeMQ、任务的处理时间比较长的情况下等。

生产者消费者模型的基本结构如下

  1. 生产者(Producer):负责生成数据或任务,放入缓冲区(Buffer)中。
  2. 消费者(Consumer):负责从缓冲区中取出数据或任务,进行处理。
  3. 缓冲区(Buffer):一般是一个有限大小的队列,用来存储生产者生成的数据或任务,同时提供给消费者使用。

生产者消费者模型的核心是缓冲区,它可以平衡生产者和消费者的处理能力,起到一个数据缓存的作用,同时也达到了一个解耦的作用。缓冲区的实现方式有多种,例如:

  1. 使用Object的wait()和notify()方法,让生产者和消费者在缓冲区满或空时进行等待和唤醒。
  2. 使用Semaphore的acquire()和release()方法,让生产者和消费者通过信号量控制缓冲区的访问。
  3. 使用BlockingQueue阻塞队列,让生产者和消费者通过put()和take()方法自动实现阻塞和唤醒。
  4. 使用Lock和Condition的await()和signal()方法,让生产者和消费者通过条件变量控制缓冲区的状态。
  5. 使用PipedInputStream和PipedOutputStream,让生产者和消费者通过管道流进行通信。

生产者消费者模型的应用场景有很多,例如:

  1. Excutor任务执行框架:通过将任务的提交和任务的执行解耦开来,提交任务的操作相当于生产者,执行任务的操作相当于消费者。
  2. 消息中间件activeMQ: 双十一的时候,会产生大量的订单,那么不可能同时处理那么多的订单,需要将订单放入一个队列里面,然后由专门的线程处理订单。
  3. 任务的处理时间比较长的情况下:比如上传附件并处理,那么这个时候可以将用户上传和处理附件分成两个过程,用一个队列暂时存储用户上传的附件,然后立刻返回用户上传成功,然后有专门的线程处理队列中的附件。

生产者消费者模型优点

  1. 解耦:生产者和消费者之间不直接通信,而是通过缓冲区来进行通信,降低了代码之间的依赖性,简化了工作负载的管理。
  2. 复用:生产者和消费者可以独立地进行复用和扩展,增加了代码的可维护性和可扩展性。
    调整并发数:生产者和消费者的处理速度可能不一致,可以通过调整并发数来平衡速度差异,提高系统的吞吐量和效率。
  3. 异步:生产者不需要等待消费者处理完数据才能继续生产,消费者也不需要等待生产者生成数据才能继续消费,通过异步的方式支持高并发,提高系统的响应性和灵活性。
  4. 支持分布式:生产者和消费者可以运行在不同的机器上,通过分布式的缓冲区来进行通信,增加了系统的可伸缩性和容错性。

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

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

基于BlockingQueue的生产者消费者模型,可以封装一个类,这个类就只有简单的插入、删除操作是一个简单的阻塞队列,并且内部用条件变量来实现。
blockQueue.hpp源代码:

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

const int gcap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(const int cap = gacp)
    :_cap = cap
    {
        pthread_mutex_init(&mutex, nullptr);
        pthread_cond_init(&_consumerCond, nullptr);
        pthread_cond_init(&_productorCond, nullptr);
    }
    bool isFull()
    {
        return _q.size() == _cap;
    }
    bool isEmpty()
    {
        return _q.empty();
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while (isFull())
        {
            pthread_cond_wait(&_productorCond, &_mutex);
            sleep(1); // 每隔1s询问一次队列是否为满,因为消费者可能在这1s中消费
        }
        _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);
            sleep(1);
        }
        *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:
    queue<T> _q;
    int _cap;
    pthread_mutex_t _mutex;
    pthread_cond_t _consumerCond;  // 消费者对应的条件变量,若空,则wait
    pthread_cond_t _productorCond; // 生产者对应的条件变量,若满,则wait
};

然后就可以实现简单的单生产单消费以及多生产多消费的样例,main文件中的代码如下:

#include "blockQueue.hpp"
#include <time.h>

void *productor(void *args)
{
    BlockQueue<int> *q = static_cast<BlockQueue<int>*>(args);
    int count = 20;
    while (count--)
    {
        int val = rand() % 5 + 1;
        cout << "生产的数据:" << val << endl;
        q->push(val);
    }
    return nullptr;
}
void *consumer(void* args)
{
    BlockQueue<int> *q = static_cast<BlockQueue<int>*>(args);
    while (true)
    {
        int val = 0;
        q->pop(&val);
        cout << "消费的数据:" << val << endl;
        usleep(300);
        if (q->isEmpty())
            break;
    }
    return nullptr;
}
// 单生产单消费
int main()
{
    srand((unsigned int)time(0)); //随机数种子

    BlockQueue<int> *q = new BlockQueue<int>;
    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, q);
    pthread_create(&p, nullptr, productor, q);

    while (true)
    {
        sleep(1);
        if (q->isEmpty())
            break;
    }

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

    return 0;
}

//多生产多消费
// int main()
// {
//     srand((uint64_t)time(nullptr));
//     // BlockQueue<int> *bq = new BlockQueue<int>();
//     BlockQueue<int> *bq = new BlockQueue<int>();
//     // 单生产和单消费 -> 多生产和多消费
//     pthread_t c[2], p[3];
//     pthread_create(&c[0], nullptr, consumer, bq);
//     pthread_create(&c[1], nullptr, consumer, bq);
//     pthread_create(&p[0], nullptr, productor, bq);
//     pthread_create(&p[1], nullptr, productor, bq);
//     pthread_create(&p[2], nullptr, productor, bq);

//     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);
//     delete bq;
//     return 0;
// }

运行结果如下:
在这里插入图片描述
基于BlockingQueue的生产者消费者模型是一种常见的多线程设计模式,它有以下几个优点:

  1. 简化编程:BlockingQueue提供了线程安全的入队和出队操作,无需自己实现同步和锁机制,降低了编程难度和出错风险。
  2. 提高性能:BlockingQueue支持阻塞和超时机制,可以根据队列的状态自动调整生产者和消费者的状态,避免了无效的等待和轮询,提高了系统的吞吐量和响应速度。
  3. 增强可扩展性:BlockingQueue可以作为有界队列或无界队列使用,可以根据实际需求调整队列的容量和策略,增加了系统的灵活性和可扩展性。

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

环形队列是一种特殊的队列,它是在队列的基础上添加了一些限制条件,使得队列可以在固定大小的存储空间下进行循环使用。环形队列可以用数组实现,数组中的元素按照一定的顺序排列,并且当队列头或者队列尾指针到达数组的尾部时,会自动从数组的头部开始重新循环使用。环形队列的一个好处是,当队列满时,可以通过覆盖队列头部的元素来继续存储新的元素,这样可以使得队列在一定程度上具有循环使用的能力,节省存储空间。但是在使用环形队列时需要注意一些细节问题,比如队列空、队列满、队列大小等等。
在这里插入图片描述
环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。但是现在有信号量这个计数器,就很简单的进行多线程间的同步过程,所以环形队列的生产消费者模型内部使用信号量来实现。
基于环形队列的生产消费模型的代码如下:

#pragma once

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

template <class T>
class RingQueue
{
public:
    RingQueue(int num = N)
    :_ring(num)
    ,_cap(num)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, _cap);
        _c_step = _p_step = 0;

        pthread_mutex_init(&_c_mutex,nullptr);
        pthread_mutex_init(&_p_mutex,nullptr);
    }
    // 生产
    void push(const T &in)
    {
        P(_space_sem);
        Lock(_p_mutex);
        _ring[_p_step++] = in;
        _p_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(&_space_sem);
        sem_destroy(&_data_sem);

        pthread_mutex_destroy(&_c_mutex);
        pthread_mutex_destroy(&_p_mutex);
    }
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }
    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&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;
};

然后生产一些任务,这些任务不弄简单的随机数,而弄一些随机出来的加减乘除运算,所以再封装一个类,这个类可以做加减乘除运算,该类的代码如下:

#pragma once

#include <iostream>
using namespace std;

class Task
{
public:
    Task()
    {}
    Task(const int x, const int y, const char op)
    :_x(x)
    ,_y(y)
    ,_op(op)
    ,_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 '%':
                _result = _x % _y;
                break;
            default:
                break;
        }
    }
    string formatArg() // 输入的格式
    {
        return to_string(_x) + _op +to_string(_y) + '=';
    }
    string formatRes() // 输出的格式
    {
        return to_string(_result) + '(' + to_string(_exitCode) + ')';
    }
    ~Task()
    {}
private:
    int _x;
    int _y;
    char _op;

    int _result;
    int _exitCode;
};

然后直接用多生产多消费进行验证,多生产多消费的main.cc文件代码如下:

#include "RingQueue.hpp"
#include "task.hpp"
#include <ctime>
#include <pthread.h>
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>

using namespace std;

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.formatRes() << endl;
    }
}

void *productorRoutine(void *args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
    while (true)
    {
        sleep(1);
        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.formatArg() << endl;
    }
}

int main()
{
    srand(time(nullptr));
    RingQueue<Task> *rq = new RingQueue<Task>();
    // 单生产单消费
    // pthread_t c, p;
    // pthread_create(&c, nullptr, consumerRoutine, rq);
    // pthread_create(&p, nullptr, productorRoutine, rq);

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

    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);

    delete rq;
    return 0;
}

最后就会有如下形式的任务被生产者派发,然后由消费者处理问题。
在这里插入图片描述
基于环形队列的生产消费模型是一种常见的并发同步模式,它有以下优缺点:
优点:

  1. 解耦:生产者和消费者不直接交互,而是通过环形队列进行数据传递,降低了两者之间的耦合度。
  2. 支持并发:生产者和消费者可以同时访问环形队列的不同位置,提高了并发性能。
  3. 支持忙闲不均:当生产者和消费者的速度不匹配时,环形队列可以缓冲数据,避免数据丢失或阻塞。

缺点:

  1. 需要额外的空间:环形队列需要预先分配固定大小的空间,可能造成空间浪费或不足。
  2. 需要额外的同步机制:环形队列需要使用信号量或其他同步机制来控制生产者和消费者之间的协作,增加了编程复杂度。
  3. 可能出现饥饿或饱和:当环形队列满或空时,生产者或消费者可能会长时间等待,影响系统的响应性。

4. 线程池

Linux线程池是一种管理多个线程的技术,它可以提高程序的性能和资源利用率。Linux线程池的基本思想是:

  1. 预先创建一定数量的线程,放在一个池中,这些线程称为核心线程。
  2. 当有新的任务到来时,如果有空闲的核心线程,就分配给它执行;如果没有空闲的核心线程,就将任务放在一个任务队列中,等待有空闲的线程来执行。
  3. 如果任务队列也满了,就创建新的线程,超过核心线程数量的线程称为非核心线程。
  4. 如果非核心线程空闲时间超过一定的限制,就销毁这些线程,回收资源。
  5. 如果核心线程空闲时间超过一定的限制,并且设置了允许回收核心线程的标志,就销毁这些线程,回收资源。

Linux线程池的优点有:

  1. 降低创建和销毁线程的开销,提高响应速度。
  2. 控制并发的数量,避免过多的线程竞争,提高系统稳定性。
  3. 统一管理和调度线程,提高代码的可维护性。

Linux线程池的实现方法有:

  1. 使用POSIX标准提供的pthread库来创建和管理线程,使用互斥锁和条件变量来实现任务队列和同步机制。
  2. 使用C++标准库中的std::thread类来创建和管理线程,使用std::queue容器来实现任务队列,使用std::mutex和std::condition_variable来实现同步机制。
  3. 使用第三方库或框架来实现线程池,例如Boost.Asio、libevent、libuv等。

对于上述所描述的,可以使用互斥锁和条件变量来实现任务队列和同步机制,实现简单的线程池,代码如下:

#pragma once

#include <iostream>
#include <queue>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include "task.hpp"
using namespace std;

const static int N = 5;

template <class T>
class ThreadPool
{
public:
    ThreadPool(int num = N)
    :_num(num)
    ,_threads(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    void lockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void threadWait()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    void threadWakeup()
    {
        pthread_cond_signal(&_cond);
    }
    bool isEmpty()
    {
        return _tasks.empty();
    }
    T popTask()
    {
        T t = _tasks.front();
        _tasks.pop();
        return t;
    }
    void pushTask(const T &t)
    {
        lockQueue();
        _tasks.push(t);
        threadWakeup(); // 插入任务后唤醒线程处理任务
        unlockQueue();
    }
    static void *threadRoutine(void *args) // 对于类的内部成员函数,会有默认的this指针,所以可以将这个函数定义在类的外部,
                                           // 或者定义静态成员函数,但静态成员函数不能直接访问类的内部成员。
    {
        pthread_detach(pthread_self()); // 线程分离,这样子线程就可以自己释放自己的资源
        ThreadPool<T> *tp = static_cast<ThreadPool<T>*>(args); 
        // 对于tp指针来说,他不能访问私有成员,所以可以用一些函数去访问类的私有成员,或者将私有成员暴露出来,属性设置为public
        while (true)
        {
            // 检测有没有任务
            tp->lockQueue();
            while (tp->isEmpty())
            {
                tp->threadWait();
            }
            T t = tp->popTask(); // 拿出队列中的任务
            tp->unlockQueue();

            //test:放入一些数据
            t(); // task任务内部是用该仿函数来处理任务的
            cout << "thread handler done, result: " << t.formatRes() << std::endl;
        }
    }
    void start()
    {
        for (int i = 0; i < _num; ++i)
        {
            pthread_create(&_threads[i], nullptr, threadRoutine, this);
        }
    }
    ~ThreadPool()
    {
        pthread_cond_destroy(&_cond);
        pthread_mutex_destroy(&_mutex);
    }
private:
    vector<pthread_t> _threads;
    int _num;

    queue<T> _tasks;

    pthread_mutex_t _mutex;  // 使用互斥锁和条件变量来实现任务队列和同步机制
    pthread_cond_t _cond;
};

#include <memory>

int main()
{
    unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    tp->start();
    while (true)
    {
        int x, y;
        char op;
        cout << "please Enter x> ";
        cin >> x;
        cout << "please Enter y> ";
        cin >> y;
        cout << "please Enter op(+-*/%)> ";
        cin >> op;
        Task t(x, y, op);
        tp->pushTask(t);
        usleep(500);
    }
    return 0;
}

当有任务时,该线程池会处理任务,没有任务是则会等待。
在这里插入图片描述

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

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

相关文章

Vue 绑定style和class

在应用界面中&#xff0c;某些元素的样式是动态的。class 与 style 绑定就是专门用来实现动态样式效果的技术。 如果需要动态绑定 class 或 style 样式&#xff0c;可以使用 v-bind 绑定。 绑定 class 样式【字符串写法】 适用于&#xff1a;类名不确定&#xff0c;需要动态指…

Stm32_标准库_8_ADC_光敏热敏传感器_测数值

在测量光敏传感器数值得基础上手动将通道改成热敏传感器通道即可 由于温度传感器的测量范围是-20 ~ 105摄氏度&#xff0c;所以输出温度得考虑带上符号这就需要在原有输出光照强度代码的基础上新添加几个函数 函数1&#xff1a; uint16_t AD_Getvailue(uint8_t ADC_Channel){…

六、DHCP实验

拓扑图&#xff1a; DHCP协议&#xff0c;给定一个ip范围使其自动给终端分配IP&#xff0c;提高了IP分配的效率 首先对PC设备选择DHCP分配ip 首先先对路由器的下端配置网关的ip 创建地址池&#xff0c;通过globle的方式实现DHCP ip pool 地址池名称 之后设置地址池的网关地址…

最大数【贪心3】

题目 分析 代码 class Solution { public:string largestNumber(vector<int>& nums) {vector<string> str;for(auto & x : nums){str.push_back(to_string(x));}sort(str.begin(),str.end(),[](const string& s1,const string& s2){return s1 s…

【JavaEE】_servlet程序的编写方法

目录 1. 创建项目 2. 引入依赖 3. 创建目录结构 3.1 在main目录下创建一个webapp目录 3.2 在webapp目录下创建一个WEB-INF目录 3.3 在WEB-INF目录下创建一个web.xml文件 3.4 在web.xml中进行代码编写 4. 编写代码 4.1 在java目录下创建类 4.2 打印"hello world&…

亚马逊测评安全吗?

测评可以说是卖家非常宝贵的财富&#xff0c;通过测评和广告相结合&#xff0c;可以快速有效的提升店铺的产品销量&#xff0c;提高转化&#xff0c;提升listing权重&#xff0c;但现在很多卖家找真人测评补单后店铺出现问题导致大家对测评的安全性感到担忧&#xff0c;因为真人…

基于php+thinphp+vue的商品购物商城网站

运行环境 开发语言&#xff1a;PHP 数据库:MYSQL数据库 使用框架:ThinkPHPvue 开发工具:VScode/Dreamweaver/PhpStorm等均可 项目简介 基于tpvue的商品定制交易网站实现前台与后台&#xff0c;用户前台&#xff1b;首页、商品信息、我的收藏、留言板、个人中心、后台管理、管…

【c语言】迷宫游戏

之前想写的迷宫游戏今天终于大功告成&#xff0c;解决了随机生成迷宫地图的问题&#xff0c;使用的是深度优先算法递归版本&#xff0c;之前的迷宫找通路问题用的是深度优先算法的非递归实现.之前写过推箱子&#xff0c;推箱子用到了人物的移动&#xff0c;以及碰到墙就不会走&…

如何解决网站被攻击的问题

在当今数字化时代&#xff0c;网站攻击已经成为互联网上的一个常见问题。这些攻击可能会导致数据泄漏、服务中断和用户信息安全问题。然而&#xff0c;我们可以采取一些简单的措施来解决这些问题&#xff0c;以确保网站的安全性和可用性。 使用强密码和多因素认证 密码是保护网…

今年这情况,还能不能选计算机了?

在知乎上看到一个有意思的问题&#xff0c;是劝退计算机的。 主要观点&#xff1a; 计算机从业人员众多加班&#xff0c;甚至需要99635岁危机秃头 综上所属&#xff0c;计算机不仅卷&#xff0c;而且还是一个高危职业呀&#xff0c;可别来干了。 关于卷 近两年确实能明显感觉…

【论文解读】单目3D目标检测 MonoCon(AAAI2022)

本文分享单目3D目标检测&#xff0c;MonoCon模型的论文解读&#xff0c;了解它的设计思路&#xff0c;论文核心观点&#xff0c;模型结构&#xff0c;以及效果和性能。 目录 一、MonoCon简介 二、论文核心观点 三、模型框架 四、模型预测信息与3D框联系 五、损失函数 六、…

CScrollBar 滚动条

1、水平滚动条、垂直滚动条&#xff1b;滚动条中有一个滚动快&#xff0c;用于表示“当前滚动的位置” 2、 3、处理滚动条消息&#xff1a;水平滚动条响应OnHScroll函数&#xff0c;竖直滚动条响应OnVScroll函数。一般在函数中必须经过一下步骤&#xff1a; 1。得到滚动…

【数据结构】二叉树--OJ练习题

目录 1 单值二叉树 2 相同的树 3 另一颗树的子树 4 二叉树的前序遍历 5 二叉树的最大深度 6 对称二叉树 7 二叉树遍历 1 单值二叉树 965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09; bool isUnivalTree(struct TreeNode* root) {if (root NULL){return true;}…

屏幕亮度调节保护您的眼睛

官方下载地址&#xff1a; 安果移动 视频演示&#xff1a;屏幕亮度调节-保护您的眼睛_哔哩哔哩_bilibili 嗨&#xff0c;亲爱的用户&#xff0c;你是否有过这样的体验&#xff1a;夜晚安静的时刻&#xff0c;想要在抖音上看看热门的舞蹈、在快手上发现生活的 趣味、或是在哔…

[MoeCTF 2023] web题解

文章目录 webhttpcookie彼岸的flagmoe图床大海捞针夺命十三枪 web http 连接到本地后&#xff0c;题目给了我们任务 第一个是要求我们GET传参UwUu第二个是要求我们POST传参Luvu第三个是要求我们cookie值为admin第四个是要求我们来自127.0.0.1第五个是要求我们用MoeBrowser浏…

Spring framework Day14:配置类的Lite模式和Full模式

前言 Lite模式和Full模式是指在软件或系统中的不同配置选项。一般来说&#xff0c;Lite模式是指较为简洁、轻量级的配置&#xff0c;而Full模式则是指更加完整、功能更丰富的配置。 Lite模式通常会去除一些不常用或占用资源较多的功能&#xff0c;以提高系统的运行效率和响应…

【C++】 对象模型与内存模型的区别

目录 0 引言1 C 内存模型2 C 对象模型3 二者区别 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;C专栏&#x1f4a5; 标题&#xff1a;【C】 对象模型与内存模型的区别❣️ 寄语&#xff1a;最重要的只有一件事&#xff01;&#x1f388; 最后&am…

Spring(17) AopContext.currentProxy() 类内方法调用切入

目录 一、简介二、代码示例2.1 接口类2.2 接口实现类2.3 AOP切面类2.4 启动类&#xff08;测试&#xff09;2.5 执行结果 一、简介 背景&#xff1a; 在之前 Spring 的 AOP 用法中&#xff0c;只有代理的类才会被切入。例如&#xff1a;我们在 Controller 层调用 Service 的方式…

nginx的优化和防盗链(重点)

一、nginx的优化&#xff08;重点&#xff09; &#xff08;一&#xff09;隐藏版本号 由于nginxbug多&#xff0c;更新版本速度比较快&#xff0c;一旦版本号暴露出去&#xff0c;有可能给对方提供攻击的漏洞 1、在http大模块中修改 2、修改nginx.h源码包 &#xff08;二&a…

竞赛 深度学习LSTM新冠数据预测

文章目录 0 前言1 课题简介2 预测算法2.1 Logistic回归模型2.2 基于动力学SEIR模型改进的SEITR模型2.3 LSTM神经网络模型 3 预测效果3.1 Logistic回归模型3.2 SEITR模型3.3 LSTM神经网络模型 4 结论5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 …