线程池:线程池的实现 | 日志

news2024/11/20 6:32:05

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

文章目录

  • 原理
  • 线程池实现
  • 日志
    • 获取当前时间函数接口
    • 启用类型设置
    • 输出到屏幕
    • 输出到文件
    • 选择输出方式
    • 创建日志消息
    • 完整代码
  • 携带日志的线程池设计

原理

在一个可执行程序内部存在多个线程和一个任务队列。如果任务队列里长时间没有任务,这些线程就会休眠,如果此时来了一个任务,那么线程就会被唤醒。像这种,提前创建好线程,需要的时候直接使用,我们称之为线程池。这种本质上就是一个生产消费模型。
在这里插入图片描述

线程池实现

//ThreadPool.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<string>
#include<vector>
#include<queue>
#include<functional>
#include"Thread.hpp"

using namespace threadModel;

static const int gdefaultnum=5;

void test()
{
    while(true)
    {
        std::cout<<"hello world"<<std::endl;
        sleep(1);
    }
}

template<typename T>
class ThreadPool
{
private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void WakeupAll()
    {
        pthread_cond_broadcast(&_cond);
    }

    void Sleep()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }

    bool IsEmpty()
    {
        return _task_queue.empty();
    }

    void HandlerTask(const std::string& name)  // this
    {
        while (true)
        {
            LockQueue();
            //如果队列为空(有任务)
            while(IsEmpty()&&_isrunning) //线程没有任务,但是在工作,继续休眠
            {
                _sleep_thread_num++;
                Sleep();
                _sleep_thread_num--;

            }

            if(IsEmpty()&&!_isrunning) // 任务是空的,并且线程退出工作
            {
                std::cout<<name<<" quit..."<<std::endl;
                UnlockQueue();
                break;
            }

            // 队列不为空,有任务 或者 队列被唤醒
            // 取任务
            T t=_task_queue.front();
            _task_queue.pop();
            UnlockQueue();

            // 此处任务已经不在任务队列中,任务已经被拿走,处理任务和临界资源是两码事

            t(); // 处理任务,不能不用也不能在临界区中处理
            std::cout<<name<<": "<<t.result()<<std::endl;
        }
        
    }

public:
    ThreadPool(int thread_num=gdefaultnum)
        :_thread_num(thread_num)
        ,_isrunning(false) //刚开始线程没有使用
        ,_sleep_thread_num(0)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }

    void Init()
    {
        func_t func=std::bind(&ThreadPool::HandlerTask,this,std::placeholders::_1);
        for(int i=0;i<_thread_num;i++)
        {
            std::string threadname="thread-"+std::to_string(i+1);
            _threads.emplace_back(threadname,func);
        }
    }

    void Start()
    {
        _isrunning=true;
        for(auto& thread:_threads)
        {
            thread.Start();
        }
    }

    void Stop()
    {
        LockQueue();
        _isrunning=false;
        WakeupAll();
        UnlockQueue();
    }

    void Equeue(const T &in)
    {
        LockQueue();
        if(_isrunning)
        {
            _task_queue.push(in);
            // 如果当前有线程在等待,需要唤醒
            if(_sleep_thread_num>0)
            {
                Wakeup();
            }
        }

        UnlockQueue();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    int _thread_num;
    std::vector<Thread> _threads;  // 管理多个线程
    std::queue<T> _task_queue; // 任务队列
    bool _isrunning; //当前线程是否在工作
    int _sleep_thread_num;   //计数器:休眠的线程个数

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

在这里插入图片描述

日志

日志是软件运行的记录信息,可以向显示器打印,也可以向文件中打印,日志必须有特定的格式:

  • [日志等级] [pid] [filename] [filenumber] [time] 日志内容(支持可变参数)

日志等级:DEBUG、INFO、WARNING、ERROR、FATAL(致命的)

// 日志等级
enum
{
    DEBUG=1,
    INFO,
    WARING,
    ERROR,
    FATAL
};
  • 日志消息:日志等级、id、文件名、行号、当前时间等
// 日志消息
class logmessage
{

public:
    std::string _level; // 日志等级
    pid_t _id; 
    std::string _filename; // 文件名
    int _filenumber;  // 行号
    std::string _cur_time;
    std::string _message_info;

};

获取当前时间函数接口

std::string GetCurTime()
{
    time_t now=time(nullptr);

    struct tm* cur_time=localtime(&now);
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"%d-%02d-%02d %02d:%02d:%02d",
            cur_time->tm_year+1900,
            cur_time->tm_mon+1,
            cur_time->tm_mday,
            cur_time->tm_hour,
            cur_time->tm_min,
            cur_time->tm_sec);
    return std::string(buffer);
}
  • time(nullptr) 返回当前的时间(从 1970 年 1 月 1 日到现在的秒数),并将其赋值给 now 变量。time_t 是表示时间点的类型。
  • localtime(&now) now 转换为当地时间,并返回一个指向 tm 结构的指针。tm 结构包含了年、月、日、时、分、秒等信息。
    在这里插入图片描述

