当前位置: 首页 > article >正文

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

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

相关文章:

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

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

Github 2025-01-30 Go开源项目日报 Top10

根据Github Trendings的统计,今日(2025-01-30统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Go项目10Ollama: 本地大型语言模型设置与运行 创建周期:248 天开发语言:Go协议类型:MIT LicenseStar数量:42421 个Fork数量:2724 次关注人…...

FortiOS 存在身份验证绕过导致命令执行漏洞(CVE-2024-55591)

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

【Rust自学】17.2. 使用trait对象来存储不同值的类型

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 17.2.1. 需求 这篇文章以一个例子来介绍如何在Rust中使用trait对象来存储不同值的类型。 …...

毛选原文-实践论

实践论 论认识和实践的关系——知和行的关系 &#xff08;一九三七年七月&#xff09; 马克思以前的唯物论&#xff0c;离开人的社会性&#xff0c;离开人的历史发展&#xff0c;去观察认识问题&#xff0c;因此不能了解认识对社会实践的依赖关系&#xff0c;即认识对生产…...

PPT自动化 python-pptx -7: 占位符(placeholder)

占位符&#xff08;placeholder&#xff09;是演示文稿中用于容纳内容的预格式化容器。它们通过让模板设计者定义格式选项&#xff0c;简化了创建视觉一致幻灯片的过程&#xff0c;同时让最终用户专注于添加内容。这加快了演示文稿的开发速度&#xff0c;并确保幻灯片之间的外观…...

VLLM性能调优

1. 抢占 显存不够的时候&#xff0c;某些request会被抢占。其KV cache被清除&#xff0c;腾退给其他request&#xff0c;下次调度到它&#xff0c;重新计算KV cache。 报这条消息&#xff0c;说明已被抢占&#xff1a; WARNING 05-09 00:49:33 scheduler.py:1057 Sequence gr…...

Java线程认识和Object的一些方法

本文目标&#xff1a; 要对Java线程有整体了解&#xff0c;深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor&#xff0c;这有助于后面的Synchronized和锁的认识。利用Synchronized wait/notify 完成一道经典的多线程题目&#xff1a;实现ABC…...

数据库管理-第287期 Oracle DB 23.7新特性一览(20250124)

数据库管理287期 2025-01-24 数据库管理-第287期 Oracle DB 23.7新特性一览&#xff08;20250124&#xff09;1 AI向量搜索&#xff1a;算术和聚合运算2 更改Compatible至23.6.0&#xff0c;以使用23.6或更高版本中的新AI向量搜索功能3 Cloud Developer包4 DBMS_DEVELOPER.GET_…...

LruCache实现

LRU最近最少使用算法 一、LRU算法 1.简介 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;算法是一种常用的缓存淘汰策略&#xff0c;当缓存达到其容量上限时&#xff0c;它会移除那些最久没有被访问的数据项。这种策略基于这样一个假设&#xff1…...

DDD架构实战第五讲总结:将领域模型转化为代码

云架构师系列课程之DDD架构实战第五讲总结:将领域模型转化为代码 一、引言 在前几讲中,我们讨论了领域模型的重要性及其在业务分析中的渐进获得方法。本讲将聚焦于如何将领域模型转化为代码,使得开发人员能够更轻松地实现用户的领域模型。 二、从模型到代码:领域驱动设计…...

【MySQL】MySQL客户端连接用 localhost和127.0.0.1的区别

# systemctl status mysqld # ss -tan | grep 3306 # mysql -V localhost与127.0.0.1的区别是什么&#xff1f; 相信有人会说是本地IP&#xff0c;曾有人说&#xff0c;用127.0.0.1比localhost好&#xff0c;可以减少一次解析。 看来这个入门问题还有人不清楚&#xff0c;其实…...

蓝桥杯例题五

无论你面对多大的困难和挑战&#xff0c;都要保持坚定的信念和积极的态度。相信自己的能力和潜力&#xff0c;努力不懈地追求自己的目标和梦想。不要害怕失败&#xff0c;因为失败是成功的垫脚石。相信自己的选择和决策&#xff0c;不要被他人的意见和批评左右。坚持不懈地努力…...

DeepSeek R1本地部署详细指南

DeepSeek R1 是由中国 AI 初创公司深度求索开发的先进推理模型&#xff0c;其性能在数学、编码和逻辑推理等任务上表现出色。在本地部署该模型可以带来更低的延迟、更高的隐私性以及对 AI 应用的更大控制权。本文将详细介绍如何在本地环境中部署 DeepSeek R1 模型。 前提条件 …...

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…...

爬虫基础(五)爬虫基本原理

目录 一、爬虫是什么 二、爬虫过程 &#xff08;1&#xff09;获取网页 &#xff08;2&#xff09;提取信息 &#xff08;3&#xff09;保存数据 三、爬虫可爬的数据 四、爬虫问题 一、爬虫是什么 互联网&#xff0c;后面有个网字&#xff0c;我们可以把它看成一张蜘蛛网…...

【Block总结】HWD,小波下采样,适用分类、分割、目标检测等任务|即插即用

论文信息 Haar wavelet downsampling (HWD) 是一项针对语义分割的创新模块&#xff0c;旨在通过减少特征图的空间分辨率来提高深度卷积神经网络&#xff08;DCNNs&#xff09;的性能。该论文的主要贡献在于提出了一种新的下采样方法&#xff0c;能够在下采样阶段有效地减少信息…...

【解决方案】MuMu模拟器移植系统进度条卡住98%无法打开

之前在Vmware虚拟机里配置了mumu模拟器&#xff0c;现在想要移植到宿主机中 1、虚拟机中的MuMu模拟器12-1是目标系统&#xff0c;对应的目录如下 C:\Program Files\Netease\MuMu Player 12\vms\MuMuPlayer-12.0-1 2、Vmware-虚拟机-设置-选项&#xff0c;启用共享文件夹 3、复…...

力扣面试150 快乐数 循环链表找环 链表抽象 哈希

Problem: 202. 快乐数 &#x1f469;‍&#x1f3eb; 参考题解 Code public class Solution {public int squareSum(int n) {int sum 0;while(n > 0){int digit n % 10;sum digit * digit;n / 10;}return sum;}public boolean isHappy(int n) {int slow n, fast squa…...

安卓(android)实现注册界面【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握LinearLayout、RelativeLayout、FrameLayout等布局的综合使用。 2.掌握ImageView、TextView、EditText、CheckBox、Button、RadioGroup、RadioButton、ListView、RecyclerView等控件在项目中的…...

SpringSecurity:There is no PasswordEncoder mapped for the id “null“

文章目录 一、情景说明二、分析三、解决 一、情景说明 在整合SpringSecurity功能的时候 我先是去实现认证功能 也就是&#xff0c;去数据库比对用户名和密码 相关的类&#xff1a; UserDetailsServiceImpl implements UserDetailsService 用于SpringSecurity查询数据库 Logi…...

微服务入门(go)

微服务入门&#xff08;go&#xff09; 和单体服务对比&#xff1a;里面的服务仅仅用于某个特定的业务 一、领域驱动设计&#xff08;DDD&#xff09; 基本概念 领域和子域 领域&#xff1a;有范围的界限&#xff08;边界&#xff09; 子域&#xff1a;划分的小范围 核心域…...

996引擎 - NPC-动态创建NPC

996引擎 - NPC-动态创建NPC 创建脚本服务端脚本客户端脚本添加自定义音效添加音效文件修改配置参考资料有个小问题,创建NPC时没有控制朝向的参数。所以。。。自己考虑怎么找补吧。 多重影分身 创建脚本 服务端脚本 Mir200\Envir\Market_Def\test\test001-3.lua -- NPC八门名…...

使用 MySQL JSON 查询筛选嵌套字段的值

在日常开发中&#xff0c;随着项目需求的不断复杂化&#xff0c;许多表字段可能会存储 JSON 格式的数据。例如&#xff0c;我们有一张 site_device 表&#xff0c;其中有一个名为 detail 的字段&#xff0c;保存了设备的详细信息。这些信息存储为 JSON 数据&#xff0c;如下所示…...

go-zero学习笔记(一)

基础环境搭建 安装go环境 网上文章比较多&#xff0c;不在赘述&#xff0c;我当时参考的文章是&#xff1a;https://blog.csdn.net/weixin_41287260/article/details/143661816 记得修改go env 中的环境变量&#xff0c; 主要是goproxy 改成七牛云的&#xff0c;这样下载代码库…...

maven、npm、pip、yum官方镜像修改文档

文章目录 Maven阿里云网易华为腾讯云 Npm淘宝腾讯云 pip清华源阿里中科大华科 Yum 由于各博客繁杂&#xff0c;本文旨在记录各常见镜像官网&#xff0c;及其配置文档。常用镜像及配置可评论后加入 Maven 阿里云 官方文档 setting.xml <mirror><id>aliyunmaven&l…...

【Docker】私有Docker仓库的搭建

一、准备工作 确保您的系统已安装Docker。如果没有安装&#xff0c;请参考Docker官方文档进行安装。 准备一个用于存储仓库数据的目录&#xff0c;例如/registry_data/。 二、拉取官方registry镜像 首先&#xff0c;我们需要从Docker Hub拉取官方的registry镜像。执行以下命…...

基于MinIO的对象存储增删改查

MinIO是一个高性能的分布式对象存储服务。Python的minio库可操作MinIO&#xff0c;包括创建/列出存储桶、上传/下载/删除文件及列出文件。 查看帮助信息 minio.exe --help minio.exe server --help …...

观察者模式和订阅发布模式的关系

有人把观察者模式等同于发布订阅模式&#xff0c;也有人认为这两种模式存在差异&#xff0c;本质上就是调度的方法不同。 发布订阅模式: 观察者模式: 相比较&#xff0c;发布订阅将发布者和观察者之间解耦。&#xff08;发布订阅有调度中心处理&#xff09;...

(2025 年最新)MacOS Redis Desktop Manager中文版下载,附详细图文

MacOS Redis Desktop Manager中文版下载 大家好&#xff0c;今天给大家带来一款非常实用的 Redis 可视化工具——Redis Desktop Manager&#xff08;简称 RDM&#xff09;。相信很多开发者都用过 Redis 数据库&#xff0c;但如果你想要更高效、更方便地管理 Redis 数据&#x…...