Linux_线程同步生产者消费者模型

同步的相关概念

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

同步的意义
互斥保证安全性,安全不一定合理或高效!!同步主要是在保证安全的前提下,让系统变得更加合理和高效!

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。条件变量是一个用来进行线程同步的特性,内部要维护线程队列。

条件变量函数

pthread_cond_init函数

// 初始化局部的条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t
*restrict attr);

// 使用宏值初始化(全局),类比pthread_mutex_init
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • cond 是指向条件变量对象的指针,该对象将被初始化。
  • attr 是指向条件变量属性的指针,用于指定条件变量的属性。如果此参数为 NULL,则使用默认属性。在大多数应用中,通常传递 NULL

函数成功时返回 0;出错时返回错误码。

pthread_cond_wait函数

// 使线程在条件变量上等待,直到该条件变量被另一个线程的信号
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict
mutex);
  • cond 是指向条件变量对象的指针。
  • mutex 是指向互斥锁对象的指针,该互斥锁必须在调用 pthread_cond_wait 之前被当前线程持有(即锁定状态)。

pthread_cond_signal函数

// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast函数 

// 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);

一个Demo样例

// 初始化全局的锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* active(void* args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        // 让每一个线程去条件变量那里去等待
        pthread_cond_wait(&cond, &mutex);    
        printf("%s is active!\n", name.c_str());
        
        pthread_mutex_unlock(&mutex);
    }
}
int main()
{
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, nullptr, active, (void*)"thread-1");
    pthread_create(&tid2, nullptr, active, (void*)"thread-2");
    pthread_create(&tid3, nullptr, active, (void*)"thread-3");

    while(true)
    {
        printf("main wakeup thread...\n");
        // 我唤醒一个线程,一个线程去做相应的工作
        pthread_cond_signal(&cond);
        sleep(1);
    }

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    return 0;    
}

将上面的pthread_cond_signal换成下面这句代码,线程就会全部被唤醒。

pthread_cond_broadcast(&cond);

生产者消费者模型

什么是生产者消费者模型

生产者消费者模型(Producer-Consumer Model)是一种经典的多线程同步问题,用于描述两个或多个线程之间共享有限资源的场景。在这个模型中,生产者负责生成数据并将其放入缓冲区(Buffer),消费者则从缓冲区中取出数据并消费。生产者和消费者通过缓冲区进行通信,但它们的执行速度可能不同,因此需要通过同步机制来协调它们的行为。

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

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的

生产者消费者模型的优点:解耦、支持并发、支持忙闲不均

1分钟记住生产者消费者模型

生产者消费者模型可简单理解为321原则

  • 三种关系(生产和生产(互斥),消费和消费(互斥),生产和消费(互斥&&同步))
  • 两种角色(生产线程,消费线程)
  • 一个缓冲区(一段内存空间,如阻塞队列)

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

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

Blocking Queue的Demo代码

设计思路:

(1)定义阻塞队列

阻塞队列是生产者和消费者之间的通信桥梁。它需要支持以下操作:

  • 入队(put):生产者调用,将数据放入队列。如果队列已满,则阻塞生产者线程,直到有空间可用。
  • 出队(take):消费者调用,从队列中取出数据。如果队列为空,则阻塞消费者线程,直到有数据可用。

(2)生产者逻辑

生产者负责生成数据并将其放入阻塞队列。

  1. 生成数据。

  2. 调用阻塞队列的put方法将数据放入队列。

  3. 如果队列已满,生产者线程会被阻塞,直到有空间可用。

(3)消费者逻辑

消费者负责从阻塞队列中取出数据并消费。

  1. 调用阻塞队列的take方法从队列中取出数据。

  2. 如果队列为空,消费者线程会被阻塞,直到有数据可用。

  3. 消费数据

// BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>