启用类型设置

void Enable(int type)
{
    _type=type;
}

Enable 函数用于设置日志输出类型,可以选择输出到屏幕或文件。

输出到屏幕

void FlushLogToSCreen(const logmessage& lg)
{
    printf("[%s][%d][%s][%d][%s] %s",
        lg._level.c_str(),
        lg._id,
        lg._filename.c_str(),
        lg._filenumber,
        lg._cur_time.c_str(),
        lg._message_info.c_str()
    );
}

FlushLogToSCreen 函数将日志信息格式化并输出到控制台。使用 printf 格式化字符串。

输出到文件

void FlushLogToFile(const logmessage& lg)
{
    std::ofstream out(_logfile,std::ios::app); // 追加打印
    if(!out.is_open())
        return;

    char logtxt[2048];
    snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s",
        lg._level.c_str(),
        lg._id,
        lg._filename.c_str(),
        lg._filenumber,
        lg._cur_time.c_str(),
        lg._message_info.c_str()
    );

    out.write(logtxt,strlen(logtxt));
    out.close();
}

FlushLogToFile 函数将日志信息写入指定的文件。以追加模式打开文件,并在打开失败时返回。
使用snprintf 格式化日志信息,然后将其写入文件。

选择输出方式

void FlushLog(const logmessage& lg)
{
    switch(_type)
    {
        case SCREEN_TYPE:
            FlushLogToSCreen(lg);
            break;
        case FILE_TYPE:
            FlushLogToFile(lg);
            break;
    }
}

创建日志消息

void logMessage(std::string filename,int filenumber,int level,const char* format,...)
{
    logmessage lg;
    lg._level=LevelToString(level);
    lg._id=getpid();
    lg._filename=filename;
    lg._filenumber=filenumber;
    lg._cur_time=GetCurTime();

    va_list ap;
    va_start(ap,format);
    char log_info[1024];
    vsnprintf(log_info,sizeof(log_info),format,ap);
    va_end(ap);
    
    lg._message_info=log_info;

    FlushLog(lg);
}

  • logMessage 函数用于创建一条新的日志消息。它接受文件名、文件编号、日志级别和格式化字符串作为参数。
  • 使用可变参数处理(va_list)来处理格式化字符串。
  • 将生成的日志信息存储在 lg 对象中,并调用 FlushLog 函数进行输出。
  • va_list ap;声明了一个 va_list 类型的变量 ap,它用于存储可变参数列表。在 C 语言中,va_list 是一个用于遍历不定数量参数的类型。
  • va_start(ap, format);va_start 宏初始化 ap 以指向函数参数列表中的第一个可变参数。这里的 format 是最后一个固定参数,va_start 会从它的下一个参数开始读取可变参数。因此,ap 现在可以用来访问 format 之后的所有参数。
  • va_end(ap)用于清理va_list 变量 ap。在读取完可变参数后,调用 va_end 是良好的实践,它可以释放与 ap 相关的资源(如果有的话)。

完整代码

#pragma once

#include<iostream>
#include<string>
#include<cstring>
#include<sys/types.h>
#include<unistd.h>
#include<stdarg.h>
#include<ctime>
#include<fstream>
#include<pthread.h>
#include<cstdio>
#include"LockGuard.hpp"

using std::cin;
using std::cout;
using std::endl;

namespace log_ns
{

