当前位置: 首页 > article >正文

线程池以及日志、线程总结

一、线程池以及日志

1、基础线程池写法

主线程在main函数中构建一个线程池,初始化(Init)后开始工作(Start)

此时线程池中每个线程都已经工作起来了,只是任务队列中任务为空,所有线程处于休眠状态(通过线程同步中的条件变量实现,即pthread_cond_wait)

当主线程向任务队列中添加任务后,唤醒一个在休眠中的线程,让其执行任务,执行完后再进入休眠状态。

基础的线程池的写法:

ThreadPool.hpp:

#pragma once#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "mythreadlib.hpp"
#define DEFAULT_THREAD_NUMS 5template <typename T> // 什么类型的任务
class ThreadPool
{private:void test(const std::string name){while (1){std::cout << name << " is running" << std::endl;sleep(1);}}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnlockQueue(){pthread_mutex_unlock(&_mutex);}bool QueueIsEmpty(){return _task_queue.empty();}void Sleep(){pthread_cond_wait(&_cond, &_mutex);}void Wake(){pthread_cond_signal(&_cond);}void WakeAll(){pthread_cond_broadcast(&_cond);}void HandlerTask(const std::string name){while (1){LockQueue();while (QueueIsEmpty() && _isrunning){_sleep_nums++;Sleep();_sleep_nums--;}if (QueueIsEmpty() && !_isrunning){UnlockQueue();break;}// 苏醒过来 执行任务T t = _task_queue.front();_task_queue.pop();UnlockQueue();std::cout<<name<<" is running:";t(); // 执行任务和获取任务一定要分开,一个在锁内,一个在锁外}}public:ThreadPool(int threadnums = DEFAULT_THREAD_NUMS): _threadnums(threadnums), _isrunning(false), _sleep_nums(0){//_my_threads.resize(_threadnums); 不能resize会有问题pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Init(){fun_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);// fun_t func = std::bind(&ThreadPool::test, this, std::placeholders::_1);for (int i = 0; i < _threadnums; i++){std::string name = "thread-" + std::to_string(i + 1);_my_threads.emplace_back(name, func); // 要将test改成处理Task的函数}}void Start(){_isrunning = true;for (auto &thread : _my_threads){thread.Start();}}void Stop(){LockQueue();_isrunning = false;WakeAll();UnlockQueue();}void Enqueue(const T &in){// 主线程向线程池中派发任务,让线程池中的线程执行任务LockQueue();if (_isrunning){_task_queue.push(in);if(_sleep_nums>0) Wake();//只要有休眠的线程就进行唤醒}UnlockQueue();}private:int _threadnums;std::vector<mythread> _my_threads;int _sleep_nums;bool _isrunning;           // 也是临界资源std::queue<T> _task_queue; // 临界资源pthread_mutex_t _mutex;pthread_cond_t _cond;
};

mythreadlib.hpp:

#pragma once#include <pthread.h>
#include <iostream>
#include <string>
#include <functional>using fun_t = std::function<void(const std::string &)>;class mythread
{// typedef void (*fun_t)(ThreadData *td);public:mythread(const std::string name, fun_t func): _name(name), _func(func){_isrunning = false;}static void *threadRun(void *args){mythread *thread = static_cast<mythread *>(args);// thread->_func(thread->_td);thread->_func(thread->_name);thread->_isrunning = false; // 运行结束return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadRun, (void *)this);if (n != 0)return false;else{_isrunning = true;return true;}}void Stop(){if (_isrunning){_isrunning = false;::pthread_cancel(_tid);}}void Join(){pthread_join(_tid, nullptr);}~mythread(){if (_isrunning){Stop();Join();}}private:pthread_t _tid;std::string _name;bool _isrunning;fun_t _func;
};

task.hpp:

#pragma once#include<iostream>
#include<functional>// typedef std::function<void()> task_t;
// using task_t = std::function<void()>;// void Download()
// {
//     std::cout << "我是一个下载的任务" << std::endl;
// }// 要做加法
class Task
{
public:Task(){}Task(int x, int y) : _x(x), _y(y){}void Excute(){_result = _x + _y;std::cout<<result()<<std::endl;}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;
};

main.cpp:

#include"ThreadPool.hpp"
#include"task.hpp"
#include<ctime>
#include<unistd.h>int main()
{//线程池srand(time(nullptr)^getpid());ThreadPool<Task> *thread_pool=new ThreadPool<Task>();thread_pool->Init();thread_pool->Start();while(1){int num1=rand()%9+1;int num2=rand()%9+1;Task ADD(num1,num2);thread_pool->Enqueue(ADD);sleep(1);}return 0;
}

2、日志

(1)日志概念

日志:软件运行的记录信息,可以向显示器打印,也可以向文件中打印,可以根据信息的重要程度使用不同的打印格式。

(2)基本格式

【日志等级】【pid】【filename】【filenumber】【time】日志内容(支持可变参数)

其中日志等级可分为:DEBUG(调试)、INFO(信息)、 WARNING(警告) 、ERROR(错误) 、FATAL(致命的)

可变参数的处理:用va_list指向可变部分, vsnprintf()

29. C语言 可变参数详解-CSDN博客

使用宏优化日志格式。

最终形成的日志:

#pragma once#include <ctime>
#include <string>
#include <unistd.h>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include "LockGuard.hpp"
#include <pthread.h>
#define FLUSHSCREEN 1
#define FLUSHFILE 2
const std::string default_file = "log.txt";enum
{DEBUG = 1,INFO,WARNING,ERROR,FATAL
};class logmessage
{
public:std::string _level;int _id;std::string _filename;int _filenumber;std::string _time;std::string _content;
};std::string LevelToString(int level)
{switch (level){case 1:return "DEBUG";break;case 2:return "INFO";break;case 3:return "WARNING";break;case 4:return "ERROR";break;case 5:return "FATAL";break;}
}std::string CurrentTime()
{time_t now = time(nullptr);struct tm *curr_time = localtime(&now);char time_string[128];snprintf(time_string, sizeof(time_string), "%d-%02d-%02d %02d:%02d:%02d", curr_time->tm_year + 1900, // 02d表示的是控制输出格式为2位整数,不够2位的用0填充curr_time->tm_mon + 1,curr_time->tm_mday,curr_time->tm_hour,curr_time->tm_min,curr_time->tm_sec);return time_string;
}pthread_mutex_t _mutex=PTHREAD_MUTEX_INITIALIZER;
class Log
{
public:Log(const std::string flush_file = default_file, int flush_type = FLUSHSCREEN): _flush_file(flush_file), _flush_type(flush_type){//pthread_mutex_init(&_mutex, nullptr);}~Log(){//pthread_mutex_destroy(&_mutex);}void logMessage(int level, const std::string filename, int filenumber, const char *format, ...) // 可变参数{logmessage logm;logm._level = LevelToString(level);logm._id = getpid();logm._filename = filename;logm._filenumber = filenumber;va_list vl;va_start(vl, format);char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, vl);va_end(vl);logm._content = log_info;logm._time = CurrentTime();FlushLog(logm);}void Enable(int type){_flush_type = type;}void FlushLog(logmessage &log){//pthread_mutex_lock(&_mutex);lockguard lock(&_mutex);switch (_flush_type){case FLUSHSCREEN:FlushToScreen(log);break;case FLUSHFILE:FlushToFile(log);break;}//pthread_mutex_unlock(&_mutex);}void FlushToScreen(logmessage &log){printf("[%s][%d][%s][%d][%s] %s", log._level.c_str(), log._id, log._filename.c_str(), log._filenumber, log._time.c_str(), log._content.c_str());}void FlushToFile(logmessage &log){std::ofstream out(_flush_file, std::ios::app);char message[1024];snprintf(message, sizeof(message), "[%s][%d][%s][%d][%s] %s", log._level.c_str(), log._id, log._filename.c_str(), log._filenumber, log._time.c_str(), log._content.c_str());out.write(message, strlen(message));out.close();}private:std::string _flush_file;int _flush_type;};// 优化
Log glog;#define LOG(level, format, ...)                                            \do                                                                     \{                                                                      \glog.logMessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__); \} while (0) // 带两个#是为了处理可变参数为空的情况#define EnableScreen()  \do                  \{                   \glog.Enable(1); \} while (0)#define EnableFile()    \do                  \{                   \glog.Enable(2); \} while (0)

3、单例模式

单例模式:一个类只创建一个对象

单例模式实现方式有两种:懒汉模式和饿汉模式。

4、最终代码

经过单例模式的修改,最终形成的线程池代码:

Threadpool.hpp:

#pragma once#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "mythreadlib.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"
#define DEFAULT_THREAD_NUMS 5template <typename T> // 什么类型的任务
class ThreadPool
{private:void test(const std::string name){while (1){std::cout << name << " is running" << std::endl;sleep(1);}}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnlockQueue(){pthread_mutex_unlock(&_mutex);}bool QueueIsEmpty(){return _task_queue.empty();}void Sleep(){pthread_cond_wait(&_cond, &_mutex);}void Wake(){pthread_cond_signal(&_cond);}void WakeAll(){pthread_cond_broadcast(&_cond);}void HandlerTask(const std::string name){while (1){LockQueue();while (QueueIsEmpty() && _isrunning){_sleep_nums++;LOG(DEBUG, "%s begin sleep\n", name.c_str());Sleep();LOG(DEBUG, "%s wake up\n", name.c_str());_sleep_nums--;}if (QueueIsEmpty() && !_isrunning){UnlockQueue();break;}// 苏醒过来 执行任务T t = _task_queue.front();_task_queue.pop();UnlockQueue();t(); // 执行任务和获取任务一定要分开,一个在锁内,一个在锁外LOG(DEBUG, "%s Handler task done\n", name.c_str());}}ThreadPool(int threadnums = DEFAULT_THREAD_NUMS): _threadnums(threadnums), _isrunning(false), _sleep_nums(0){//_my_threads.resize(_threadnums); 不能resize会有问题pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}ThreadPool(const ThreadPool<T> &) = delete;void operator=(const ThreadPool<T> &) = delete;//设置拷贝构造和=操作符重载为deletevoid Init(){fun_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);// fun_t func = std::bind(&ThreadPool::test, this, std::placeholders::_1);for (int i = 0; i < _threadnums; i++){std::string name = "thread-" + std::to_string(i + 1);_my_threads.emplace_back(name, func); // 要将test改成处理Task的函数LOG(DEBUG, "construct %s done,init success\n", name.c_str());}}void Start(){_isrunning = true;for (auto &thread : _my_threads){thread.Start();LOG(DEBUG, "start %s done\n", thread.getname().c_str());}}public:static ThreadPool<T> *Get_Instance(){if (_tp == nullptr){lockguard lock(&_tp_mutex);if (_tp == nullptr){_tp = new ThreadPool<T>;_tp->Init();_tp->Start();LOG(DEBUG,"Thread pool create success!\n");}}return _tp;}void Stop(){LockQueue();_isrunning = false;WakeAll();UnlockQueue();LOG(DEBUG,"all thread stop success!\n");}void Enqueue(const T &in){// 主线程向线程池中派发任务,让线程池中的线程执行任务LockQueue();if (_isrunning){_task_queue.push(in);if (_sleep_nums > 0)Wake(); // 只要有休眠的线程就进行唤醒}UnlockQueue();}private:int _threadnums;std::vector<mythread> _my_threads;int _sleep_nums;bool _isrunning;           // 也是临界资源std::queue<T> _task_queue; // 临界资源pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T> *_tp;static pthread_mutex_t _tp_mutex;
};template <typename T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;template <typename T>
pthread_mutex_t ThreadPool<T>::_tp_mutex = PTHREAD_MUTEX_INITIALIZER;

main.cpp:

#include "ThreadPool.hpp"
#include "task.hpp"
#include "Log.hpp"
#include <ctime>
#include <unistd.h>/*int main()
{//线程池srand(time(nullptr)^getpid());ThreadPool<Task> *thread_pool=new ThreadPool<Task>();thread_pool->Init();thread_pool->Start();while(1){int num1=rand()%9+1;int num2=rand()%9+1;Task ADD(num1,num2);thread_pool->Enqueue(ADD);sleep(1);}return 0;
}*//*int main()
{//测试未优化的日志Log log1;log1.logMessage(DEBUG, __FILE__, __LINE__, "this is a logmessage of %d,%f\n", 30, 3.14);sleep(1);log1.logMessage(DEBUG, __FILE__, __LINE__, "this is a logmessage of %d,%f\n", 30, 3.14);log1.Enable(FLUSHFILE);sleep(1);log1.logMessage(DEBUG, __FILE__, __LINE__, "this is a logmessage of %d,%f\n", 30, 3.14);sleep(1);log1.logMessage(DEBUG, __FILE__, __LINE__, "this is a logmessage of %d,%f\n", 30, 3.14);return 0;
}*//*int main()
{EnableScreen();//LOG(DEBUG,"YES");//依然支持LOG(DEBUG,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(WARNING,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(ERROR,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(INFO,"this is a logmessage of %d,%f\n", 30, 3.14);EnableFile();LOG(DEBUG,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(WARNING,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(ERROR,"this is a logmessage of %d,%f\n", 30, 3.14);LOG(INFO,"this is a logmessage of %d,%f\n", 30, 3.14);return 0;
}*//*int main()
{//线程池srand(time(nullptr)^getpid());ThreadPool<Task> *thread_pool=new ThreadPool<Task>();thread_pool->Init();thread_pool->Start();while(1){int num1=rand()%9+1;int num2=rand()%9+1;Task ADD(num1,num2);thread_pool->Enqueue(ADD);LOG(INFO,"task is product:%s\n",ADD.debug().c_str());sleep(1);}return 0;
}*/int main()
{// 单例模式线程池的测试srand(time(nullptr) ^ getpid());int cnt=10;while (cnt--){int num1 = rand() % 9 + 1;int num2 = rand() % 9 + 1;Task ADD(num1, num2);ThreadPool<Task>::Get_Instance()->Enqueue(ADD);sleep(1);}ThreadPool<Task>::Get_Instance()->Stop();return 0;
}

二、线程概念拓展

1、可重入和线程安全

(1)概念

(2)理解

一个函数是可重入的,那么它一定是线程安全的;但一个线程是安全的,不表示该线程内的函数是可重入的,其内部的函数可能是不可重入的,例如加锁但不释放的函数,是线程安全但不可重入函数。

2、死锁

(1)死锁概念

一个线程一把锁也可能形成死锁,例如对同一个锁加锁两次而未释放。

但更多的情况是俩个执行流,两把锁,要推进代码需要两把锁都申请到,但俩个执行流分别申请一把锁,而不释放,都同时申请对方的锁。

(2)死锁的必要条件

(3)避免死锁

破坏必要条件:

①尽量不使用锁

②当某优先级比较高的线程申请不到锁的时候强制释放锁

③申请锁的代码的顺序要一致

3、STL,智能指针和线程安全

相关文章:

线程池以及日志、线程总结

一、线程池以及日志 1、基础线程池写法 主线程在main函数中构建一个线程池&#xff0c;初始化(Init)后开始工作(Start) 此时线程池中每个线程都已经工作起来了&#xff0c;只是任务队列中任务为空&#xff0c;所有线程处于休眠状态(通过线程同步中的条件变量实现&#xff0c…...

Vue 响应式渲染 - 过滤应用

Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue响应式渲染综合 - 过滤应用 目录 过滤应用 引入vue Vue设置 设置页面元素 模糊查询过滤实现 函数表达式实现 总结 过滤应用 综合响应式渲染做一个输入框&#xff0c;用来实现&#xff1b;搜索输入框关键词符合列表。…...

【ThreeJS Basics 1-3】Hello ThreeJS,实现第一个场景

文章目录 环境创建一个项目安装依赖基础 Web 页面概念解释编写代码运行项目 环境 我的环境是 node version 22 创建一个项目 首先&#xff0c;新建一个空的文件夹&#xff0c;然后 npm init -y , 此时会快速生成好默认的 package.json 安装依赖 在新建的项目下用 npm 安装依…...

银行国际结算

银行国结项目&#xff0c;即国际结算项目&#xff0c;是银行业务中的重要组成部分&#xff0c;它涉及跨国界的货币收付和资金转移。 一、银行国结项目的定义 银行国结项目是指银行为国际贸易、投资等活动提供的国际结算服务&#xff0c;包括各种国际支付和资金清算业务。这些…...

Go语言构建微服务:从入门到实战

引言 在云原生时代&#xff0c;微服务架构已成为构建复杂分布式系统的首选方案。Go语言凭借其卓越的并发支持、简洁的语法和高效的运行时&#xff0c;成为微服务开发的利器。本文将深入探讨如何用Go构建健壮的微服务系统&#xff0c;并通过完整案例演示关键实现细节。 一、微…...

深入理解动态代理

为什么需要动态代理 对于代码的增强逻辑我们是清楚具体实现的,一种方式是增强逻辑作为委托类,被其他业务类调用, 这样会有很多重复代码,而且,当需要根据动态参数来决定增强逻辑时,重复代码会更多,逻辑会更不清晰 二,也是动态代理产生的原始需求,解决类爆照问题, 所以…...

私有属性和方法(python)

一、私有属性&#xff08;属性名前面加两个短下划线&#xff09; &#xff08;一&#xff09;私有属性与公有属性区别 公有属性&#xff1a;在类里面和外面均可以访问和修改 私有属性&#xff1a;需要用set方法才能修改&#xff0c;get方法才能访问 &#xff08;二&#xf…...

Cherry Studio之DeepSeek联网/本地,建属于自己的AI助理!

上一篇文章&#xff0c;讲了DeepSeek-R1部署到本地的方法。这一篇文章&#xff0c;我们让DeepSeek再一次升级&#xff0c;通过图形化界面来交互&#xff0c;从而变成我们的AI助理&#xff0c;让DeepSeek R1发挥最大实力&#xff01; 首选需要借助硅基流动的API接口&#xff0c…...

TcpClientTest

ClientTest&#xff1a; using System; using System.Net.Sockets; using System.Text;class TcpClientTest {static void Main(string[] args){try{// 创建一个TcpClient实例并连接到服务器 TcpClient client new TcpClient("1vg5062570.51mypc.cn", 43319);//1v…...

IGBT的两级关断

IGBT&#xff08;绝缘栅双极型晶体管&#xff09;的两级关断&#xff08;Two-stage turn-off&#xff09;是一种优化关断过程的方法&#xff0c;主要用于减少关断时的电压过冲和dv/dt&#xff08;电压变化率&#xff09;过高的问题&#xff0c;特别是在大功率应用中&#xff08…...

【STM32】ADC

本次实现的是ADC实现数字信号与模拟信号的转化&#xff0c;数字信号时不连续的&#xff0c;模拟信号是连续的。 1.ADC转化的原理 模拟-数字转换技术使用的是逐次逼近法&#xff0c;使用二分比较的方法来确定电压值 当单片机对应的参考电压为3.3v时&#xff0c;0~ 3.3v(模拟信号…...

从MyBatis-Plus看Spring Boot自动配置原理

一、问题引入&#xff1a;神秘的配置生效之谜 当我们使用MyBatis-Plus时&#xff0c;只需在pom.xml中添加依赖&#xff1a; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3…...

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 0基础…...

雪花算法应用

什么是雪花算法&#xff1f; 雪花算法是由 Twitter 开源的分布式 ID 生成算法&#xff0c;用于生成 64 位的长整型唯一 ID。其结构如下&#xff1a; - 1 位符号位&#xff1a;始终为 0 - 41 位时间戳&#xff1a;精确到毫秒 - 10 位工作机器 ID&#xff1a;包含 5 位数据中心 …...

Chapter3:结构化程序设计

参考书籍&#xff1a;《C#边做边学》&#xff1b; 3.结构化程序设计 3.1 结构化程序设计的3种基本结构 顺序结构&#xff1a;先执行 A {\rm A} A语句&#xff0c;再执行 B {\rm B} B语句&#xff0c;两者是顺序执行的关系&#xff1b; 选择结构&#xff1a;根据所定选择条件为…...

白话文实战Nacos(保姆级教程)

前言 上一篇博客 我们创建好了微服务项目,本篇博客来体验一下Nacos作为注册中心和配置中心的功能。 注册中心 如果我们启动了一个Nacos注册中心,那么微服务比如订单服务,启动后就可以连上注册中心把自己注册上去,这过程就是服务注册。每个微服务,比如商品服务都应该注册…...

c语言函数学习

C语言函数学习笔记&#xff1a;从入门到实践 一、什么是函数&#xff1f; 函数是C语言中用于封装特定功能的代码块&#xff0c;是模块化编程的核心。通过函数可以实现&#xff1a; 代码复用&#xff1a;避免重复编写相同逻辑 逻辑清晰&#xff1a;将复杂程序分解为多个小模块…...

linux利用nfs服务器,实现数据和windows环境拷贝

1. 在Linux上设置NFS服务器 1.1 安装NFS服务器软件 首先&#xff0c;你需要在Linux服务器上安装NFS服务器软件。假设你使用的是基于Debian的系统&#xff08;如Ubuntu&#xff09;&#xff0c;可以按照以下步骤操作&#xff1a; sudo apt update sudo apt install nfs-kerne…...

智能理解 PPT 内容,快速生成讲解视频

当我们想根据一版 PPT 制作出相对应的解锁视频时&#xff0c;从撰写解锁词&#xff0c;录制音频到剪辑视频&#xff0c;每一个环节都需要投入大量的时间和精力&#xff0c;本方案将依托于阿里云函数计算 FC 和百炼模型服务&#xff0c;实现从 PPT 到视频的全自动转换&#xff0…...

FFmpeg + OpenGL ES 美颜相机教程大纲

做OpenGL和FFmpeg也有很长一段时间了&#xff0c;最近打算结合FFmpegOpenGL ES做一期视频教程&#xff0c;下面是完整视频教程大纲。最终的项目实战效果是实现一款美颜相机。教程分为理论讲解和实战开发两部分&#xff0c;适合有一定编程基础的开发者。课程计划是免费发布在B站…...

IEC61850标准下的数据和数据模型服务的详细介绍

目录 一、摘要 二、概述 三、详细介绍 1、读服务器目录(GetServerDirectory) 2、读逻辑设备目录(GetLogicalDeviceDirectory) 3、读逻辑节点目录(GetLogicalNodeDirectory) 4、读全部数据值(GetAllDataValues) 5、读数据值(GetDataValues) 6、设置数据值(SetDataValues…...

【3.Git与Github的历史和区别】

目录 Git的历史和Github的区别本质和功能 Git的历史和Github的区别 Git是由Linux内核的创造者Linus Torvalds于2005年创建的。当时&#xff0c;Linux内核开源项目使用BitKeeper作为版本控制系统&#xff0c;但2005年BitKeeper的商业公司终止了与Linux社区的合作&#xff0c;收…...

前端页面添加水印

前端页面添加水印 主要功能说明&#xff1a; 这是一个用于添加页面水印的工具函数水印会以半透明的形式显示在页面上&#xff0c;并且会重复平铺水印文字会有-15度的倾斜角度水印会覆盖整个页面&#xff0c;但不会影响页面的正常交互每次调用函数时会先删除已存在的水印&…...

Left side cannot be assigned to

Delphi XE E2064 Left side cannot be assigned to 错误解决方法-CSDN博客 Delphi XE E2064 Left side cannot be assigned to 错误解决方法 1. 起源 此问题源于[秋风人事档案管理系统]用Delphi XE重编译中所发现。 快十年了&#xff0c;当初Delphi 7所编写项目&#xff0c…...

R语言LCMM多维度潜在类别模型流行病学研究:LCA、MM方法分析纵向数据

全文代码数据&#xff1a;https://tecdat.cn/?p39710 在数据分析领域&#xff0c;当我们面对一组数据时&#xff0c;通常会有已知的分组情况&#xff0c;比如不同的治疗组、性别组或种族组等&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 然而&#xff0c;…...

伺服使能的含义解析

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#的运动控制程序的时候&#xff0c;一个必要的步骤就是对伺服上使能&#…...

ModuleJS 与 CommonJS 混用的两种解决方案

目录 方案一 方案二 统一使用 ModuleJS 统一使用CommonJS 方案一 使用构建工具&#xff0c;webpack、vite等系列构建工具。这些构建工具底层则会将两种不同的系统模块语言转为同一种语言&#xff0c;然后代码也能正常执行。 方案二 如果你可以修改文件的文件后缀&#xf…...

5. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Nacos

一、什么是Nacos Nacos 是阿里巴巴开源的一款云原生应用基础设施&#xff0c;它旨在简化微服务架构中服务治理和配置管理的复杂性。通过 Nacos&#xff0c;服务在启动时可以自动注册&#xff0c;而其他服务则可以通过名称来查找并访问这些注册好的实例。同时&#xff0c;Nacos…...

VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

VUE项目中实现权限控制&#xff0c;菜单权限&#xff0c;按钮权限&#xff0c;接口权限&#xff0c;路由权限&#xff0c;操作权限&#xff0c;数据权限实现 权限系统分类&#xff08;RBAC&#xff09;引言菜单权限按钮权限接口权限路由权限 菜单权限方案方案一&#xff1a;菜单…...

网站的记住我功能与用户登录持久化

1.先决条件&#xff1a;拿到了后端发的凭证并做了持久化储存 2.在1的基础上&#xff0c;加上一个记住我功能&#xff0c;记住我的体现暂时定为&#xff1a; a.不勾选记住我&#xff1a;在浏览器的对话窗不关闭的情况下&#xff0c;凭证是存在有效的&#xff0c;但关闭了对话框…...