static const int gcap = 10;
template <typename T>
class BlockQueue
{
private:
    bool IsFull() { return _q.size() == _cap; }
    bool IsEmpty() { return _q.empty(); }
public:
    BlockQueue(int cap = gcap)
        : _cap(cap)
        , _cwait_num(0)
        , _pwait_num(0)
    {
        // 基于RAII设计初始化和销毁
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_productor_cond, nullptr);
        pthread_cond_init(&_consumer_cond, nullptr);
    }
    void Put(const T &in) // 生产者
    {
        pthread_mutex_lock(&_mutex);
        // 为了防止伪唤醒,我们这里使用while进行判断
        // 伪唤醒是指将线程唤醒之后,线程没有资源可以访问,然后继续去等待
        while (IsFull())
        {
            _pwait_num++; // 若空间满了,就让等待的生产者数量++
            // 若满了,则生产者不能生产,必须在条件变量下等待
            // 临界区里面抱着锁等待是不被允许的!!!所以pthread_cond_wait
            // 被调用的时候:除了指明自己在那个条件变量下等待,还会释放自己抱着的锁
            pthread_cond_wait(&_productor_cond, &_mutex);
            // 返回时,线程被唤醒 && 重新申请并持有锁(它会在临界区内醒来!)
            // 才能继续往下运行
            _pwait_num--; // 生产者被唤醒,数量--
        }
        // while(IsFull())条件不满足 || 线程被唤醒, 退出循环
        _q.push(in);
        // 肯定有数据,唤醒消费者
        if (_cwait_num)
            pthread_cond_signal(&_consumer_cond);
        pthread_mutex_unlock(&_mutex);
    }
    void Take(T *out) // 消费者
    {
        pthread_mutex_lock(&_mutex);
        while (IsEmpty())
        {
            _cwait_num++; // 若数据空了,就让等待的消费者数量++
            pthread_cond_wait(&_consumer_cond, &_mutex);
            _cwait_num--; // 消费者被唤醒,数量--
        }
        *out = _q.front();
        _q.pop();
        // 肯定有空间,唤醒生产者
        if (_pwait_num)
            pthread_cond_signal(&_productor_cond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_productor_cond);
        pthread_cond_destroy(&_consumer_cond);
    }

private:
    std::queue<T> _q;               // 使用普通队列作为保存数据的容器
    int _cap;                       // bq最大容量
    pthread_mutex_t _mutex;         // 互斥
    pthread_cond_t _productor_cond; // 生产者条件变量
    pthread_cond_t _consumer_cond;  // 消费者条件变量

    int _cwait_num;                 // 在条件变量下等待的消费者数量
    int _pwait_num;                 // 在条件变量下等待的生产者数量
};
// Main.cc
#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>

void* Consumer(void* args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);
    while(true)
    {
        sleep(2);

        int data;
        // 1.从bq中拿到数据
        bq->Take(&data);
        // 2.做各种处理
        printf("Consumer, 消费了一个数据: %d\n", data);
    }
}
void* Productor(void* args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);
    // 1.从外部获取数据
    int data = 10;
    while(true)
    {
        // 2.生产到bq中
        printf("Productor, 生产了一个数据: %d\n", data);
        bq->Put(data);
        data++;
    }
}
int main()
{
    BlockQueue<int> *bq = new BlockQueue<int>(4);// 共享资源
    // 单生产,单消费
    pthread_t c, p;
    // 最后一个参数传递bq,保证同一个线程看到同一个队列
    pthread_create(&c, nullptr, Consumer, bq);
    pthread_create(&p, nullptr, Productor, bq);

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

    delete bq;
    return 0;
}

让消费者睡眠2秒,生产者正常运行

让生产者睡眠2秒,消费者正常运行

ps. 上面代码是单生产单消费模式的,若想改为多生产多消费的,直接添加线程就可以。因为在生产者消费者模型的321原则中,21已经有了,3中的其中一种关系(生产和消费)已经有了,只需要补充完剩下的两种就可以。而我们设计的生产者与生产者或者消费者与消费者之间本身就只有一把锁,所以它们天然的就存在互斥关系!

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

认识POSIX信号量以及接口

信号量本身就是一个计数器,它用来描述你所申请资源数目的多少