    // 日志等级
    enum
    {
        DEBUG=1,
        INFO,
        WARING,
        ERROR,
        FATAL
    };

    // 日志消息
    class logmessage
    {

    public:
        std::string _level; // 日志等级
        pid_t _id; 
        std::string _filename; // 文件名
        int _filenumber;  // 行号
        std::string _cur_time;
        std::string _message_info;

    };

    pthread_mutex_t glock=PTHREAD_MUTEX_INITIALIZER; // 定义一个全局的锁

    std::string LevelToString(int level)
    {
        LockGuard lockguard(&glock);
        switch(level)
        {
            case DEBUG:
                return "DEBUG";
            case INFO:
                return "INFO";
            case WARING:
                return "WARING";
            case ERROR:
                return "ERROR";
            case FATAL:
                return "FATAL";
            default:
                return "UNKNOWN";
        }
    }

    std::string GetCurTime()
    {
        time_t now=time(nullptr);

        struct tm* cur_time=localtime(&now);
        char buffer[128];
        snprintf(buffer,sizeof(buffer),"%d-%02d-%02d %02d:%02d:%02d",
                cur_time->tm_year+1900,
                cur_time->tm_mon+1,
                cur_time->tm_mday,
                cur_time->tm_hour,
                cur_time->tm_min,
                cur_time->tm_sec);
        return std::string(buffer);
    }

    #define SCREEN_TYPE 1
    #define FILE_TYPE 2

    const std::string glogfile="./log.txt";
    // 日志
    class Log
    {
    public:
        Log(const std::string& logfeile=glogfile):_logfile(logfeile),_type(SCREEN_TYPE)
        {

        }

        void Enable(int type)
        {
            _type=type;
        }

        void FlushLogToSCreen(const logmessage& lg)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._cur_time.c_str(),
                lg._message_info.c_str()
            );
        }

        void FlushLogToFile(const logmessage& lg)
        {
            std::ofstream out(_logfile,std::ios::app); // 追加打印
            if(!out.is_open())
                return;

            char logtxt[2048];
            snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._cur_time.c_str(),
                lg._message_info.c_str()
            );

            out.write(logtxt,strlen(logtxt));
            out.close();
        }

        void FlushLog(const logmessage& lg)
        {

            switch(_type)
            {
                case SCREEN_TYPE:
                    FlushLogToSCreen(lg);
                    break;
                case FILE_TYPE:
                    FlushLogToFile(lg);
                    break;
            }
        }

        void logMessage(std::string filename,int filenumber,int level,const char* format,...)
        {
            logmessage lg;
            lg._level=LevelToString(level);
            lg._id=getpid();
            lg._filename=filename;
            lg._filenumber=filenumber;
            lg._cur_time=GetCurTime();

            va_list ap;
            va_start(ap,format);
            char log_info[1024];
            vsnprintf(log_info,sizeof(log_info),format,ap);
            va_end(ap);
            
            lg._message_info=log_info;

            //cout<<lg._message_info<<endl;
            // 打印日志
            FlushLog(lg);

        }

        ~Log()
        {

        }

    private:
        int _type;
        std::string _logfile;
    };

    Log lg;

    #define LOG(Level,Format,...) do{lg.logMessage(__FILE__,__LINE__,Level,Format,##__VA_ARGS__);}while(0)
    #define EnableScreen() do{lg.Enable(SCREEN_TYPE);}while(0)
    #define EnableFile() do{lg.Enable(FILE_TYPE);}while(0)

};

在这里插入图片描述

携带日志的线程池设计

//ThreadPool.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<string>
#include<vector>
#include<queue>
#include<functional>
#include"Thread.hpp"
#include"Log.hpp"

using namespace threadModel;
using namespace log_ns;

static const int gdefaultnum=5;

void test()
{
    while(true)
    {
        std::cout<<"hello world"<<std::endl;
        sleep(1);
    }
}

template<typename T>
class ThreadPool
{
private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void WakeupAll()
    {
        pthread_cond_broadcast(&_cond);
    }

