【在Linux世界中追寻伟大的One Piece】多线程(三)
目录
1 -> Linux线程同步
1.1 -> 条件变量
1.2 -> 同步概念与竞态条件
1.3 -> 条件变量函数
1.4 -> 为什么pthread_cond_wait需要互斥量
1.5 -> 条件变量使用规范
2 -> 生产者消费者模型
2.1 -> 为什么要使用生产者消费者模型
2.2 -> 生产者消费者模型优点
2.3 -> 基于BlockingQueue的生产者消费者模型
2.4 -> C++ queue模拟阻塞队列的生产消费模型
2.5 -> POSIX信号量
2.6 -> 基于环形队列的生产消费模型
3 -> 线程池
3.1 -> 线程池概念
3.2 -> 线程池应用场景
3.3 -> 线程池示例
4 -> 线程安全的单例模式
4.1 -> 什么是单例模式
4.2 -> 单例模式的特点
4.3 -> 饿汉方式实现单例模式
4.4 -> 懒汉方式实现单例模式
4.5 -> 懒汉方式实现单例模式(线程安全版本)
5 -> STL,智能指针和线程安全
5.1 -> STL中的容器是否是线程安全的
5.2 -> 智能指针是否是线程安全的
1 -> Linux线程同步
1.1 -> 条件变量
- 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
- 例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
1.2 -> 同步概念与竞态条件
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题不难理解。
1.3 -> 条件变量函数
初始化
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);
简单案例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>pthread_cond_t cond;
pthread_mutex_t mutex;void* r1(void* arg)
{while (1) {pthread_cond_wait(&cond, &mutex);printf("活动\n");}
}void* r2(void* arg)
{while (1) {pthread_cond_signal(&cond);sleep(1);}
}int main(void)
{pthread_t t1, t2;pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, r1, NULL);pthread_create(&t2, NULL, r2, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);
}
[root@localhost linux]# ./a.out活动活动活动
1.4 -> 为什么pthread_cond_wait需要互斥量
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
- 条件不会无缘无故的突然变得满足,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
- 按照上面的说法,设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上就行了。
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false)
{pthread_mutex_unlock(&mutex);//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);}
pthread_mutex_unlock(&mutex);
- 由于解锁和等待不是原子操作。调用解锁之后,pthread_cond_wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_cond_wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是一个原子操作。
- int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);进入该函数后,会去看条件量是否等于0,等于0,就把互斥量变成1,直到cond_wait返回,把条件量改成1,把互斥量恢复成原样。
1.5 -> 条件变量使用规范
- 等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
- 给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
2 -> 生产者消费者模型
2.1 -> 为什么要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者索要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
2.2 -> 生产者消费者模型优点
- 解耦
- 支持并发
- 支持忙闲不均
2.3 -> 基于BlockingQueue的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于:当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。
2.4 -> C++ queue模拟阻塞队列的生产消费模型
代码:
#include <iostream>
#include <queue>
#include <stdlib.h>
#include <pthread.h>#define NUM 8class BlockQueue
{
private:std::queue<int> q;int cap;pthread_mutex_t lock;pthread_cond_t full;pthread_cond_t empty;private:void LockQueue(){pthread_mutex_lock(&lock);}void UnLockQueue(){pthread_mutex_unlock(&lock);}void ProductWait(){pthread_cond_wait(&full, &lock);}void ConsumeWait(){pthread_cond_wait(&empty, &lock);}void NotifyProduct(){pthread_cond_signal(&full);}void NotifyConsume(){pthread_cond_signal(&empty);}bool IsEmpty(){return (q.size() == 0 ? true : false);}bool IsFull(){return (q.size() == cap ? true : false);}public:BlockQueue(int _cap = NUM) :cap(_cap){pthread_mutex_init(&lock, NULL);pthread_cond_init(&full, NULL);pthread_cond_init(&empty, NULL);}void PushData(const int& data){LockQueue();while (IsFull()) {NotifyConsume();std::cout << "queue full, notifyconsume data, product stop." << std::endl;ProductWait();}q.push(data);// NotifyConsume();UnLockQueue();}void PopData(int& data){LockQueue();while (IsEmpty()) {NotifyProduct();std::cout << "queue empty, notifyproduct data, consume stop." << std::endl;ConsumeWait();}data = q.front();q.pop();// NotifyProduct();UnLockQueue();}~BlockQueue(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&full);pthread_cond_destroy(&empty);}
};void* consumer(void* arg)
{BlockQueue* bqp = (BlockQueue*)arg;int data;for (; ; ) {bqp->PopData(data);std::cout << "Consume data done : " << data <<std::endl;}
}//more faster
void* producter(void* arg)
{BlockQueue* bqp = (BlockQueue*)arg;srand((unsigned long)time(NULL));for (; ; ) {int data = rand() % 1024;bqp->PushData(data);std::cout << "Prodoct data done: " << data <<std::endl;// sleep(1);}
}int main()
{BlockQueue bq;pthread_t c, p;pthread_create(&c, NULL, consumer, (void*)&bq);pthread_create(&p, NULL, producter, (void*)&bq);pthread_join(c, NULL);pthread_join(p, NULL);return 0;
}
2.5 -> POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。
初始化信号量
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);参数:
pshared : 0 表示线程间共享,非零表示进程间共享
value:信号量初始值
销毁信号量
int sem_destroy(sem_t* sem);
等待信号量
功能:等待信号量,会将信号量的值减 1
int sem_wait(sem_t * sem); //P()
发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1。
int sem_post(sem_t * sem);//V()
生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量)。
2.6 -> 基于环形队列的生产消费模型
- 环形队列采用数组模拟,用模运算来模拟环状特性。
- 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。
- 但是现有的信号量这个计数器,是简单的进行多进程间的同步过程。
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>#define NUM 16class RingQueue
{
private:std::vector<int> q;int cap;sem_t data_sem;sem_t space_sem;int consume_step;int product_step;public:RingQueue(int _cap = NUM) :q(_cap), cap(_cap){sem_init(&data_sem, 0, 0);sem_init(&space_sem, 0, cap);consume_step = 0;product_step = 0;}void PutData(const int& data){sem_wait(&space_sem); // Pq[consume_step] = data;consume_step++;consume_step %= cap;sem_post(&data_sem); //V}void GetData(int& data){sem_wait(&data_sem);data = q[product_step];product_step++;product_step %= cap;sem_post(&space_sem);}~RingQueue(){sem_destroy(&data_sem);sem_destroy(&space_sem);}
};void* consumer(void* arg)
{RingQueue* rqp = (RingQueue*)arg;int data;for (; ; ) {rqp->GetData(data);std::cout << "Consume data done : " << data << std::endl;sleep(1);}
}//more faster
void* producter(void* arg)
{RingQueue* rqp = (RingQueue*)arg;srand((unsigned long)time(NULL));for (; ; ) {int data = rand() % 1024;rqp->PutData(data);std::cout << "Prodoct data done: " << data << std::endl;// sleep(1);}
}int main()
{RingQueue rq;pthread_t c, p;pthread_create(&c, NULL, consumer, (void*)&rq);pthread_create(&p, NULL, producter, (void*)&rq);pthread_join(c, NULL);pthread_join(p, NULL);}
3 -> 线程池
3.1 -> 线程池概念
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。
3.2 -> 线程池应用场景
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
3.3 -> 线程池示例
threadpool.hpp
#ifndef __M_TP_H__
#define __M_TP_H__#include <iostream>
#include <queue>
#include <pthread.h>#define MAX_THREAD 5typedef bool (*handler_t)(int);class ThreadTask
{
private:int _data;handler_t _handler;
public:ThreadTask() :_data(-1), _handler(NULL) {}ThreadTask(int data, handler_t handler) {_data = data;_handler = handler;}void SetTask(int data, handler_t handler) {_data = data;_handler = handler;}void Run() {_handler(_data);}
};class ThreadPool
{
private:int _thread_max;int _thread_cur;bool _tp_quit;std::queue<ThreadTask*> _task_queue;pthread_mutex_t _lock;pthread_cond_t _cond;private:void LockQueue() {pthread_mutex_lock(&_lock);}void UnLockQueue() {pthread_mutex_unlock(&_lock);}void WakeUpOne() {pthread_cond_signal(&_cond);}void WakeUpAll() {pthread_cond_broadcast(&_cond);}void ThreadQuit() {_thread_cur--;UnLockQueue();pthread_exit(NULL);}void ThreadWait() {if (_tp_quit) {ThreadQuit();}pthread_cond_wait(&_cond, &_lock);}bool IsEmpty() {return _task_queue.empty();}static void* thr_start(void* arg) {ThreadPool* tp = (ThreadPool*)arg;while (1) {tp->LockQueue();while (tp->IsEmpty()) {tp->ThreadWait();}ThreadTask* tt;tp->PopTask(&tt);tp->UnLockQueue();tt->Run();delete tt;}return NULL;}public:ThreadPool(int max = MAX_THREAD) :_thread_max(max),_thread_cur(max),_tp_quit(false) {pthread_mutex_init(&_lock, NULL);pthread_cond_init(&_cond, NULL);}~ThreadPool() {pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}bool PoolInit() {pthread_t tid;for (int i = 0; i < _thread_max; i++) {int ret = pthread_create(&tid, NULL, thr_start,this);if (ret != 0) {std::cout << "create pool thread error\n";return false;}}return true;}bool PushTask(ThreadTask* tt) {LockQueue();if (_tp_quit) {UnLockQueue();return false;}_task_queue.push(tt);WakeUpOne();UnLockQueue();return true;}bool PopTask(ThreadTask** tt) {*tt = _task_queue.front();_task_queue.pop();return true;}bool PoolQuit() {LockQueue();_tp_quit = true;UnLockQueue();while (_thread_cur > 0) {WakeUpAll();usleep(1000);}return true;}};
#endif
main.cpp
bool handler(int data)
{srand(time(NULL));int n = rand() % 5;printf("Thread: %p Run Tast: %d--sleep %d sec\n", pthread_self(), data, n);sleep(n);return true;
}int main()
{int i;ThreadPool pool;pool.PoolInit();for (i = 0; i < 10; i++) {ThreadTask* tt = new ThreadTask(i, handler);pool.PushTask(tt);}pool.PoolQuit();return 0;
}
g++ -std=c++0x test.cpp -o test -pthread -lrt
4 -> 线程安全的单例模式
4.1 -> 什么是单例模式
单例模式(Singleton Pattern)是一种设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式常用于管理共享资源,如配置信息、线程池、缓存等。单例模式的核心思想是控制对象的实例化过程,使得在整个应用程序中只有一个实例存在,并且所有对该实例的访问都通过同一个访问点进行。
4.2 -> 单例模式的特点
- 唯一性:单例模式确保一个类只有一个实例。这意味着在整个应用程序的生命周期中,无论在何处调用该类的实例,都将返回同一个对象。
- 全局访问点:单例模式提供了一个全局访问点,通常是一个静态方法,用于获取该类的唯一实例。这使得在程序的任何地方都能够方便地访问该实例。
- 延迟初始化:单例模式的实例通常在第一次被请求时才会创建,这有助于节省资源。这种延迟初始化的特性也被称为懒加载(Lazy Initialization)。
- 线程安全:在多线程环境下,单例模式需要确保其唯一性和全局访问的正确性。这通常通过同步机制来实现,如使用锁或其他并发控制手段。
- 控制实例化:单例模式的构造函数通常是私有的,这防止了外部代码通过常规的构造函数创建新的实例。
- 可扩展性:单例模式可以通过继承或其他方式进行扩展,以满足不同的应用需求。
- 资源管理:单例模式常用于管理共享资源,如数据库连接、线程池、配置信息等,确保这些资源在整个应用程序中只有一个实例,从而提高资源的使用效率和管理便利性。
- 性能优化:由于单例模式只创建一个实例,因此可以减少内存开销和提高性能,特别是在处理大量数据或频繁访问的对象时。
- 简化代码结构:单例模式可以简化代码结构,因为它提供了一个单一的、全局的访问点,使得代码的维护和理解更加容易。
- 适用场景:单例模式适用于那些在整个应用程序中只需要一个实例的场景,如日志记录器、配置管理器、数据库连接池等。
4.3 -> 饿汉方式实现单例模式
template <typename T>
class Singleton
{static T data;
public:static T* GetInstance() {return &data;}
};
只要通过Singleton这个包装类来使用T对象,则一个进程中只有一个T对象的实例。
4.4 -> 懒汉方式实现单例模式
template <typename T>
class Singleton
{static T* inst;
public:static T* GetInstance() {if (inst == NULL) {inst = new T();}return inst;}
};
存在一个严重的问题,线程不安全。
第一个调用GetInstance时,如果两个线程同时调用,可能会创建出两份T对象的实例。
但是后续再次调用,就没有问题了。
4.5 -> 懒汉方式实现单例模式(线程安全版本)
// 懒汉模式, 线程安全
template <typename T>
class Singleton
{volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.static std::mutex lock;public:static T* GetInstance() {if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.if (inst == NULL) {inst = new T();}lock.unlock();}return inst;}
};
注意:
- 加锁解锁的位置
- 双重if判断,避免不必要的锁竞争
- volatile关键字防止过度优化
5 -> STL,智能指针和线程安全
5.1 -> STL中的容器是否是线程安全的
不是。
原因是,STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。
而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)。
因此STL默认不是线程安全。如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。
5.2 -> 智能指针是否是线程安全的
对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。
对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效,原子的操作引用计数。
感谢各位大佬支持!!!
互三啦!!!
相关文章:
【在Linux世界中追寻伟大的One Piece】多线程(三)
目录 1 -> Linux线程同步 1.1 -> 条件变量 1.2 -> 同步概念与竞态条件 1.3 -> 条件变量函数 1.4 -> 为什么pthread_cond_wait需要互斥量 1.5 -> 条件变量使用规范 2 -> 生产者消费者模型 2.1 -> 为什么要使用生产者消费者模型 2.2 -> 生产…...
eBay 基于 Celeborn RESTful API 进行自动化工具集成实践
作者:王斐,ebay Hadoop 团队软件工程师,Apache Kyuubi PMC member,Apache Celeborn Committer。 简介:Apache Celeborn 是一个统一的大数据中间服务,致力于提高不同MapReduce引擎的效率和弹性。为了Spark …...
Vue 前端 el-input 如何实现输入框内容始终添加在尾部%
要在 el-input 输入框的尾部添加 %,你可以通过两种方式来实现: 使用 suffix 插槽:这是最直接和最常用的方法。使用 append 插槽:如果你需要在输入框内或者右侧显示其他内容。 方法 1:使用 suffix 插槽 el-input 提供…...
如何通过轻易云实现泛微OA与金蝶云星空的数据无缝对接
FD003-非生产性付款申请 泛微>金蝶付款单-422 数据集成案例分享 在企业信息化系统中,数据的高效流转和准确对接是确保业务流程顺畅运行的关键。本文将重点探讨如何通过轻易云数据集成平台,实现泛微OA-Http与金蝶云星空之间的数据无缝对接,…...
【docker】docker的起源与容器的由来、docker容器的隔离机制
Docker 的起源与容器的由来 1. 虚拟机的局限:容器的需求萌芽 在 Docker 出现之前,开发和部署软件主要依赖虚拟机(VMs): 虚拟机通过模拟硬件运行操作系统,每个应用程序可以运行在自己的独立环境中。虽然虚…...
mysql 事务之LBCC与MVCC
一、事务 数据库事务(Database Transaction)是数据库管理系统(DBMS)中执行的一系列操作,这些操作被当作一个逻辑单元进行处理,以保证数据的一致性和完整性。 ACID,事务四个关键特性 1、原子性…...
记一次 .NET某hdp智能柜系统 卡死分析
一:背景 1. 讲故事 停了一个月时间没有更新博客了,主要是这段时间有些许事情导致心神不宁,我这个人也比较浮躁所以无法潜心修炼,事情如下: 被狗咬了 也不知道是不是出门没看黄历,在小区门口店里买烟&am…...
大模型专栏--Spring Ai Alibaba介绍和功能演示
Spring AI Alibaba 介绍和功能演示 背景 Spring AI Alibaba 开源项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。…...
【GPT】为什么人需要睡觉?
睡觉对人类来说是至关重要的生理和心理需求。以下是一些为什么人需要睡觉的主要原因,以及睡眠对身体和大脑的影响: 1. 恢复与修复 身体修复:在睡眠期间,身体进行细胞修复和再生。例如,生长激素在深度睡眠中分泌&#…...
【Linux】磁盘 | 文件系统 | inode
🪐🪐🪐欢迎来到程序员餐厅💫💫💫 主厨:邪王真眼 主厨的主页:Chef‘s blog 所属专栏:青果大战linux 总有光环在陨落,总有新星在闪烁 模电好难啊ÿ…...
A051-基于Spring Boot的网络海鲜市场系统的设计与实现
🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹 赠送计算机毕业设计600…...
谷歌浏览器Chrome打开百度很慢,其他网页正常的解决办法,试了很多,找到了适合的
最近不知怎么的,Chrome突然间打开百度很慢,甚至打不开。不光我一个人遇到这问题,我同事也遇到这个问题。开发中难免遇到问题,需要百度,现在是百度不了。 作为一名开发人员,习惯了使用Chrome进行开发&#…...
《企业级低代码开发平台技术要求》核心要点解析
一、引言 在数字化转型的浪潮中,企业级低代码开发平台成为推动企业创新与发展的关键力量。深圳市标准化协会发布的《企业级低代码开发平台技术要求》(T/SZAS 77—2024)为该领域提供了重要的规范与指引。深入剖析其核心要点,对于理…...
AI一键生成3D动画:腾讯最新方案,为小程序带来革命性变化
随着3D技术的快速发展,将静态的3D模型转化为能够生动展现各种动作的角色已经成为许多创作者和开发者梦寐以求的能力。然而,在过去,这一过程往往需要大量的手动工作和技术积累。现在,腾讯推出了一项创新的技术——AI一键生成3D动画,它不仅极大地简化了流程,还显著提高了效…...
AD软件如何快速切换三维视图,由2D切换至3D,以及如何恢复
在Altium Designer软件中,切换三维视图以及恢复二维视图的操作相对简单。以下是具体的步骤: 切换三维视图 在PCB设计界面中,2D切换3D,快捷键按住数字键盘中的“3”即可切换; 快捷键ctrlf(或者vb快捷键也…...
【赵渝强老师】PostgreSQL的数据库
PostgreSQL的逻辑存储结构主要是指数据库中的各种数据库对象,包括:数据库集群、数据库、表、索引、视图等等。所有数据库对象都有各自的对象标识符oid(object identifiers),它是一个无符号的四字节整数,相关对象的oid都…...
opencv 区域提取三种算法
opencv 区域提取三种算法 1.轮廓查找 findContours()函数,得到轮廓的点集集合 cv::vector<cv::vector<Point>> contours;threshold(roiMat,binImg,m_pPara.m_nMinGray,m_pPara.m_nMaxGray,THRESH_BINARY);//膨胀处理Mat dilaElement getStructuringE…...
C++初阶(十六)--STL--list的模拟实现
目录 结点类的实现 迭代器类的模拟实现 迭代器类的模板参数说明 构造函数 *运算符重载 ->运算符的重载 运算符的重载 --运算符重载 !运算符重载 运算符重载 list类的模拟实现 成员变量 默认成员函数 构造函数 拷贝构造函数 赋值运算符重载 迭代器相关函数 …...
树莓集团:以人工智能为核心,打造数字化生态运营新典范
在当今数字化浪潮席卷全球的背景下,各行各业都在积极探索数字化转型的路径。作为数字产业的领军者,树莓集团凭借其深厚的技术积累和创新理念,在人工智能、大数据、云计算等前沿技术领域不断突破,成功打造了一个以人工智能为核心的…...
基于深度学习的卷积神经网络十二生肖图像识别系统(PyQt5界面+数据集+训练代码)
本研究提出了一种基于深度学习的十二生肖图像识别系统,旨在利用卷积神经网络(CNN)进行图像分类,特别是十二生肖图像的自动识别。系统的核心采用了两种经典的深度学习模型:ResNet50和VGG16,进行图像的特征提…...
Torchtune在AMD GPU上的使用指南:利用多GPU能力进行LLM微调与扩展
Torchtune on AMD GPUs How-To Guide: Fine-tuning and Scaling LLMs with Multi-GPU Power — ROCm Blogs 这篇博客提供了一份详细的使用Torchtune在AMD GPU上微调和扩展大型语言模型(LLM)的指南。Torchtune 是一个PyTorch库,旨在让您轻松地…...
ESLint
代码规范 一套写代码的约定规则;比如赋值符号左右是否需要空格,一句话结束是否要加;.... 代码规范错误 如果你的代码不符合standard的要求,ESLint(脚手架里配的东西)会告诉你哪个文件第几行错了 解决代码…...
小程序-基于java+SpringBoot+Vue的微信小程序养老院系统设计与实现
项目运行 1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境:Tomcat 7.x,8.x,9.x版本均可 4.硬件环境:…...
如何具体实现商品详情的提取?
在电商领域,获取商品详情信息对于市场分析、价格比较、商品推荐等应用场景至关重要。本文将详细介绍如何使用Java编写爬虫程序,以合法合规的方式获取淘宝商品的详情信息,并提供详细的代码示例。 1. 环境准备 在开始编写爬虫之前,…...
antd table 自定义表头过滤表格内容
注意:该功能只能过滤可一次性返回全部数据的表格,通过接口分页查询的请自主按照需求改动哈~ 实现步骤: 1.在要过滤的列表表头增加过滤图标,点击图标显示浮窗 2.浮窗内显示整列可选选项,通过勾选单选或者全选、搜索框来…...
高效处理 iOS 应用中的大规模礼物数据:以直播项目为例(1-礼物池)
引言 在现代iOS应用开发中,处理大规模数据是一个常见的挑战。尤其实在直播项目中,礼物面板作为展示用户互动的重要部分,通常需要实时显示海量的礼物数据。这些数据不仅涉及到不同的区域、主播的动态差异,还需要保证高效的加载与渲…...
Maven - 优雅的管理多模块应用的统一版本号
文章目录 概述一、使用 versions-maven-plugin 插件1. 在主 pom.xml 中定义插件2. 修改版本号3. 回退修改4. 提交修改 二、使用占位符统一管理版本号1. 在主 pom.xml 中定义占位符2. 使用 flatten-maven-plugin 插件自动替换占位符3. 修改版本号4. 为什么这种方式更方便&#x…...
C++设计模式(观察者模式)
一、介绍 1.动机 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”,即一个对象的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵…...
【unity】WebSocket 与 EventSource 的区别
WebSocket 也是一种很好的选择,尤其是在需要进行 双向实时通信(例如聊天应用、实时数据流等)时。与 EventSource 不同,WebSocket 允许客户端和服务器之间建立一个持久的、全双工的通信通道。两者的区别和适用场景如下:…...
从ETL到DataOps:WhaleStudio替代Informatica,实现信创化升级
作者 | 白鲸开源 姜维 在数据集成和调度的领域,Informatica曾经是公认的权威工具。其强大的ETL功能、多年积累的市场经验,使其成为众多企业数据处理的核心工具。 然而,随着新一代大数据平台的迅速崛起,以及信创化改造的要求愈发严…...
第四十二篇 EfficientNet:重新思考卷积神经网络的模型缩放
文章目录 摘要1、简介2、相关工作3、复合模型缩放3.1、 问题公式化3.2、扩展维度3.3、复合比例 4、EfficientNet架构5、实验5.1、扩展MobileNets和ResNets5.2、EfficientNet的ImageNet结果5.3、EfficientNet的迁移学习结果 6、讨论7、结论 摘要 卷积神经网络(ConvNets)通常在固…...
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
T. 已测试目录 主机类型主机版本Docker镜像版本结果WSL2Ubuntu22.04Ubuntu20.04PASSWSL2Ubuntu22.04Ubuntu18.04PASS R. 软硬件要求: 编译硬件需求:做多系统测试,磁盘500GB起步(固态)(机械会卡死),内存3…...
Web Worker 和 WebSocket的区别
Web Worker(消息传递机制) 定义:是为了在浏览器中提供多线程支持,允许 JavaScript 在后台线程运行,而不阻塞主线程。它非常适合执行耗时的计算任务或处理大量数据,避免主线程(通常是 UI 线程&a…...
vmware Ubuntu2004运行STAR-Searcher
github链接 安装ros noetic gazebo11 略 gazebo更新方法 sudo sh -c echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable lsb_release -cs main" > /etc/apt/sources.list.d/gazebo-stable.list wget https://packages.osrfoundation.org/gaz…...
04_JavaScript引入到文件
JavaScript引入到文件 嵌入到HTML文件中 <body> <script> var age 20 </script></body> 引入本地独立JS文件 <body> <script type"text/javascript" src"./itbaizhan.js"> </script></body> 引入网络来…...
计算机网络的功能
目录 信息交换 资源共享 分布式处理 可靠性增强 集中管理 信息交换 计算机网络最基本的功能之一是允许不同设备之间的数据通信。这包括电子邮件的发送和接收、即时消息的传递、文件传输等。通过网络,用户可以轻松地与全球各地的其他人进行沟通和协作。 信息交…...
38 基于单片机的宠物喂食(ESP8266、红外、电机)
目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机,采用L298N驱动连接P2.3和P2.4口进行电机驱动, 然后串口连接P3.0和P3.1模拟ESP8266, 红外传感器连接ADC0832数模转换器连接单片机的P1.0~P1.…...
Git仓库迁移到远程仓库(源码、分支、提交)
单个迁移仓库 一、迁移仓库 1.准备工作 > 手动在电脑创建一个临时文件夹,CMD进入该目录 > 远程仓库上创建一个同名的空仓库 2.CMD命令:拉取旧Git仓库(包含提交、分支、源码) $ git clone --bare http://git.domain.cn/…...
Go语言压缩文件处理
目录 Go 语言压缩文件处理1. 压缩文件:Zip函数2. 解压文件:UnZip 函数3. 小结 Go 语言压缩文件处理 在现代的应用开发中,处理压缩文件(如 .zip 格式)是常见的需求。Go 语言提供了内置的 archive/zip 包来处理 .zip 文…...
Libevent库-http通信不同请求方式的处理
做项目的时候用到了http通信,同事用libevent库写的,特此记录后端从前端拿到消息后的处理方式 void CHTTPTest::request(const std::any & data) {// data 是从前端拿到的数据void *obj std::any_cast<void *>(data); // std::any是C17新标准…...
Vue3 v-if与v-show的区别
v-if 与 v-show 我们都可以在开发中用于条件渲染,在面试时也是个常考的题目 实现原理 v-if:是真正的条件渲染,当v-if true时,元素会被创建、渲染,并插入到dom树中,这个过程会耗费系统的资源,当…...
同时在github和gitee配置密钥
同时在github和gitee配置密钥 1. 生成不同的 SSH 密钥 为每个平台生成单独的 SSH 密钥。 # 为 GitHub 生成密钥(默认文件路径为 ~/.ssh/github_id_rsa) ssh-keygen -t rsa -b 4096 -C "your_github_emailexample.com" -f ~/.ssh/github_id_…...
Scala—数组(不可变数组Array、可变数组ArrayBuffer)用法详解
Scala集合概述-链接 大家可以点击上方链接,先对Scala的集合有一个整体的概念🤣🤣🤣 在 Scala 中,数组是一种特殊的集合类型,可以是可变的也可以是不可变的。 1. 不可变数组 在 Scala 中,不可变…...
Failed to find SV in PRN block of SINEX file (Name svnav.dat)
gamit 精密星历生成失败 gamit svnav.dat没更新 下载svnav.dat.allgnss 重命名成 svnav.dat ,替换到tables,即可。...
H.265流媒体播放器EasyPlayer.js无插件H5播放器关于移动端(H5)切换网络的时候,播放器会触发什么事件
EasyPlayer.js无插件H5播放器作为一款功能全面的H5流媒体播放器,凭借其多种协议支持、多种解码方式、丰富的渲染元素和强大的应用功能,以及出色的跨平台兼容性,为用户提供了高度定制化的选项和优化的播放体验。无论是视频直播还是点播&#x…...
Ubuntu上使用自带python创建虚拟环境
虚拟环境让项目之间的依赖关系更加清晰,也可以避免全局安装的包的版本冲突问题。 1.查看python Ubuntu上一般都是自带python的,查看python版本及指向(使用的话python要换成python3): ls -l /usr/bin | grep python 2.安装python虚拟环境工…...
对智能电视直播App的恶意监控
首先我们要指出中国广电总局推出的一个政策性文件是恶意监控的始作俑者,这个广电总局的政策性文件禁止智能电视和电视盒子安装直播软件。应该说这个政策性文件是为了保护特殊利益集团,阻挠技术进步和发展的。 有那么一些电视机和电视盒子的厂商和电信运…...
网络原理(一):应用层自定义协议的信息组织格式 HTTP 前置知识
目录 1. 应用层 2. 自定义协议 2.1 根据需求 > 明确传输信息 2.2 约定好信息组织的格式 2.2.1 行文本 2.2.2 xml 2.2.3 json 2.2.4 protobuf 3. HTTP 协议 3.1 特点 4. 抓包工具 1. 应用层 在前面的博客中, 我们了解了 TCP/IP 五层协议模型: 应用层传输层网络层…...
微软企业邮箱:安全可靠的企业级邮件服务!
微软企业邮箱的设置步骤?如何注册使用烽火域名邮箱? 微软企业邮箱作为一款专为企业设计的邮件服务,不仅提供了高效便捷的通信工具,更在安全性、可靠性和功能性方面树立了行业标杆。烽火将深入探讨微软企业邮箱的多重优势。 微软…...
matlab导出3D彩色模型(surface类转stl,并对白模上色)
在matlab中绘制3维图形时,需要将3维图形导出到PPT中展示。但是直接导出图片效果欠佳,无法全方位展示。 最近学习了如何将matlab中的图形导出为stl模型,然后再采用简单的方法对模型上色。 中间尝试过matlab导出stl、ply、3dm等多种格式&…...