信号量原理:一元信号量(或二元信号量)主要用于实现资源的互斥访问。当一个线程(或进程)获取了信号量(将其值从1减为0),它便获得了对某个共享资源的独占访问权,其他试图获取该信号量的线程将被阻塞,直到信号量被释放(其值从0加回1)。

信号量对公共资源使用时可以整体使用,也可以不整体使用。整体使用就是把整个资源看作一份。当二元信号量的值为1时,表示资源未被占用,线程可以获取信号量并进入临界区;当信号量的值为0时,表示资源已被占用,其他线程必须等待。(如上面的阻塞队列)。

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步

信号量的两个重要操作

1. P操作(Wait操作)

P操作(也称为sem_wait)用于等待信号量,其目的是减少信号量的值,并根据信号量的当前值决定是否阻塞当前线程或进程。

2. V操作(Post操作)

V操作(也称为sem_post)用于释放信号量,其目的是增加信号量的值,并可能唤醒等待该信号量的线程或进程。

 sem_init函数
// 初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数

  • sem:指向要初始化的信号量对象的指针。
  • pshared:控制信号量的作用域。如果pshared的值非0,则信号量在进程间共享;如果为0,则信号量仅在调用进程内的线程间共享
  • value:信号量的初始值。这个值必须是非负的。

返回值

成功时,sem_init 返回0。失败时,返回-1,并设置errno以指示错误

sem_destroy函数
// 销毁信号量
int sem_destroy(sem_t *sem);

参数

  • sem:指向要销毁的信号量对象的指针。

返回值

  • 成功时,sem_destroy 返回 0。失败时,返回 -1,并设置 errno 以指示错误。
sem_wait函数
// 等待信号量
int sem_wait(sem_t *sem); //P()

参数

  • sem:指向要操作的信号量对象的指针。

返回值

  • 成功时返回0。失败时返回-1,并设置errno以指示错误。

该函数用于信号量的P操作,从信号量的值中减去1,如果信号量的值变为0,则当前线程将被阻塞,直到信号量的值被其他线程通过 sem_post 增加。

sem_post函数
// 发布信号量
int sem_post(sem_t *sem);//V()

参数

  • sem:指向要操作的信号量的指针。

返回值

  • 成功时,sem_post返回0。
  • 失败时,返回-1,并设置errno以指示错误。可能的错误包括EINVAL,表示传入的信号量不是一个有效的信号量。

该函数用于信号量的V操作,将信号量的值增加1;唤醒等待线程:如果有线程因为调用sem_wait函数而在该信号量上等待(即信号量的值为0且线程被阻塞),那么sem_post函数可能会唤醒其中一个等待的线程。具体唤醒哪个线程取决于操作系统的线程调度策略。

环形队列的Demo代码

这里环形队列我们使用数组模拟,用模运算来模拟环状特性。

设计思路:

1. 环形队列的结构

环形队列是一个固定大小的数组,通过两个指针(headtail)来表示队列的头部和尾部。队列的头部用于消费者读取数据,队列的尾部用于生产者写入数据。

2. 生产者和消费者线程

2.1 生产者线程

  • 使用信号量P(sem_wait函数)操作进行空间的预定,若没有空间则进行等待

  • 加锁

  • 写入数据:将数据写入 tail 指向的位置。

  • 更新 tail:将 tail 向前移动一位:tail = (tail + 1) % size

  • 解锁

  • 释放一个数据,即消费者有新数据可用

2.2 消费者线程

  • 使用信号量V(sem_post函数)操作进行数据的预定,若没有数据则进行等待

  • 加锁

  • 读取数据:从 head 指向的位置读取数据。

  • 更新 head:将 head 向前移动一位:head = (head + 1) % size

  • 解锁

  • 释放一个空间,即生产者有空闲位置可用

3. 同步机制