    void Sleep()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }

    bool IsEmpty()
    {
        return _task_queue.empty();
    }

    void HandlerTask(const std::string& name)  // this
    {
        while (true)
        {
            LockQueue();
            //如果队列为空(有任务)
            while(IsEmpty()&&_isrunning) //线程没有任务,但是在工作,继续休眠
            {
                _sleep_thread_num++;
                LOG(INFO,"%s thread sleep begin!\n",name.c_str());

                Sleep();

                LOG(INFO,"%s thread wakeup!\n",name.c_str());

                _sleep_thread_num--;

            }

            if(IsEmpty()&&!_isrunning) // 任务是空的,并且线程退出工作
            {
                UnlockQueue();
                LOG(INFO,"%s quit\n",name.c_str());
                break;
            }

            // 队列不为空,有任务 或者 队列被唤醒
            // 取任务
            T t=_task_queue.front();
            _task_queue.pop();
            UnlockQueue();

            // 此处任务已经不在任务队列中,任务已经被拿走,处理任务和临界资源是两码事

            t(); // 处理任务,不能不用也不能在临界区中处理
            LOG(DEBUG,"hander task done, task is: \n%s",t.result().c_str());
        }
        
    }

public:
    ThreadPool(int thread_num=gdefaultnum)
        :_thread_num(thread_num)
        ,_isrunning(false) //刚开始线程没有使用
        ,_sleep_thread_num(0)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }

    void Init()
    {
        func_t func=std::bind(&ThreadPool::HandlerTask,this,std::placeholders::_1);
        for(int i=0;i<_thread_num;i++)
        {
            std::string threadname="thread-"+std::to_string(i+1);
            _threads.emplace_back(threadname,func);
            LOG(DEBUG,"construct thread %s done, init success.\n",threadname.c_str());
        }
    }

    void Start()
    {
        _isrunning=true;
        for(auto& thread:_threads)
        {
            LOG(DEBUG,"Start thread %s done.\n",thread.Name().c_str());
            thread.Start();
        }
    }

    void Stop()
    {
        LockQueue();
        _isrunning=false;
        WakeupAll();
        UnlockQueue();
        LOG(INFO,"Thread Pool Stop Success!\n");
    }

    void Equeue(const T &in)
    {
        LockQueue();
        if(_isrunning)
        {
            _task_queue.push(in);
            // 如果当前有线程在等待,需要唤醒
            if(_sleep_thread_num>0)
            {
                Wakeup();
            }
        }

        UnlockQueue();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    int _thread_num;
    std::vector<Thread> _threads;  // 管理多个线程
    std::queue<T> _task_queue; // 任务队列
    bool _isrunning; //当前线程是否在工作
    int _sleep_thread_num;   //计数器:休眠的线程个数

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

//Main.cc
#include"ThreadPool.hpp"
#include"Task.hpp"
#include"Log.hpp"
#include<memory>

using std::cin;
using std::cout;
using std::endl;
using namespace log_ns;

int main()
{

    EnableScreen();
    //std::unique_ptr<ThreadPool> tp=std::make_unique<>();  //构建一个ThreadPool对象
    ThreadPool<Task> *tp=new ThreadPool<Task>();
    
    tp->Init();

    tp->Start();

    int cnt=10;
    while (cnt)
    {
        // 不断地向线程池推送任务
        sleep(1);
        Task t(1,1);
        tp->Equeue(t);
        LOG(INFO,"equeue a task, %s\n",t.debug().c_str());
        sleep(1);
    }
    tp->Stop();
    LOG(INFO,"thread pool stop!\n");
    sleep(10);

    return 0;
}
// Thread.hpp
#pragma once
#include <pthread.h>
#include <iostream>
#include <string>
#include<functional>

namespace threadModel
{
    // 线程执行的方法
    //typedef void (*func_t)(ThreadData* td);
    using func_t=std::function<void(const std::string&)>;

    class Thread
    {
    public:
        void Excute()
        {
            _isrunning = true;
            _func(_name);
            _isrunning = false;
        }

    public:
        Thread(const std::string &name, func_t func) : _name(name), _func(func)
        {
        }
        // void *ThreadRoutine(void* args)实际上参数里面还有一个Thread* this
        static void *ThreadRoutine(void *args) // 加上static后,参数里面就没有Thread* this
        {
            Thread *self = static_cast<Thread *>(args); // 获得当前对象
            self->Excute();
            return nullptr;
        }

        bool Start()
        {
            int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
            if (n != 0)
                return false;
            return true;
        }

        std::string Status()
        {
            if (_isrunning)
                return "running";
            else
                return "sleep";
        }

        void Stop()
        {
            if (_isrunning)
            {
                pthread_cancel(_tid);
                _isrunning = false;
            }
        }

        void Join()
        {
            pthread_join(_tid, nullptr);
        }
        
        std::string Name()
        {
            return _name;
        }

        ~Thread()
        {
            Stop();
        }

    private:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func; // 线程执行的回调函数
    };
}
//Task.hpp
#pragma once
#include<iostream>
#include<string>
#include<functional>

class Task
{

public:
    Task()
    {}
    Task(int x,int y):_x(x),_y(y)
    {}

    void Excute()
    {
        _result=_x+_y;
    }

    void operator()()
    {
        Excute();
    }

    std::string debug()
    {
        std::string msg=std::to_string(_x)+"+"+std::to_string(_y)+"=?";
        return msg;
    }
    std::string result()
    {
        std::string msg=std::to_string(_x)+"+"+std::to_string(_y)+"="+std::to_string(_result);
        return msg;
    }

private:
    int _x;
    int _y;
    int _result;
};

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

C++容器之vector模拟实现(代码纯享版!!!)

目录 前言 一、头文件 .h文件 总结 前言 本文是模拟实现vector部分功能的代码&#xff0c;可以直接拿去使用 一、头文件 .h文件 #include<assert.h> #include<iostream> using namespace std; namespace zz {template<class T>class vector{public:typedef…

C++ set,multiset与map,multimap的基本使用

1. 序列式容器和关联式容器 string、vector、list、deque、array、forward_list等STL容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间一般没有紧密的关联关系&#xff0c;比如交换一下&#xff0c;他依旧是序列式容器。顺…

STM32器件支持包安装,STLINK/JLINK驱动安装

一、支持包安装 1、离线安装 先下载支持包之后&#xff0c;再进行安装。如下图要安装STM32F1系列&#xff0c;双击 出现如下&#xff0c;会自动锁定安装路径&#xff0c;然后点击下一步&#xff0c;直接安装。 2、在线安装 首先需要电脑联网。如下。先点击第一个红框绿色按钮…

常见的VPS或者独立服务器的控制面板推荐

随着越来越多的企业和个人转向VPS和独立服务器以获得更高的性能和灵活性&#xff0c;选择合适的控制面板变得尤为重要。一个好的控制面板可以大大简化服务器管理&#xff0c;提高工作效率。本篇文章将介绍2024年最值得推荐的VPS控制面板&#xff0c;帮助您做出明智的选择。 1.…

STL容器适配器

欢迎来到本期节目- - - STL容器适配器 适配器模式&#xff1a; 在C中&#xff0c;适配器是一种设计模式&#xff0c;有时也称包装样式&#xff1b; 通过将类自己的接口包裹在一个已存在的类中&#xff0c;使得因接口不兼容而不能在一起工作的类能在一起工作&#xff1b; 也就…

使用VBA快速生成Excel工作表非连续列图片快照

Excel中示例数据如下图所示。 现在需要拷贝A2:A15,D2:D15,J2:J15,L2:L15,R2:R15为图片&#xff0c;然后粘贴到A18单元格&#xff0c;如下图所示。 大家都知道VBA中Range对象有CopyPicture方法可以拷贝为图片&#xff0c;但是如果Range对象为非连续区域&#xff0c;那么将产生10…

详解DHCP服务工作原理及配置案例

一. DHCP概述 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一个主机IP简化分配管理的TCP/IP协议&#xff0c;用户通过DHCP服务器动态的分配给客户端IP地址及其他环境的配置工作&#xff0c;包括IP地址、子网掩码、网关和…

【NVIDIA】如何使用nvidia-smi命令管理和监控GPU

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

KPConv: Flexible and Deformable Convolution for Point Clouds

Abstract Kernel Point Convolution&#xff08;KPConv&#xff09;是一种点云卷积方法&#xff0c;它可以直接在点云数据上进行操作&#xff0c;无需任何中间的表示形式。方法的核心在于使用核点来定义卷积权重&#xff0c;核点位于欧几里得空间中&#xff0c;并仅对靠近它们…

Spring DI 笔记

目录 1.什么是DI? 2.依赖注入的三种⽅式 2.1属性注⼊ 2.2构造⽅法注⼊ 2.3Setter 注⼊ 2.4三种注⼊优缺点分析 3.Autowired存在问题 1.什么是DI? DI: 依赖注⼊ 依赖注⼊是⼀个过程&#xff0c;是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源&#xff0c;⽽资源指的…

(JAVA)浅尝关于 “栈” 数据结构

1. 栈的概述&#xff1a; 1.1 生活中的栈 存储货物或供旅客住宿的地方&#xff0c;可引申为仓库、中转站。例如酒店&#xff0c;在古时候叫客栈&#xff0c;是供旅客休息的地方&#xff0c;旅客可以进客栈休息&#xff0c;休息完毕后就离开客栈 1.2计算机中的栈 将生活中的…

第1 章 第一节:基础语法

第1 章 第一节&#xff1a;基础语法 1.1书写规则 1.1.1关键字 在Java语言中&#xff0c;已经定义好的&#xff0c;具有一定的功能和作用的英文单词。所有的关键字都是小写的 在Java中总共有51个关键字&#xff0c;还有两个保留字const\goto. 常见的关键字&#xff1a; if…

User-Agent在WebMagic爬虫中的重要性

对于需要从网站上抓取数据的开发者来说&#xff0c;WebMagic是一个强大的工具。它是一个简单灵活的Java爬虫框架&#xff0c;用于抓取网页数据。在爬虫技术中&#xff0c;User-Agent&#xff08;用户代理&#xff09;是一个关键的HTTP请求头&#xff0c;它告诉服务器关于客户端…

中九无科研无竞赛保研经验帖——上交软院、中科大计算机、复旦工程硕、南大工程硕、浙大软件

本人bg: 学校&#xff1a;中九软件工程rk&#xff1a;夏令营5%&#xff0c;预推免3%&#xff08;都是写的预估排名&#xff09;六级&#xff1a;480&#xff0c; 四级&#xff1a;540科研&#xff1a;无竞赛&#xff1a;美赛M&#xff0c;以及水赛国三、省二若干 保研前期没有…

jenkins项目发布基础

随着软件开发需求及复杂度的不断提高,团队开发成员之间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。Jenkins 自动化部署可以解决集成、测试、部署等重复性的工作,工具集成的效率明显高于人工操作;并且持续集成可以更早的获取代码变更的信息,…

向日葵远程控制怎么下载?推荐4个远程控制工具网站。

从官网下载的软件质量有保障&#xff0c;安全性和可信度也比较好。所以不管需要使用什么样的软件&#xff0c;最好是到官网下载。如果是有远程控制的需求&#xff0c;我可以推荐几个安全可靠的网站给大家。 &#xff11;、向日葵远程控制大师 直达链接&#xff1a;https://dow…

【STM32单片机_(HAL库)】4-3【定时器TIM】定时器输出PWM实现呼吸灯实验

1.硬件 STM32单片机最小系统LED灯模块 2.软件 pwm驱动文件添加定时器HAL驱动层文件添加GPIO常用函数定时器输出PWM配置步骤main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "pwm.h"int main(void) {HA…

运用循环单链表实现约瑟夫问题

代码&#xff1a; #include <iostream> using namespace std; struct node {int id;node *next; }; int len0; //存现在链表的长度int main() {node*head,*temp,*tail;headnew node;head->next head;tailhead;int A,B;cin>>A>>B;lenA;int num1;while…

unity一键注释日志和反注释日志

开发背景&#xff1a;游戏中日志也是很大的开销&#xff0c;虽然有些日志不打印但是毕竟有字符串的开销&#xff0c;甚至有字符串拼接的开销&#xff0c;有些还有装箱和拆箱的开销&#xff0c;比如Debug.Log(1) 这种 因此需要注释掉&#xff0c;当然还需要提供反注释的功能&am…

数据结构-链表笔记

移除节点 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListN…