Linux:43线程封装与互斥lesson31
mmap文件映射视屏:待看...
目录
线程栈
代码证明:一个线程的数据,其他线程也可以访问
线程封装
简单封装,2.thread
Thread.hpp
Main.cc
Makefile
结果:
编辑
问题1:
问题2: lamba表达式
模版封装 3.thread_template
Thread.hpp
Main.cc
Makefile
结果
编辑
线程局部存储4threadlocal
test.cc
Makefile
结果:
添加:__thread
结果:
pthread_setname_np:设置线程的名字。
pthread_getname_np:用于获取线程的名称。
用2.thread进行修改
同步和互斥
5.mutex代码,加锁和解锁,见一下
线程栈
• 对于Linux进程或者说主线程,简单理解就是main函数的栈空间,在fork的时候,实际上就是复 制了⽗亲的 stack 空间地址,然后写时拷⻉(cow)以及动态增⻓。如果扩充超出该上限则栈溢出 会报段错误(发送段错误信号给该进程)。进程栈是唯⼀可以访问未映射⻚⽽不⼀定会发⽣段错 误⸺⸺超出扩充上限才报。
• 然⽽对于主线程⽣成的⼦线程⽽⾔,其 stack 将不再是向下⽣⻓的,⽽是事先固定下来的。线 程栈⼀般是调⽤glibc/uclibc等的 pthread 库接 pthread_create 创建的线程,在⽂件映 射区(或称之为共享区)。其中使⽤ mmap 系统调⽤,这个可以从 glibc 的
nptl/allocatestack.c 中的 allocate_stack 函数中看到:
mem = mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
此调⽤中的 size 参数的获取很是复杂,你可以⼿⼯传⼊stack的⼤⼩,也可以使⽤默认的,⼀般⽽ ⾔就是默认的 8M 。这些都不重要,重要的是,这种stack不能动态增⻓,⼀旦⽤尽就没了,这是和 ⽣成进程的fork不同的地⽅。在glibc中通过mmap得到了stack之后,底层将调⽤ sys_clone 系 统调⽤:
对于⼦线程的 stack ,它其实是在进程的地址空间中map出来的⼀块内存区域,原则上是 线程私有的,但是同⼀个进程的所有线程⽣成的时候,是会浅拷⻉⽣成者的 task_struct 的很多 字段,如果愿意,其它线程也还是可以访问到的,于是⼀定要注意。
每个线程都有自己的栈结构:
独立的上下文:有独立的PCB+TCP(用户层,pthread库内部)
独立的栈:每个线程都有自己的栈结构,要么是进程自己的要么是库中创建进程时mmap申请出来的。
结论:一个线程的数据,其他线程也可以访问:只要拿到对应的地址即可。
代码证明:一个线程的数据,其他线程也可以访问
//证明:一个线程的数据,其他线程也可以访问:只要拿到对应的地址即可。
#include <sched.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>int *p = nullptr;void *threadrun(void *args){int a = 123;p = &a;while(true) {sleep(1);}}int main(){pthread_t tid;pthread_create(&tid, nullptr, threadrun, nullptr);while(true){std::cout << "*p : " << *p << std::endl;sleep(1);}pthread_join(tid, nullptr);return 0;}
线程封装
线程ID:就是动态库。
线程的封装
以面向对象的形式把线程进行封装
计数器:进程编号: static uint32_t number = 0;
_name:线程名字
_tid:tid
_isdetach:是否被分分离。
_isrunning:是否正在运行,是否调用Start()
Start()线程开始/创建:
--创建线程,pthread_creat
--如果分离,那么就设置线程分离,pthreaddetach
EnableDetach():
是否分离。
Detach():
如果一个线程已经跑起来了,要把它设置为分离状态,用detach()
Enablerunning():
是否已经运行
Stop():停止进程
pthread_cancle
是运行状态才可以stop(),
Join():
如果是分离的那么就不可以Join()
Routine(),写在在public会报错,
因为Routine属于类内的成员函数,默认包含this指针???
_func()回调方法,static没有this指针 ,无法回调房钱成员,
解决:把thos指针传入Routine
简单封装,2.thread
Thread.hpp
#ifndef _THREAD_H_
#define _THREAD_H_
//一个头文件保护机制,防止头文件被重复包含
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>namespace ThreadModlue{static uint32_t number = 1;class Thread{using func_t = std::function<void()>;// 添加模版的前身private:void Enabledetach(){std::cout<<"线程被分离了"<<std::endl;_isdetach = true;}void Enablerunning(){std::cout<<"线程正在运行"<<std::endl;_isrunning = true;}static void* Routine(void*args){//static,没有默认指针,需要在线程创建的时候,通过args传回this指针Thread* self = static_cast<Thread*>(args);//强转成Thread*类型self->Enablerunning();if(self->_isdetach){self->Enabledetach();}self->_func();//调用函数要加上()括号才可以调用return nullptr;}public:Thread(func_t func):_tid(0),_isdetach(false),_isrunning(false),_res(nullptr),_func(func){_name = "thread - " + std::to_string(number++);}void Detach(){if(_isdetach)return;if(_isrunning){pthread_detach(_tid);std::cout<<"线程分离成功"<<std::endl;Enabledetach();}}bool Start(){if(_isrunning)return false;//线程正在运行,不可再次启动int n = pthread_create(&_tid,nullptr,Routine,this);if( n != 0 ){std::cerr<<"创建线程错误"<<strerror(n)<<std::endl;return false;}else{std::cout<<"线程创建成功:"<<_name<<std::endl;return true;}}bool Stop(){if(_isrunning){int n = pthread_cancel(_tid);if(n != 0){std::cerr<<"线程停止错误:"<<strerror(n)<<std::endl;return false;}else{_isrunning = false;std::cout<<"线程停止:"<<_name<<std::endl;return true;}}}void Join(){if(_isdetach){std::cout<<"已经被分离的线程无法join"<<std::endl;return;}int n = pthread_join(_tid,&_res);if(n != 0){std::cerr<<"线程join错误:"<<strerror(n)<<std::endl;}else{std::cout << "线程join成功" << std::endl;}}private:pthread_t _tid;bool _isdetach;bool _isrunning;std::string _name;void* _res;func_t _func;};
}
#endif
Main.cc
#include "Thread.hpp"
#include <unistd.h>
#include <vector>using namespace ThreadModlue;int main(){Thread t([](){while(true){std::cout << "我是一个新线程: "<< std::endl; // 我的线程的名字是什么呀?debugsleep(1);}});t.Start();t.Detach();sleep(2);t.Stop();sleep(2);t.Join();return 0;
}
Makefile
test_thread:Main.ccg++ -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f test_thread
结果:
问题1:
在实现线程的封装过程中,Routine(),写在在public会报错,
因为Routine属于类内的成员函数,默认包含this指针???
这句话怎么理解 ???static void* Routine(void*args){ //static,没有默认指针,需要在线程创建的时候,通过args传回this指针Thread* self = static_cast<Thread*>(args);//强转成Thread*类型self->Enablerunning();if(self->_isdetach){self->Enabledetach();}self->_func();//调用函数要加上()括号才可以调用return nullptr;}
pthread_create调用Routine函数,但是Routine没有默认的this指针,
那么就需要在pthread_create的第四个传入Routine的参数传入this指针作为参数才可以
问题2: lamba表达式
Thread t([](){while(true){std::cout << "我是一个新线程: "<< std::endl; // 我的线程的名字是什么呀?debugsleep(1);}});
模版封装 3.thread_template
Thread.hpp
#ifndef _THREAD_H_
#define _THREAD_H_
//一个头文件保护机制,防止头文件被重复包含
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>namespace ThreadModlue{static uint32_t number = 1;template<typename T>//这里设置模版类型class Thread{using func_t = std::function<void(T)>;// 添加模版的前身 + 模版添加需要传参数!!!private:void Enabledetach(){std::cout<<"线程被分离了"<<std::endl;_isdetach = true;}void Enablerunning(){std::cout<<"线程正在运行"<<std::endl;_isrunning = true;}static void* Routine(void*args){//static,没有默认指针,需要在线程创建的时候,通过args传回this指针Thread<T>* self = static_cast<Thread<T>*>(args);//强转成Thread<T*>类型,self->Enablerunning();if(self->_isdetach){self->Enabledetach();}self->_func(self->_data);//调用函数要加上()括号才可以调用return nullptr;}public:Thread(func_t func,T data)//传参 T data:_tid(0),_isdetach(false),_isrunning(false),_res(nullptr),_func(func),_data(data)//初始化参数{_name = "thread - " + std::to_string(number++);}void Detach(){if(_isdetach)return;if(_isrunning){pthread_detach(_tid);std::cout<<"线程分离成功"<<std::endl;Enabledetach();}}bool Start(){if(_isrunning)return false;//线程正在运行,不可再次启动int n = pthread_create(&_tid,nullptr,Routine,this);if( n != 0 ){std::cerr<<"创建线程错误"<<strerror(n)<<std::endl;return false;}else{std::cout<<"线程创建成功:"<<_name<<std::endl;return true;}}bool Stop(){if(_isrunning){int n = pthread_cancel(_tid);if(n != 0){std::cerr<<"线程停止错误:"<<strerror(n)<<std::endl;return false;}else{_isrunning = false;std::cout<<"线程停止:"<<_name<<std::endl;return true;}}}void Join(){if(_isdetach){std::cout<<"已经被分离的线程无法join"<<std::endl;return;}int n = pthread_join(_tid,&_res);if(n != 0){std::cerr<<"线程join错误:"<<strerror(n)<<std::endl;}else{std::cout << "线程join成功" << std::endl;}}~Thread(){}private:pthread_t _tid;bool _isdetach;bool _isrunning;std::string _name;void* _res;func_t _func;T _data;};
}
#endif
Main.cc
#include "Thread.hpp"
#include <unistd.h>using namespace ThreadModlue;// 我们可以传递对象吗???
class ThreadData
{
public:pthread_t tid;std::string name;
};void Count(ThreadData td)
{while (true){std::cout << "我是一个新线程" << std::endl;sleep(1);}
}int main()
{ThreadData td;Thread<ThreadData> t(Count, td);t.Start();sleep(5);t.Stop();t.Join();return 0;
}
Makefile
test_thread:Main.ccg++ -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f test_thread
结果
线程局部存储4threadlocal
test.cc
#include <pthread.h>
#include <iostream>
#include <string>
#include <unistd.h>// 该count叫做线程的局部存储!
int count = 1;
// 线程局部存储有什么用?全局变量,我又不想让这个全局变量被其他线程看到!
// 线程局部存储,只能存储内置类型和部分指针std::string Addr(int &c)
{char addr[64];snprintf(addr, sizeof(addr), "%p", &c);return addr;
}void *routine1(void *args)
{(void)args;while (true){std::cout << "thread - 1, count = " << count << "[我来修改count], "<< "&count: " << Addr(count) << std::endl;count++;sleep(1);}
}void *routine2(void *args)
{(void)args;while (true){std::cout << "thread - 2, count = " << count<< ", &count: " << Addr(count) << std::endl;sleep(1);}
}int main()
{pthread_t tid1, tid2;pthread_create(&tid1, nullptr, routine1, nullptr);pthread_create(&tid2, nullptr, routine2, nullptr);pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);return 0;
}
Makefile
test_thread:test.ccg++ -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f test_thread
结果:
全局变量被两个线程共享 ,线程1修改全局变量count,线程2也可以看得到
添加:__thread
#include <pthread.h>
#include <iostream>
#include <string>
#include <unistd.h>// 该count叫做线程的局部存储!
__thread int count = 1;
// 线程局部存储有什么用?全局变量,我又不想让这个全局变量被其他线程看到!
// 线程局部存储,只能存储内置类型和部分指针std::string Addr(int &c)
{char addr[64];snprintf(addr, sizeof(addr), "%p", &c);return addr;
}void *routine1(void *args)
{(void)args;while (true){std::cout << "thread - 1, count = " << count << "[我来修改count], "<< "&count: " << Addr(count) << std::endl;count++;sleep(1);}
}void *routine2(void *args)
{(void)args;while (true){std::cout << "thread - 2, count = " << count<< ", &count: " << Addr(count) << std::endl;sleep(1);}
}int main()
{pthread_t tid1, tid2;pthread_create(&tid1, nullptr, routine1, nullptr);pthread_create(&tid2, nullptr, routine2, nullptr);pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);return 0;
}
结果:
解析:
与上面不同的是,在添加__thread后,线程1修改count,线程2读取的count不再随着线程1 的修改而修改
解释:
添加__thread修饰:该count叫做局部存储
同一个变量名:指向不同的地址
在各自的线程管理块里面创建存储
pthread_setname_np:设置线程的名字。
pthread_getname_np:用于获取线程的名称。
#include <pthread.h>int pthread_setname_np(pthread_t thread, const char *name);参数
pthread_t thread:要设置名称的线程 ID。如果设置为 pthread_self(),则表示当前线程。
const char *name:要设置的线程名称。通常是一个简短的字符串,长度通常不超过 16 个字符(包括终止符 \0)。
返回值
成功时返回 0。
失败时返回错误码(如 EINVAL 表示无效参数,EAGAIN 表示名称过长等)。#include <pthread.h>int pthread_getname_np(pthread_t thread, char *name, size_t len);参数
pthread_t thread:要获取名称的线程 ID。如果设置为 pthread_self(),则表示当前线程。
char *name:用于存储线程名称的缓冲区。
size_t len:缓冲区的大小。
返回值
成功时返回 0。
失败时返回错误码(如 EINVAL 表示无效参数,ERANGE 表示缓冲区太小等)。
用2.thread进行修改
#ifndef _THREAD_H_
#define _THREAD_H_
//一个头文件保护机制,防止头文件被重复包含
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>namespace ThreadModlue{static uint32_t number = 1;class Thread{using func_t = std::function<void()>;// 添加模版的前身private:void Enabledetach(){std::cout<<"线程被分离了"<<std::endl;_isdetach = true;}void Enablerunning(){std::cout<<"线程正在运行"<<std::endl;_isrunning = true;}static void* Routine(void*args){//static,没有默认指针,需要在线程创建的时候,通过args传回this指针Thread* self = static_cast<Thread*>(args);//强转成Thread*类型self->Enablerunning();if(self->_isdetach){self->Enabledetach();}pthread_setname_np(self->_tid, self->_name.c_str());self->_func();//调用函数要加上()括号才可以调用return nullptr;}public:Thread(func_t func):_tid(0),_isdetach(false),_isrunning(false),_res(nullptr),_func(func){_name = "thread - " + std::to_string(number++);}void Detach(){if(_isdetach)return;if(_isrunning){pthread_detach(_tid);std::cout<<"线程分离成功"<<std::endl;Enabledetach();}}bool Start(){if(_isrunning)return false;//线程正在运行,不可再次启动int n = pthread_create(&_tid,nullptr,Routine,this);if( n != 0 ){std::cerr<<"创建线程错误"<<strerror(n)<<std::endl;return false;}else{std::cout<<"线程创建成功:"<<_name<<std::endl;return true;}}bool Stop(){if(_isrunning){int n = pthread_cancel(_tid);if(n != 0){std::cerr<<"线程停止错误:"<<strerror(n)<<std::endl;return false;}else{_isrunning = false;std::cout<<"线程停止:"<<_name<<std::endl;return true;}}}void Join(){if(_isdetach){std::cout<<"已经被分离的线程无法join"<<std::endl;return;}int n = pthread_join(_tid,&_res);if(n != 0){std::cerr<<"线程join错误:"<<strerror(n)<<std::endl;}else{std::cout << "线程join成功" << std::endl;}}private:pthread_t _tid;bool _isdetach;bool _isrunning;std::string _name;void* _res;func_t _func;};
}
#endif
#include "Thread.hpp"
#include <unistd.h>
#include <vector>using namespace ThreadModlue;int main(){// Thread t([](){// while(true)// {// std::cout << "我是一个新线程: "<< std::endl; // 我的线程的名字是什么呀?debug// sleep(1);// }// });// t.Start();// t.Detach();// sleep(2);// t.Stop();// sleep(2);// t.Join();// return 0;std::vector<Thread> threads;for (int i = 0; i < 10; i++){threads.emplace_back([](){while(true){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debugsleep(1);} });}for (auto &thread : threads){thread.Start();}for (auto &thread : threads){thread.Join();}
}
同步和互斥
5.mutex代码,加锁和解锁,见一下
// 操作共享变量会有问题的售票系统代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h>int ticket = 1000; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *route(void *arg) {char *id = (char *)arg;while (1){pthread_mutex_lock(&lock);if (ticket > 0) // 1. 判断{usleep(1000); // 模拟抢票花的时间printf("%s sells ticket:%d\n", id, ticket); // 2. 抢到了票ticket--; // 3. 票数--pthread_mutex_unlock(&lock);}else{pthread_mutex_unlock(&lock);break;}}return nullptr; }int main(void) {pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void *)"thread 1");pthread_create(&t2, NULL, route, (void *)"thread 2");pthread_create(&t3, NULL, route, (void *)"thread 3");pthread_create(&t4, NULL, route, (void *)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL); }
下一篇:线程互斥:
写文章-CSDN创作中心
相关文章:

Linux:43线程封装与互斥lesson31
mmap文件映射视屏:待看... 目录 线程栈 代码证明:一个线程的数据,其他线程也可以访问 线程封装 简单封装,2.thread Thread.hpp Main.cc Makefile 结果: 编辑 问题1: 问题2: lamba表达式 模版封…...

“工作区”升级为“磁盘”、数据集统计概览优化|ModelWhale 版本更新
本次更新围绕用户在实际项目中对平台的理解和管理体验进行了多项优化。 “工作区”升级为“磁盘”、及其管理优化 平台“工作区”概念正式更名为“磁盘”,突出其存储功能。原有以目录代称的存储区域划分同步更名,其中“work目录”更改为“个人磁盘”&am…...

【Mysql基础】一、基础入门和常见SQL语句
📚博客主页:代码探秘者-CSDN博客 🌈:最难不过坚持 ✨专栏 🌈语言篇C语言\ CJavase基础🌈数据结构专栏数据结构🌈算法专栏必备算法🌈数据库专栏MysqlRedis🌈必备篇 其他…...

AWS之存储服务
目录 一、传统存储术语 二、传统存储与云存储的关系 三、云存储之AWS 使用场景 文件存储 数据块存储 对象存储 EBS、EFS、S3对比 EBS块存储 S3对象存储 S3 使用案例 S3 存储类 EFS文件存储 一、传统存储术语 分类 接口/技术类型 应用场景特点 关系及区别 机械硬…...

Jmeter中的Json提取器如何使用?
在JMeter中使用JSON提取器可以方便地从JSON格式的响应数据中提取特定字段的值。以下是详细步骤和示例: 1. 添加JSON提取器 右击目标HTTP请求 -> 选择 添加 -> 后置处理器 -> JSON提取器。 2. 配置JSON提取器参数 变量名称(Names of created…...

从零理解 C++ 中的原子布尔变量:`std::atomic<bool>` 入门指南
文章目录 引言:为什么需要原子变量?一、什么是 std::atomic<bool>?二、为什么不用普通 bool?一个反面例子三、std::atomic<bool> 的用法四、std::atomic<bool> 的优势五、完整示例:多线程文件传输六…...
软件领域第三方检测机构:如何保障软件品质与安全?
在软件领域,第三方检测机构扮演着极其重要的角色,它们与软件开发者和使用者保持独立,对软件的品质和安全性进行公正而专业的评估。凭借严格的技术方法和丰富的实践经历,这些机构能够揭示软件中潜藏的问题,进而确保软件…...
单例模式的两种设计
单例模式确保一个类只有一个实例,并提供一个全局访问点。 1. 饿汉模式 (Eager Initialization) 饿汉模式在程序启动时就创建实例,线程安全。 cpp class EagerSingleton { public:// 删除拷贝构造函数和赋值运算符EagerSingleton(const EagerSingleton…...
MySQL 5.7 之后的特性解析:从 8.0 到 8.4 的技术进化
MySQL 是全球最流行的开源关系型数据库之一,广泛应用于 Web 应用、数据分析和企业级系统。自 MySQL 5.7(2015 年发布)以来,MySQL 8.0(2018 年)、8.1(2023 年)、8.4(2024 …...
PHP框架在分布式系统中的应用!
随着互联网业务的快速发展,分布式系统因其高可用性、可扩展性和容错性成为现代应用架构的主流选择。而PHP作为一门成熟的Web开发语言,凭借其简洁的语法、丰富的框架生态和持续的性能优化,逐渐在分布式系统中崭露头角。本文将深入探讨PHP框架在…...
[学习]RTKLib详解:ionex.c、options.c与preceph.c
RTKLib详解:ionex.c、options.c与preceph.c 本文是 RTKLlib详解 系列文章的一篇,目前该系列文章还在持续总结写作中,以发表的如下,有兴趣的可以翻阅。 [学习] RTKlib详解:功能、工具与源码结构解析 [学习]RTKLib详解&…...

六个仓库合并为一个仓库,保留master和develop分支的bat脚本
利用git subtree可以实现多个仓库合并为一个仓库,手动操作起来太麻烦了,今天花了点时间写了一个可执行的脚本,现在操作起来就方便多了。 1、本地新建setup.bat文件 2、用编辑器打开(我用的是Notepad) 3、把下面代码…...

养生:通往健康生活的桥梁
在生活节奏日益加快的今天,养生已成为维持身心健康的必要手段。从日常饮食到运动锻炼,从睡眠质量到心态调节,每一个环节都对我们的生活品质有着重要影响。以下是一些实用的养生建议,帮助你打造健康生活。 饮食养生:均…...

【前端基础】9、CSS的动态伪类(hover、visited、hover、active、focus)【注:本文只有几个粗略说明】
一、什么是伪类 选择器的一种,用于选择处于特定状态的元素。 最常见的现象:鼠标放在某些文字上面,文字就会加上颜色。 鼠标没放上去之前: 鼠标放上去之后: 二、动态伪类 图片来源(链接文章也有其他伪…...

Simufact Welding重塑新能源汽车电池盒焊接工艺
引言 近年来,新能源汽车行业呈爆发式增长,已然成为全球能源转型与汽车产业升级的核心方向。在新能源汽车中,电池系统占据核心地位,作为电池系统重要组成部分的电池盒,也发挥着不可或缺的作用 。目前,电池盒…...

WordPress 网站上的 jpg、png 和 WebP 图片插件
核心功能 1. 转换 AVIF 并压缩 AVIF 将您 WordPress 网站上的 jpg、png 和 WebP 图片转换为 AVIF 格式,并根据您设置的压缩级别压缩 AVIF 图片。如果原始图片已经是 WordPress 6.5 以上支持的 AVIF 格式,则原始 AVIF 图片将仅被压缩。 2. 转换 WebP 并…...
《Vue.js》阅读之响应式数据与副作用函数
Vue.js 《Vue.js设计与实现》(霍春阳) 适合:从零手写Vue3响应式系统,大厂面试源码题直接覆盖。重点章节:第4章(响应式)、第5章(渲染器)、第8章(编译器&…...

如何应对网站被爬虫和采集?综合防护策略与实用方案
在互联网时代,网站内容被恶意爬虫或采集工具窃取已成为常见问题。这不仅侵犯原创权益,还可能影响网站性能和SEO排名。以下是结合技术、策略与法律的综合解决方案,帮助网站构建有效防护体系。 一、技术防护:阻断爬虫的“技术防线”…...

AI智慧公园管理方案:用科技重塑市民的“夜游体验”
AI智慧公园管理方案:多场景智能巡检与安全防控 一、背景与痛点分析 夏季夜间,公园成为市民休闲娱乐的核心场所,但管理难度随之激增: 宠物管理失控:未牵绳宠物进入园区,随地排泄、惊扰游客,甚…...
学习日志04 java
PTA上的练习复盘 java01 编程题作业感悟: 可以用ai指导自己怎么调试,但是不要把调代码这过程里面的精华交给ai,就是自己去修正错误不能让ai代劳!~~~ 1 scanner.close() Scanner *** new Scanner(System.in); ***.close(); …...

LVGL- 按钮矩阵控件
1 按钮矩阵控件 lv_btnmatrix 是 LVGL(Light and Versatile Graphics Library) v8 中提供的一个非常实用的控件,用于创建带有多个按钮的矩阵布局。它常用于实现虚拟键盘、数字键盘、操作面板、选择菜单等场景,特别适用于嵌入式设…...
【node】6 包与npm
前言 目标 1 了解什么是包 2 怎么使用npm下载包 #mermaid-svg-Ur0d2uCdQeAQOJjW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Ur0d2uCdQeAQOJjW .error-icon{fill:#552222;}#mermaid-svg-Ur0d2uCdQeAQOJjW .erro…...

1. 使用 IntelliJ IDEA 创建 React 项目:创建 React 项目界面详解;配置 Yarn 为包管理器
1. 使用 IntelliJ IDEA 创建 React 项目:创建 React 项目界面详解;配置 Yarn 为包管理器 🧩 使用 IntelliJ IDEA 创建 React 项目(附 Yarn 配置与 Vite 建议)📷 创建 React 项目界面详解1️⃣ Name…...
T-SQL在SQL Server中判断表、字段、索引、视图、触发器、Synonym等是否存在
SQL Server创建或者删除表、字段、索引、视图、触发器前判断是否存在。 目录 1. SQL Server创建表之前判断表是否存在 2. SQL Server新增字段之前判断是否存在 3. SQL Server删除字段之前判断是否存在 4. SQL Server新增索引之前判断是否存在 5. SQL Server判断视图是否存…...
Docker中mysql镜像保存与导入
一、Docker中mysql镜像保存 Docker 的 MySQL 镜像保存通常有两种场景:一种是保存镜像本身的修改(如配置、初始化数据),另一种是持久化保存容器运行时产生的数据(如数据库表、用户数据)。以下是具体方法&am…...

【JVM】从零开始深度解析JVM
本篇博客给大家带来的是JVM的知识点, 重点在类加载和垃圾回收机制上. 🐎文章专栏: JavaEE初阶 🚀若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅🚀 …...

算法训练营第十四天|110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和、222.完全二叉树的节点个数
110.平衡二叉树 题目 思路与解法 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def isBalanced(self, r…...
Java单例模式总结
说明:单例模式的核心是确保一个类只有一个实例,并提供全局访问点。饿汉式和懒汉式是两种常见的实现方式 一、饿汉式和懒汉式 1. 饿汉式(Eager Initialization) public class EagerSingleton {// 类加载时直接初始化实例private…...

在 Elasticsearch 中删除文档中的某个字段
作者:来自 Elastic Kofi Bartlett 探索在 Elasticsearch 中删除文档字段的方法。 更多有关 Elasticsearch 文档的操作,请详细阅读文章 “开始使用 Elasticsearch (1)”。 想获得 Elastic 认证?查看下一期 Elasticsear…...
OpenCV中适用华为昇腾(Ascend)后端的逐元素操作(Per-element Operations)
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 针对华为昇腾(Ascend)后端的逐元素操作(Per-element Operations),这些操作通常用于图…...