为了确保生产者和消费者之间的同步,需要使用互斥锁(mutex)和信号量(semaphore)来控制对共享资源的访问。

  • 互斥锁:用于保护生产者线程和消费者线程,确保同一时间只有一个线程可以修改队列。

  • 信号量:

    • sem_wait:表示等待信号量,从信号量的值中减去1

    • sem_post:表示发布信号量,从信号量的值中加1, 并唤醒等待的信号量

// Sem.hpp
#pragma once
#include <semaphore.h>

int defaultvalue = 1;
class Sem
{
public:
    Sem(int value = defaultvalue)
    {
        int n = ::sem_init(&_sem, 0, value);
        (void)n;
    }
    void P()
    {
        int n = ::sem_wait(&_sem);
        (void)n;
    }
    void V()
    {
        int n = ::sem_post(&_sem);
        (void)n;
    }
    ~Sem()
    {
        int n = ::sem_destroy(&_sem);
        (void)n;
    }

private:
    sem_t _sem;        // 信号量
    int _init_value;
};
// RingBuffer.hpp
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include "Sem.hpp"

template <typename T>
class RingBuffer
{
public:
    RingBuffer(int size)
        : _ring(size),    // 给vector初始化一个大小
          _size(size),
          _tail(0),       // 生产者的默认位置
          _head(0),       // 消费者的默认位置
          _datasem(0),    // 默认数据为0
          _spacesem(size) // 默认空间为满
    {
        int n = ::pthread_mutex_init(&_p_lock, nullptr);
        (void)n;
        int m = ::pthread_mutex_init(&_c_lock, nullptr);
        (void)m;
    }
    // 生产者
    void Equeue(const T &in)
    {
        _spacesem.P(); // 预定一个空间
        // 上锁
        pthread_mutex_lock(&_p_lock);
        _ring[_tail] = in; // 生产
        _tail++;
        _tail %= _size; // 维持环形特性
        pthread_mutex_unlock(&_p_lock);

        _datasem.V(); // 释放一个数据
    }
    // 消费者
    void Pop(T *out)
    {
        _datasem.P(); // 预定一个数据
        // 上锁
        pthread_mutex_lock(&_c_lock);
        *out = _ring[_head]; // 取出数据
        _head++;
        _head %= _size;
        pthread_mutex_unlock(&_c_lock);

        _spacesem.V(); // 释放一个空间
    }
    ~RingBuffer()
    {
        int n = ::pthread_mutex_destroy(&_p_lock);
        (void)n;
        int m = ::pthread_mutex_destroy(&_c_lock);
        (void)m;
    }

private:
    std::vector<T> _ring;    // 环, 临界资源
    int _size;               // 总容量
    int _tail;               // 生产者位置
    int _head;               // 消费位置

    Sem _datasem;            // 数据信号量
    Sem _spacesem;           // 空间信号量
    pthread_mutex_t _p_lock; // 生产者锁
    pthread_mutex_t _c_lock; // 消费者锁
};
// Main.cc
#include "RingBuffer.hpp"
#include <string>
#include <unistd.h>

// 生产
void *Consumer(void *args)
{
    RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);
    int data = 0;
    while (true)
    {
        // sleep(1);
        ring_buffer->Equeue(data);
    
        std::cout << "生产了一个数据: " << data << std::endl;
        data++;
    }
    return nullptr;
}
// 消费
void *Productor(void *args)
{
    RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);

    while (true)
    {
        sleep(2);
        int data;
        ring_buffer->Pop(&data);
        std::cout << "消费了一个数据: " << data << std::endl;
    }
    return nullptr;
}

int main()
{
    RingBuffer<int> *ring_buffer = new RingBuffer<int>(5); // 共享资源 -> 临界资源
    pthread_t c1, p1, c2, c3, p2;
    pthread_create(&c1, nullptr, Consumer, ring_buffer);
    pthread_create(&c2, nullptr, Consumer, ring_buffer);
    pthread_create(&c3, nullptr, Consumer, ring_buffer);
    pthread_create(&p1, nullptr, Productor, ring_buffer);
    pthread_create(&p2, nullptr, Productor, ring_buffer);

    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);

    delete ring_buffer;
    return 0;
}

