Linux:43线程封装与互斥lesson31

news2025/7/19 21:07:39

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; // 我的线程的名字是什么呀?debug
            sleep(1);
        }
    });
    t.Start();
    t.Detach();
    sleep(2);

    t.Stop();

    sleep(2);

    t.Join();

    return 0;
}

 Makefile

test_thread:Main.cc
	g++ -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; // 我的线程的名字是什么呀?debug
            sleep(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.cc
	g++ -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.cc
	g++ -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; // 我的线程的名字是什么呀?debug
            sleep(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创作中心 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“工作区”升级为“磁盘”、数据集统计概览优化|ModelWhale 版本更新

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

【Mysql基础】一、基础入门和常见SQL语句

&#x1f4da;博客主页&#xff1a;代码探秘者-CSDN博客 &#x1f308;&#xff1a;最难不过坚持 ✨专栏 &#x1f308;语言篇C语言\ CJavase基础&#x1f308;数据结构专栏数据结构&#x1f308;算法专栏必备算法&#x1f308;数据库专栏MysqlRedis&#x1f308;必备篇 其他…

AWS之存储服务

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

Jmeter中的Json提取器如何使用?

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

从零理解 C++ 中的原子布尔变量:`std::atomic<bool>` 入门指南

文章目录 引言&#xff1a;为什么需要原子变量&#xff1f;一、什么是 std::atomic<bool>&#xff1f;二、为什么不用普通 bool&#xff1f;一个反面例子三、std::atomic<bool> 的用法四、std::atomic<bool> 的优势五、完整示例&#xff1a;多线程文件传输六…

六个仓库合并为一个仓库,保留master和develop分支的bat脚本

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

养生:通往健康生活的桥梁

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

【前端基础】9、CSS的动态伪类(hover、visited、hover、active、focus)【注:本文只有几个粗略说明】

一、什么是伪类 选择器的一种&#xff0c;用于选择处于特定状态的元素。 最常见的现象&#xff1a;鼠标放在某些文字上面&#xff0c;文字就会加上颜色。 鼠标没放上去之前&#xff1a; 鼠标放上去之后&#xff1a; 二、动态伪类 图片来源&#xff08;链接文章也有其他伪…

Simufact Welding重塑新能源汽车电池盒焊接工艺

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

WordPress 网站上的 jpg、png 和 WebP 图片插件

核心功能 1. 转换 AVIF 并压缩 AVIF 将您 WordPress 网站上的 jpg、png 和 WebP 图片转换为 AVIF 格式&#xff0c;并根据您设置的压缩级别压缩 AVIF 图片。如果原始图片已经是 WordPress 6.5 以上支持的 AVIF 格式&#xff0c;则原始 AVIF 图片将仅被压缩。 2. 转换 WebP 并…

如何应对网站被爬虫和采集?综合防护策略与实用方案

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

AI智慧公园管理方案:用科技重塑市民的“夜游体验”

AI智慧公园管理方案&#xff1a;多场景智能巡检与安全防控 一、背景与痛点分析 夏季夜间&#xff0c;公园成为市民休闲娱乐的核心场所&#xff0c;但管理难度随之激增&#xff1a; 宠物管理失控&#xff1a;未牵绳宠物进入园区&#xff0c;随地排泄、惊扰游客&#xff0c;甚…

LVGL- 按钮矩阵控件

1 按钮矩阵控件 lv_btnmatrix 是 LVGL&#xff08;Light and Versatile Graphics Library&#xff09; v8 中提供的一个非常实用的控件&#xff0c;用于创建带有多个按钮的矩阵布局。它常用于实现虚拟键盘、数字键盘、操作面板、选择菜单等场景&#xff0c;特别适用于嵌入式设…

1. 使用 IntelliJ IDEA 创建 React 项目:创建 React 项目界面详解;配置 Yarn 为包管理器

1. 使用 IntelliJ IDEA 创建 React 项目&#xff1a;创建 React 项目界面详解&#xff1b;配置 Yarn 为包管理器 &#x1f9e9; 使用 IntelliJ IDEA 创建 React 项目&#xff08;附 Yarn 配置与 Vite 建议&#xff09;&#x1f4f7; 创建 React 项目界面详解1️⃣ Name&#xf…

【JVM】从零开始深度解析JVM

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

算法训练营第十四天|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…

在 Elasticsearch 中删除文档中的某个字段

作者&#xff1a;来自 Elastic Kofi Bartlett 探索在 Elasticsearch 中删除文档字段的方法。 更多有关 Elasticsearch 文档的操作&#xff0c;请详细阅读文章 “开始使用 Elasticsearch &#xff08;1&#xff09;”。 想获得 Elastic 认证&#xff1f;查看下一期 Elasticsear…

初识Linux · TCP基本使用 · 回显服务器

目录 前言&#xff1a; 回显服务器 TCPserver_v0 TCPserver_v1--多进程版本 TCPserver_v2--多线程版本 前言&#xff1a; 前文我们介绍了UDP的基本使用&#xff0c;本文我们介绍TCP的基本使用&#xff0c;不过TCP的使用我们这里先做一个预热&#xff0c;即只是使用TCP的A…

【layout组件 与 路由镶嵌】vue3 后台管理系统

前言 很多同学在第一次搭建后台管理系统时&#xff0c;会遇到一个问题&#xff0c;layout组件该放哪里&#xff1f;如何使用&#xff1f;路由又该如何设计&#xff1f; 这边会讲一下我的思考过程和最后的结果&#xff0c;大家可以参考一下&#xff0c;希望大家看完能有所收获。…

mobile自动化测试-appium webdriverio

WebdriverIO是一款支持mobile app和mobile web自动化测试框架&#xff0c;与appium集成&#xff0c;完成对mobile应用测试。支持ios 和android两种平台&#xff0c;且功能丰富&#xff0c;是mobile app自动化测试首选框架。且官方还提供了mobile 应用测试example代码&#xff0…