【Linux】多线程 -> 线程同步与基于BlockingQueue的生产者消费者模型
线程同步
条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如:一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
同步概念
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。
条件变量函数
初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond);
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒一批线程。
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个线程。
示例代码:
makefile:
testCond:testCond.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f testCond
testCond.cc:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>int tickets = 1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void *start_routine(void *args)
{std::string name = static_cast<const char *>(args);while (true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);// 判断暂时省略std::cout << name << "->" << tickets << std::endl;tickets--;pthread_mutex_unlock(&mutex);}
}int main()
{// 通过条件变量控制线程的执行pthread_t t[4];for (int i = 0; i < 4; i++){char *name = new char[64];snprintf(name, 64, "thread %d", i + 1);pthread_create(t + i, nullptr, start_routine, (void *)name);}while (true){sleep(1);//pthread_cond_broadcast(&cond); // 唤醒一批线程pthread_cond_signal(&cond);//唤醒一个线程std::cout << "main thread wakeup one thread... " << std::endl;}for (const auto &i : t){pthread_join(i, nullptr);}return 0;
}
pthread_cond_signal:唤醒一个线程。 pthread_cond_broadcast:唤醒一批线程。
这些线程会持续等待一个条件变量的信号。主线程每隔 1 秒就会发送一个条件变量信号,唤醒其中一个等待的线程。被唤醒的线程会输出当前剩余的票数并将票数减 1。
可以看到,由于条件变量的存在,输出结果变得有顺序性。
- 为什么 pthread_cond_wait 需要互斥量?
1. 保证条件检查和等待操作的原子性
在多线程环境中,线程需要先检查某个条件是否满足,如果不满足则进入等待状态。这个检查条件和进入等待的操作必须是原子的,否则可能会出现竞态条件。
例如,在生产者 - 消费者模型中,消费者线程需要检查缓冲区是否为空,如果为空则等待。假设没有互斥量保护,可能会出现以下情况:
消费者线程检查到缓冲区为空,准备进入等待状态。
在消费者线程真正进入等待状态之前,生产者线程往缓冲区中添加了数据,并发出了条件变量的通知。
消费者线程此时才进入等待状态,由于之前通知已经发出,消费者线程可能会一直等待下去,导致程序出现错误。
使用互斥量可以保证条件检查和进入等待状态这两个操作的原子性。当线程调用pthread_cond_wait时,它会先释放互斥量,然后进入等待状态;当被唤醒时,又会重新获取互斥量。这样就避免了上述竞态条件的发生。
2. 保护共享资源和条件变量
条件变量通常与共享资源相关联,线程在检查条件和修改共享资源时需要保证线程安全。互斥量可以用来保护这些共享资源,确保同一时间只有一个线程能够访问和修改它们。
在调用pthread_cond_wait之前,线程需要先获取互斥量,这样可以保证在检查条件和进入等待状态时,其他线程不会同时修改共享资源和条件变量。当线程被唤醒后,再次获取互斥量,又可以保证在处理共享资源时的线程安全。
生产者消费者模型
- 为何要使用生产者消费者模型?
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
“321”原则(便于记忆)
- 3种关系:生产者和生产者(互斥关系)、消费者和消费者(互斥关系)、生产者和消费者(互斥、同步)。
- 2种角色:生产者线程和消费者线程。
- 1种交易场所:一段特定结构的缓冲区。
优点
- 解耦:生产线程和消费线程解耦。
- 支持忙闲不均:生产和消费的一段时间的忙闲不均。
- 提高效率:支持并发。
基于BlockingQueue的生产消费模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。
下面我们以单生产者,单消费者为例:
makefile:
MainCp:MainCp.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f MainCp
BlockQueue.hpp:
#include <iostream>
#include <queue>
#include <pthread.h>const int gmaxcap = 5;template <class T>
class BlockQueue
{
public:BlockQueue(const int &maxcap = gmaxcap) : _maxcap(maxcap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_pcond, nullptr);pthread_cond_init(&_ccond, nullptr);}void push(const T &in) // 输入型参数,const &;输出型参数 *;输入输出型参数 &;{pthread_mutex_lock(&_mutex);// 1.判断while (is_full())// if(is_full())//细节2:充当判断的语法必须是while,不能是if,因为在被唤醒时,有可能存在异常或伪唤醒。{// 细节1:pthread_cond_wait是在临界区啊。// pthread_cond_wait的第二个参数,必须是我们正在使用的互斥锁。// a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起。// b.pthread_cond_wait: 该函数在被唤醒返回的时候,会自动的重新获取你传入的锁。pthread_cond_wait(&_pcond, &_mutex); // 因为生产条件不满足,无法生产,生产者进行等待。}// 2.走到这里,一定是没有满的。_q.push(in);// 3.一定能保证阻塞队列里有数据。// 细节3:pthread_cond_signal:可以放在临界区内部,也可以放在外部。pthread_cond_signal(&_ccond); // 唤醒消费者消费。这里可以有一定的策略。pthread_mutex_unlock(&_mutex);// pthread_cond_siganl(&_ccond);}void pop(T *out){pthread_mutex_lock(&_mutex);// 1.判断while (is_empty())// if(is_empty()){pthread_cond_wait(&_ccond, &_mutex); // 因为消费条件不满足,无法消费,消费者进行等待。}// 2.走到这里,一定是不为空的。*out = _q.front();_q.pop();// 3.一定能保证阻塞队列里至少有一个空位置。pthread_cond_signal(&_pcond); // 唤醒生产者生产。这里可以有一定的策略。pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size() == _maxcap;}private:std::queue<T> _q;int _maxcap; // 队列中元素的上限pthread_mutex_t _mutex;pthread_cond_t _pcond; // 生产者对应的条件变量pthread_cond_t _ccond; // 消费者对应的条件变量
};
MainCp.cc:
#include "BlockQueue.hpp"
#include <ctime>
#include <sys/types.h>
#include <unistd.h>void *consumer(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data;bq->pop(&data);std::cout << "消费数据:" << data << std::endl;}return nullptr;
}
void *productor(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data = rand() % 10 + 1; // 这里我们先用一个随机数构建一个数据。bq->push(data);std::cout << "生产数据:" << data << std::endl;}return nullptr;
}int main()
{srand((unsigned long)time(nullptr) ^ getpid());BlockQueue<int> *bq = new BlockQueue<int>();pthread_t c, p;// 生产消费要看到同一份资源,也就是阻塞队列pthread_create(&c, nullptr, consumer, bq);pthread_create(&p, nullptr, productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0;
}
如果不加控制,生产消费就会疯狂的执行,没有顺序。
- 你怎么证明它是一个阻塞队列呢?
让生产者每隔一秒生产一个,消费者一直消费。那么最终的预期结果就是生产一个,消费一个;生产一个,消费一个。
void *consumer(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data;bq->pop(&data);std::cout << "消费数据:" << data << std::endl;}return nullptr;
}
void *productor(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data = rand() % 10 + 1; // 这里我们先用一个随机数构建一个数据。bq->push(data);std::cout << "生产数据:" << data << std::endl;sleep(1);}return nullptr;
}
让消费者每隔一秒消费一个,生产者一直生产。那么最终的预期结果就是消费一个,生产一个;消费一个,生产一个。
void *consumer(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data;bq->pop(&data);std::cout << "消费数据:" << data << std::endl;sleep(1);}return nullptr;
}
void *productor(void *bq_)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(bq_);while (true){// 生产活动int data = rand() % 10 + 1; // 这里我们先用一个随机数构建一个数据。bq->push(data);std::cout << "生产数据:" << data << std::endl;}return nullptr;
}
这就是基于阻塞队列的生产消费模型。
上面我们阻塞队列里放的就是一个整形数据,我们可以再完善一下。我们是可以直接在阻塞队列中放任务的。让生产者给消费者派发任务。
makefile:
MainCp:MainCp.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f MainCp
BlockQueue.hpp:
#include <iostream>
#include <queue>
#include <pthread.h>const int gmaxcap = 5;template <class T>
class BlockQueue
{
public:BlockQueue(const int &maxcap = gmaxcap) : _maxcap(maxcap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_pcond, nullptr);pthread_cond_init(&_ccond, nullptr);}void push(const T &in) // 输入型参数,const &;输出型参数 *;输入输出型参数 &;{pthread_mutex_lock(&_mutex);// 1.判断while (is_full())// if(is_full())//细节2:充当判断的语法必须是while,不能是if,因为在被唤醒时,有可能存在异常或伪唤醒。eg:一个生产者十个消费者,broadcast唤醒。{// 细节1:pthread_cond_wait是在临界区啊。// pthread_cond_wait的第二个参数,必须是我们正在使用的互斥锁。// a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起。// b.pthread_cond_wait: 该函数在被唤醒返回的时候,会自动的重新获取你传入的锁。pthread_cond_wait(&_pcond, &_mutex); // 因为生产条件不满足,无法生产,生产者进行等待。}// 2.走到这里,一定是没有满的。_q.push(in);// 3.一定能保证阻塞队列里有数据。// 细节3:pthread_cond_signal:可以放在临界区内部,也可以放在外部。pthread_cond_signal(&_ccond); // 唤醒消费者消费。这里可以有一定的策略。pthread_mutex_unlock(&_mutex);// pthread_cond_siganl(&_ccond);}void pop(T *out){pthread_mutex_lock(&_mutex);// 1.判断while (is_empty())// if(is_empty()){pthread_cond_wait(&_ccond, &_mutex); // 因为消费条件不满足,无法消费,消费者进行等待。}// 2.走到这里,一定是不为空的。*out = _q.front();_q.pop();// 3.一定能保证阻塞队列里至少有一个空位置。pthread_cond_signal(&_pcond); // 唤醒生产者生产。这里可以有一定的策略。pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size() == _maxcap;}private:std::queue<T> _q;int _maxcap; // 队列中元素的上限pthread_mutex_t _mutex;pthread_cond_t _pcond; // 生产者对应的条件变量pthread_cond_t _ccond; // 消费者对应的条件变量
};
Task.hpp:
#pragma once#include <iostream>
#include <cstdio>
#include <functional>class Task
{using func_t = std::function<int(int, int, char)>;// typedef std::function<int(int,int,char)>func_t;
public:Task(){}Task(int x, int y, char op, func_t func): _x(x), _y(y), _op(op), _callback(func){}std::string operator()(){int result = _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);return buffer;}private:int _x;int _y;char _op;func_t _callback;
};
MainCp.cc:
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <ctime>
#include <sys/types.h>
#include <unistd.h>const std::string oper = "+-*/%";int mymath(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}else{result = x / y;}}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}else{result = x % y;}}break;}return result;
}void *consumer(void *bq_)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(bq_);while (true){// 消费活动Task t;bq->pop(&t);std::cout << "消费任务:" << t() << std::endl;//sleep(1);}return nullptr;
}
void *productor(void *bq_)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(bq_);while (true){// 生产活动int x = rand() % 100 + 1; // 这里我们先用一个随机数构建一个数据。int y = rand() % 10;int operCode = rand() % oper.size();Task t(x, y, oper[operCode], mymath);bq->push(t);std::cout << "生产任务:" << t.toTaskString() << std::endl;sleep(1);}return nullptr;
}int main()
{srand((unsigned long)time(nullptr) ^ getpid());BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c, p;// 生产消费要看到同一份资源,也就是阻塞队列pthread_create(&c, nullptr, consumer, bq);pthread_create(&p, nullptr, productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0;
}
让生产者sleep1秒,看到的结果就是生产一个任务,消费一个任务。
让消费者sleep1秒,看到的结果就是消费一个任务,生产一个任务。
这样,我们就完成了一个线程给另一个线程派发任务:生产者给消费者派发任务。
- 上面是单生产者,单消费者,如果我们直接改成多个生产者多个消费者可以吗?
MainCp.cc:
//
//...
//...
int main()
{srand((unsigned long)time(nullptr) ^ getpid());BlockQueue<Task> *bq = new BlockQueue<Task>();pthread_t c, c1, p, p1, p2;// 生产消费要看到同一份资源,也就是阻塞队列pthread_create(&p, nullptr, productor, bq);pthread_create(&p1, nullptr, productor, bq);pthread_create(&p2, nullptr, productor, bq);pthread_create(&c1, nullptr, consumer, bq);pthread_create(&c, nullptr, consumer, bq);pthread_join(c, nullptr);pthread_join(c1, nullptr);pthread_join(p, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);delete bq;return 0;
}
可以看到是可以的。无论外部的线程再多,真正进入到阻塞队列里生产或消费的线程永远只有一个。
生产者要向blockqueue里放任务,消费者要向blockqueue里取任务。由于有锁的存在,这个(生产过程和消费过程)过程是串行的,也就是blockqueue里任何时刻只有一个执行流。那么:
- 那么生产者消费者模型高效在哪里呢?创建多线程生产和消费的意义是什么呢?
1、对于生产者而言,它获取数据构建任务,是需要花时间的。
- 如果这个任务的构建非常耗时,这个线程(生产者)在构建任务的同时,其他线程(生产者)可以并发的继续构建任务。
2、对于消费者而言,它拿到任务以后,是需要花时间处理这个任务的!
- 如果这个任务的处理非常耗时,这个线程(消费者)在处理任务的同时,其他线程(消费者)可以并发的继续从阻塞队列里拿任务处理。
所以,高效并不体现在生产者把任务放进阻塞队列里高效,或者消费者从阻塞队列里拿任务高效。而是,体现在多个线程可以同时并发的构建或处理任务。
对于单生产单消费,它的并发性体现在,消费者从阻塞队列里拿任务和生产者构建任务,或者生产者往阻塞队列里放任务和消费者处理任务的过程是并发的。
总结:生产消费模型高效体现在,可以在生产前,和消费之后,让线程并行执行。
创建多线程生产和消费的意义:
多个线程可以并发生产,并发消费。
以上就是线程同步和基于阻塞队列的生产者消费者模型。
相关文章:
【Linux】多线程 -> 线程同步与基于BlockingQueue的生产者消费者模型
线程同步 条件变量 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。 例如:一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这…...
Openssl交叉编译
在 OpenSSL 交叉编译中,linux-aarch64是一个用于指定目标平台的配置选项,表示目标是 X86 架构的 64位系统。这个选项可以从 OpenSSL 的 ./Configure 命令支持的平台列表中获取。 你可以通过运行以下命令查看 OpenSSL 支持的所有平台配置选项:…...
基于Ubuntu系统的docker环境对MySQL8.0.36主从部署
1. 环境准备 1、1 前言 本文基于Ubuntu系统的docker环境对MySQL8.0.36进行2台物理服务器的主从部署。1.2 服务器硬件环境准备 准备2台物理服务器192.168.8.6 和 192.168.8.9。 2台物理服务器在同一个局域网环境下,可以相互网络通信。 服务器操作系统版本:Ubuntu 24.04.1 L…...
吃一堑长一智
工作中经历,有感触记录下 故事一 以前在一家公司时,自己是一名开发人员,遇到问题请教领导解决方案,当时领导给了建议,后来上线后出问题了,背了锅。心里想的是领导说这样做的呀,为什么出问题还…...
详解Nginx 配置
一、Nginx 介绍 Nginx 是一款轻量级的 Web 服务器 / 反向代理服务器及电子邮件(IMAP/POP3)代理服务器。它由俄罗斯的程序设计师 Igor Sysoev 所开发,自 2004 年发布以来,凭借其高性能、低内存消耗、高并发处理能力等特点…...
《深度揭秘:DeepSeek如何解锁自然语言处理密码》
在人工智能蓬勃发展的当下,自然语言处理(NLP)成为了连接人类与机器的关键桥梁。作为该领域的佼佼者,DeepSeek以其卓越的语义理解和生成能力,备受瞩目。今天,就让我们深入探寻DeepSeek在自然语言处理中实现语…...
备战蓝桥杯 -牛客
习题-[NOIP2006]明明的随机数 1046-习题-[NOIP2006]明明的随机数_2021秋季算法入门班第一章习题:模拟、枚举、贪心 思路:这道题用stl的set,今天写这道题复习了一下set的用法: s.find(a) s.end()的意思是判断元素a是否存在于集…...
Pandas:从一个DataFrame中直接索引赋值到另一个索引位置出错的Bug及其解决方案
这里写自定义目录标题 动机代码1,这个代码是有问题的代码2,这个代码是我调试代码。拆分代码,最后找到问题所在,这个代码是正确的代码3。本以为找到问题所在之后,又稍微修改了下代码 2,这个代码还是没问题的…...
电脑想安装 Windows 11 需要开启 TPM 2.0 怎么办?
尽管 TPM 2.0 已经内置在许多新电脑中,但很多人并不知道如何激活这一功能,甚至完全忽略了它的存在。其实,只需简单的几步操作,你就能开启这项强大的安全特性,为你的数字生活增添一层坚固的防护屏障。无论你是普通用户还…...
请谈谈 Vue 中的响应式原理,如何实现?
一、Vue2响应式原理:Object.defineProperty的利与弊 实现原理: // 数据劫持核心实现 function defineReactive(obj, key, val) {const dep new Dep(); // 依赖收集容器Object.defineProperty(obj, key, {get() {if (Dep.target) { // 当前Watcher实例…...
深入解析浏览器渲染全流程:从URL输入到页面渲染的底层原理与性能优化(附实战代码)
本文以https://example.com为例,逐层剖析浏览器从输入URL到页面渲染的完整链路,涵盖DNS解析、TCP/TLS握手、HTTP请求、DOM/CSSOM构建等核心阶段,结合代码示例与性能调优技巧,助你掌握浏览器底层运行机制。 一、导航阶段࿱…...
vue 识别 <think></think>
在 Vue.js 中处理自定义标签或者在 HTML 中嵌入特定标记(例如 )通常涉及到两个方面:模板语法和组件化。 模板语法 Vue 使用基于 HTML 的模板语法来声明式地将 DOM 绑定至底层数据。默认情况下,Vue 会忽略未知元素,除非…...
Three.js 快速入门教程【一】开启你的 3D Web 开发之旅
系列文章目录 Three.js 快速入门教程【一】开启你的 3D Web 开发之旅 Three.js 快速入门教程【二】透视投影相机 Three.js 快速入门教程【三】渲染器 Three.js 快速入门教程【四】三维坐标系 Three.js 快速入门教程【五】动画渲染循环 Three.js 快速入门教程【六】相机控件 Or…...
ubuntu 执行 sudo apt-get update 报错
记录一下,遇到这个问题了,网络上看到的解决办法,亲测有效 执行sudo apt-get update ,却报以下错误,“SECURITY: URL redirect target contains control characters rejecting ” 经检查发现,/etc/apt/source.list 下的…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-loaders.py
loaders.py ultralytics\data\loaders.py 目录 loaders.py 1.所需的库和模块 2.class SourceTypes: 3.class LoadStreams: 4.class LoadScreenshots: 5.class LoadImagesAndVideos: 6.class LoadPilAndNumpy: 7.class LoadTensor: 8.def autocast_list(source…...
ubuntu24基于虚拟机无法从主机拖拽文件夹
以下是解决问题的精简步骤: 安装 open-vm-tools-desktop: bash复制 sudo apt-get install open-vm-tools-desktop 重启虚拟机后,文字复制粘贴功能可正常工作。 禁用 Wayland: 编辑 /etc/gdm3/custom.conf 文件: bash复…...
二叉树(数据结构)
二叉树 二叉树也是用过递归定义的结构 先序遍历又称前序遍历 按照先序遍历的方法去手算处理这个二叉树 先A B C 再 A B D E C(也就是把B换成BDE再放进去) 再 A B D E C F 看这个插入的方法要掌握像二叉树这样向一个…...
Vue 实现通过URL浏览器本地下载 PDF 和 图片
1、代码实现如下: 根据自己场景判断 PDF 和 图片,下载功能可按下面代码逻辑执行 const downloadFile async (item: any) > {try {let blobUrl: any;// PDF本地下载if (item.format pdf) {const response await fetch(item.url); // URL传递进入i…...
MySQL版本选择与安装
MySQL版本选择与安装 MySQL 5.5 优点: 稳定性:5.5版本是长期支持(LTS)版本,因此它非常稳定,被广泛部署在生产环境中。 兼容性:与旧版本的MySQL和各种应用程序有很好的兼容性。 缺点: 过时:…...
Python网络爬虫技术详解文档
Python网络爬虫技术详解文档 目录 网络爬虫概述爬虫核心技术解析常用Python爬虫库实战案例演示反爬虫机制与应对策略爬虫法律与道德规范高级爬虫技术资源推荐与学习路径1. 网络爬虫概述 1.1 什么是网络爬虫 网络爬虫(Web Crawler)是一种按特定规则自动抓取互联网信息的程序…...
STM32 CubeMx配置串口收发使用DMA并调用Idle模式(二)
本篇主要结合代码落实,之前串口已经配置好的DMA方式。 一、首先我们把串口看成一个对象,它有属性、私有数据和方法; 每个串口都有名字属性;有初始化、发送、接收方法;还有一个私有数据(这个私有数据是每个…...
现代游戏UI架构深度解析——以UIController为核心的模块化界面管理系统
一、架构全景与设计哲学 本文将以重构后的UIController为核心,深入探讨Unity引擎下的高效UI管理方案。该体系采用"分层-分治"设计理念,通过界面生命周期管理、动态适配策略、资源优化机制三个维度的协同工作,构建了适应复杂交互需…...
DeepSeek 助力 Vue 开发:打造丝滑的点击动画(Click Animations)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
**ARM Cortex-M4** 和 **ARM Cortex-M7* 运行freeRTOS
**是的,Cortex-M4 和 Cortex-M7 都可以运行 FreeRTOS**。FreeRTOS 是一个轻量级的实时操作系统(RTOS),专为嵌入式系统设计,支持多种硬件架构,包括 ARM Cortex-M 系列(如 Cortex-M0、M3、M4、M7 …...
### net7 + 出现了 自带的 限流中间件 固定窗口、滑动窗口 并发 令牌桶 全局限流器
资料 限流的方法 速率限制算法 固定窗口算法 是最简单的算法之一。它将请求限制为一个固定的时间窗口,该窗口在任何时间点都只允许固定数量的请求。 滑动窗口算法 是固定窗口算法的改进版本,它将请求限制为一个可变的窗口,该窗口在任何时间…...
【Python】迭代器与生成器详解,附代码(可迭代对象、定义、实现方式、区别、使用场景)
文章目录 1. 可迭代对象1.1 常见的可迭代对象1.2 迭代器和生成器 2. 迭代器2.1 定义2.2 原理2.3 特点2.4 示例2.4.1 for语句进行遍历2.4.2 next() 函数进行遍历2.4.3 自定义迭代器 2.5 内置迭代器 3. 生成器3.1 定义3.2 创建方式3.2.1 生成器表达式3.2.2 生成器函数 3.3 特点 4…...
05.Docker 容器命令
Docker 容器命令 Docker 容器命令1. 启动容器用法2. 显示当前存在容器3. 查看容器内的进程4. 查看容器资源使用情况5. 查看容器的详细信息6. 删除容器7. 容器的启动和停止8. 给正在运行的容器发信号9. 进入正在运行的容器10. 暴露所有容器端口11. 指定端口映射12. 查看容器的日…...
猿大师办公助手对比其他WebOffice在线编辑Office插件有什么优势
1. 原生Office功能完整嵌入,排版一致性保障 猿大师办公助手直接调用本地安装的微软Office、金山WPS或永中Office,支持所有原生功能(如复杂公式、VBA宏等),确保网页编辑与本地打开的文档排版完全一致。 提供OLE嵌入和完…...
Ubuntu搭建RTSP服务器
下载 http://www.live555.com/liveMedia/public/ 安装ffmpeg sudo apt install -y ffmpeg 转换文件(必须!) ffmpeg -i test.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 test.264编译 ./genMakefiles linux-64bit make 启动服务器…...
视觉分析之边缘检测算法
9.1 Roberts算子 Roberts算子又称为交叉微分算法,是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。 常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。 其缺点是对边缘的定位…...
14.2 Auto-GPT 开源项目深度解析:从代码架构到二次开发实践
Auto-GPT 开源项目深度解析:从代码架构到二次开发实践 关键词:Auto-GPT 源码解读、自主智能体开发、开源项目贡献指南、智能体扩展开发、AI 代理技术栈 一、Auto-GPT 项目全景速览 1.1 项目定位与技术定位 GitHub 数据亮眼: 🌟 Star 数:98k+(截至2024年3月)🚀 贡献…...
Java中的常用类 --String
学习目标 掌握String常用方法掌握StringBuilder、StringBuffer了解正则 1.String ● String是JDK中提前定义好的类型 其所在的包是java.lang ,String翻译过来表示字符串类型,也就是说String类中已经提前定义好了很多方法都是用来处理字符串的,所以Str…...
手机控制电脑远程关机
远程看看软件兼容iOS和Android设备,该软件除了能通过电脑远程关闭另一台电脑外,您还可以通过它在手机上远程关闭公司的电脑。您可以按照以下步骤进行操作以实现电脑远程关机: 步骤1.在手机应用商店搜索“远程看看”进行软件安装,…...
用DeepSeeker + AI 进行物料主数据编码规则学习训练
AI 进行物料主数据编码规则学习训练 作为需要通过AI赋能SAP用户,如何通过DeepSeeker AI帮助SAP进行物料主数据的学习和规则验证 一、核心场景与痛点分析 SAP物料主数据管理挑战 数据质量问题 字段值错误(如单位错误、分类错误)重复数据&#…...
ES6相关操作(2)
一.Promise Promise是ES6引入的异步编程工具。 语法上Promise是一个构造函数,用于封装异步操作并可以获取操作成功或失败的结果 Promise构造函数:Promise(excutor){} Promise的常用函数:then,catch 实例化Promise对象(创建Promise工具) let data"请求数据"//该数据为…...
Java中JDK、JRE,JVM之间的关系
Java中的JDK、JRE和JVM是三个核心概念,其关系可概括为JDK > JRE > JVM,具体如下: 一、定义与作用 JDK(Java Development Kit) 定义:Java开发工具包,用于开发和编译Java程序。包含内容&…...
HTTP SSE 实现
参考: SSE协议 SSE技术详解:使用 HTTP 做服务端数据推送应用的技术 一句概扩 SSE可理解为:服务端和客户端建立连接之后双方均保持连接,但仅支持服务端向客户端推送数据。推送完毕之后关闭连接,无状态行。 下面是基于…...
51单片机介绍
1、单片机基础知识 1.1、单板机 将CPU芯片、存储器芯片、I/O接口芯片和简单的I/O设备(小键盘、LED显示器)等装配到一块印刷电路板上,再配上监控程序(固化在ROM中),就构成了一台单板微型计算机(简称单板机)。 1.2、单片机 在一片集成电路芯片上集成微处理器、存储器…...
Github访问不了解决方案(Mac)
电脑又重装了,到Github下载东西,又访问不了,记录一下解决方案:修改hosts文件。 补充 1、确定可以访问Github的IP地址 打开多个地点ping[github.com]服务器-网站测速-站长工具 输入github.com,点击Ping检测ÿ…...
机器视觉3D深度图颜色含义解析
在机器视觉中,3D深度图颜色变化通常表示以下含义: 1.深度信息变化 颜色深浅:颜色越深,物体越近;颜色越浅,物体越远。 颜色渐变:平滑的渐变表示深度连续变化,突变则表示深度不连续。 …...
鸡兔同笼问题
鸡兔同笼问题是这样一个问题: 现有鸡、兔合装在一个笼子里。数头一共100个头,数脚一共300只脚。问有多少只鸡多少只兔? 在这里讨论这个问题的解法当然太小儿科了。但是y_tab这个C语言解释器只提供了1维数组。如果需要用到2维数组时ÿ…...
【Next.js App Router 深度解剖手册】
🔍 Next.js App Router 深度解剖手册 让我们抛开表象,直击 App Router 的设计核心! 本文将用 2000 字 底层原理图解,带你彻底理解这个现代路由系统的运作机制。系好安全带,准备深入代码底层! Ὠ…...
Spring Boot ShardingJDBC分库分表(草稿)
ShardingJDBC分库分表 1.Maven 引用 <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.1</version></dependency><dependency><…...
基于射频开关选择的VNA校准设计
活动发起人小虚竹 想对你说: 这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!…...
Pipeline 获取 Jenkins参数
Pipeline 获取 Jenkins参数 Jenkins 提供了一系列默认的环境变量,这些变量在构建过程中可以被使用。以下是一些常见的 Jenkins 默认环境变量: WORKSPACE: 当前构建的工作目录路径 JOB_NAME: 当前构建的作业名称 BUILD_NUMBER: 当前构建的编号ÿ…...
ARM Linux平台下 OpenCV Camera 实验
一、硬件原理 1. OV2640 1.1 基本功能 OV2640 是一款低功耗、高性能的图像传感器,支持以下功能: 最高分辨率:200 万像素(1600x1200)。 输出格式:JPEG、YUV、RGB。 内置图像处理功能:自动曝…...
【Python爬虫(30)】构建高效爬虫数据管理体系:从抓取到协作
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
淘宝/天猫店铺订单数据导出、销售报表设计与数据分析指南
在电商运营中,订单数据是店铺运营的核心资产之一。通过对订单数据的导出、整理和分析,商家可以更好地了解销售情况、优化运营策略、提升客户满意度,并制定科学的业务决策。本文将详细介绍淘宝/天猫店铺订单数据的导出方法、销售报表的设计思路…...
EasyRTC智能硬件:实时畅联、沉浸互动、消音护航
在当今智能硬件迅猛发展的时代,音视频通讯技术已成为设备与用户、设备与设备间不可或缺的沟通纽带。而EasyRTC,凭借其无可比拟的实时性能、卓越的互动感受以及强大的交互实力,正逐步演变为智能硬件领域的“超级动力”核心。特别是其倾力打造的…...
Docker Mysql 数据迁移
查看启动命令目录映射 查看容器名称 docker ps查看容器的启动命令 docker inspect mysql8.0 |grep CreateCommand -A 20如下图所示:我这边是把/var/lib/mysql 目录映射到我宿主机的/mnt/mysql/data目录下,而且我的数量比较大使用方法1的话时间比较久,所以我采用方法2 如果没…...