相关细节:

1.为了完成多生产和多消费的模型,消费者和生产者的同步和互斥问题我们的环形队列中已经解决。除此之外,我们还需要维护生产者和生产者 &&消费者和消费者之间的互斥关系。由于环形队列的下标也是属于临界资源的,如果不维持关系内部的互斥关系,是一定会破坏环形队列结构的。所以势必要引入生产者互斥锁和消费者互斥锁。

2.先加锁 or 先申请信号量

一定是先申请信号量,在加锁。先申请信号量,会让所有线程先瓜分好信号量,在其它线程进行等待锁的时候,此时资源已经申请好了,可以直接进入临界区;如果先加锁,在申请信号量,所有线程都会在锁上等待,等待完成之后在去申请信号量,这样解决问题的实际效率会变低。

3.使用信号量实现生产者消费者模型,对资源进行使用,申请时,为什么不判断一下条件是否满足?

信号量本身就是判断条件! 信号量:是一个计数器,是资源的预订机制。预订:可以理解为在外部,可以不判断资源是否满足,就可以知道内部资源的情况! 

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

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

相关文章

Qt u盘自动升级软件

Qt u盘自动升级软件 Chapter1 Qt u盘自动升级软件u盘自动升级软件思路&#xff1a;step1. 获取U盘 判断U盘名字是否正确&#xff0c; 升级文件是否存在。step2. 升级step3. 升级界面 Chapter2 Qt 嵌入式设备应用程序&#xff0c;通过U盘升级的一种思路Chapter3 在开发板上运行的…

拦截器快速入门及详解

拦截器Interceptor 快速入门 什么是拦截器&#xff1f; 是一种动态拦截方法调用的机制&#xff0c;类似于过滤器。 拦截器是Spring框架中提供的&#xff0c;用来动态拦截控制器方法的执行。 拦截器的作用&#xff1a;拦截请求&#xff0c;在指定方法调用前后&#xff0c;根…

信息安全专业优秀毕业设计选题汇总:热点选题

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整理…

Linux中使用unzip

安装命令 yum install unzip unzip常用选项和参数 选项 说明 -q 隐藏解压过程中的消息输出 -d /path/to/directory 指定解压文件的目标目录 -P password 如果.zip文件被密码保护&#xff0c;使用此选项可以指定打开文件所需的密码 解压命令 unzip 要解压的压缩包unz…

ThreadLocal源码解析

文章目录 一、概述二、get()方法三、set()方法四、可能导致的内存泄漏问题五、remove六、思考&#xff1a;为什么要将ThreadLocalMap的value设置为强引用&#xff1f; 一、概述 ThreadLocal是线程私有的&#xff0c;独立初始化的变量副本。存放在和线程进行绑定的ThreadLocalMa…

批量解密,再也没有任何限制了

有的时候我们在网上下载了PDF文档。发现没有办法进行任何的操作&#xff0c;就连打印权限都没有。今天给大家介绍的这个软件可以一键帮你进行PDF解密&#xff0c;非常方便&#xff0c;完全免费。 PDF智能助手 批量解密PDF文件 这个软件不是很大&#xff0c;只有10MB&#xff…

《LLM大语言模型+RAG实战+Langchain+ChatGLM-4+Transformer》

文章目录 Langchain的定义Langchain的组成三个核心组件实现整个核心组成部分 为什么要使用LangchainLangchain的底层原理Langchain实战操作LangSmithLangChain调用LLM安装openAI库-国内镜像源代码运行结果小结 使用Langchain的提示模板部署Langchain程序安装langserve代码请求格…

车载软件 --- 大一新生入门汽车零部件嵌入式开发

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

有效运作神经网络

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 训练集、验证集、测试集偏差、方差正则化正则化参数为什么正则化可以减少过拟合Dropout正则化Inverted Dropout其他的正则化方法数据增广Early stopping 归一化梯度消失与梯度爆…

【深度优先搜索篇】走迷宫的魔法:算法如何破解迷宫的神秘密码

当你在夜晚孤军奋战时&#xff0c;满天星光以为你而闪烁。 欢迎拜访&#xff1a;羑悻的小杀马特.-CSDN博客 本篇主题&#xff1a;轻轻松松拿捏洛谷走迷宫问题 制作日期&#xff1a;2024.12.31 隶属专栏&#xff1a;C/C题海汇总 首先我…

SQL进阶实战技巧:如何分析浏览到下单各步骤转化率及流失用户数?

目录 0 问题描述 1 数据准备 2 问题分析 3 问题拓展 3.1 跳出率计算 3.2 计算从浏览商品到支付订单的不同路径的用户数&#xff0c;并按照用户数降序排列。 往期精彩 0 问题描述 统计从浏览商品到最终下单的各个步骤的用户数和流失用户数,并计算转化率 用户表结构和…

Autosar-Os是怎么运行的?(内存保护)

写在前面&#xff1a; 入行一段时间了&#xff0c;基于个人理解整理一些东西&#xff0c;如有错误&#xff0c;欢迎各位大佬评论区指正&#xff01;&#xff01;&#xff01; 1.功能概述 以TC397芯片为例&#xff0c;英飞凌芯片集成了MPU模块&#xff0c; MPU模块采用了硬件机…

什么是Maxscript?为什么要学习Maxscript?

MAXScript是Autodesk 3ds Max的内置脚本语言,它是一种与3dsMax对话并使3dsMax执行某些操作的编程语言。它是一种脚本语言,这意味着您不需要编译代码即可运行。通过使用一系列基于文本的命令而不是使用UI操作,您可以完成许多使用UI操作无法完成的任务。 Maxscript是一种专有…

(一)QT的简介与环境配置WIN11

目录 一、QT的概述 二、QT的下载 三、简单编程 常用快捷键 一、QT的概述 简介 Qt&#xff08;发音&#xff1a;[kjuːt]&#xff0c;类似“cute”&#xff09;是一个跨平台的开发库&#xff0c;主要用于开发图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;…

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…

SpringCloudGateWay和Sentinel结合做黑白名单来源控制

假设我们的分布式项目&#xff0c;admin是8087&#xff0c;gateway是8088&#xff0c;consumer是8086 我们一般的思路是我们的请求必须经过我们的网关8088然后网关转发到我们的分布式项目&#xff0c;那我要是没有处理我们绕过网关直接访问项目8087和8086不也是可以&#xff1…

将多目标贝叶斯优化与强化学习相结合用于TinyML

论文标题 Combining Multi-Objective Bayesian Optimization with Reinforcement Learning for TinyML 作者信息 Mark Deutel, Friedrich-Alexander-Universitt Erlangen-Nrnberg, Germany Georgios Kontes, Fraunhofer IIS, Fraunhofer Institute for Integrated Circuits …

Big Bird:适用于更长序列的Transformer模型

摘要 基于Transformer的模型&#xff0c;如BERT&#xff0c;已成为自然语言处理&#xff08;NLP&#xff09;中最成功的深度学习模型之一。然而&#xff0c;它们的一个核心限制是由于其全注意力机制&#xff0c;对序列长度的二次依赖&#xff08;主要是在内存方面&#xff09;…

26_DropDown使用方法

创建下拉框DropDown 其中样板Template 是展示的选项框 其中Caption 是选中某个选项之后 展示的内容&#xff08;Caption Text 说明文字/Caption Image 说明图示&#xff09; 修改其 说明文字Caption Text 创建一个说明图示Image 设置为居左 而Item是 展示的选项框所展示的文字与…

【redis进阶】redis 总结

目录 介绍一下什么是 Redis&#xff0c;有什么特点 Redis 支持哪些数据类型 Redis 数据类型底层的数据结构/编码方式是什么 ZSet 为什么使用跳表&#xff0c;而不是使用红黑树来实现 Redis 的常见应用场景有哪些 怎样测试 Redis 服务器的连通性 如何设置 key 的过期时间 Redis …