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

Linux:线程池和单例模式

一、普通线程池

1.1 线程池概念 

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价(用空间换时间的一种策略)。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 *

线程池的应用场景:

* 1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

* 2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

* 3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误.

* 线程池示例(也是一个生产消费模型):

* 1. 创建固定数量线程池,循环从任务队列中获取任务对象,

* 2. 获取到任务对象后,执行任务对象中的任务接口

1.2 线程池的实现

线程池:

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;
};

       如果我们将线程的方法写在类内,我们的phread方法没法传参数!!因为类成员函数默认会携带this指针!!   所以我们不想要这个this指针,就必须把这个成员函数变成静态的!

        但是还不够!!因为如果我们把他定义成静态成员函数,那么他是无法使用类内的非静态成员函数的!!因此我们如果要解决这个问题,我们就可以将this对象通过参数传递过去,这样的话我们就可以在静态函数内通过这个对象去调用类内的非静态成员方法了!

    既然我们这些函数都写在类内了,那么我们就可以将一些函数封装一下,从而增加代码的可读性! 

主函数:

#include <iostream>
#include <ctime>
#include "ThreadPool.hpp"
#include "Task.hpp"pthread_spinlock_t slock;int main()
{// pthread_spin_init(&slock, 0);// pthread_spin_destroy(&slock);// 如果获取单例对象的时候,也是多线程获取的呢?std::cout << "process runn..." << std::endl;sleep(3);ThreadPool<Task> *tp = new ThreadPool<Task>(5);srand(time(nullptr) ^ getpid());while(true){//1. 构建任务int x = rand() % 10 + 1;usleep(10);int y = rand() % 5;char op = opers[rand()%opers.size()];Task t(x, y, op);tp->Push(t);//2. 交给线程池处理std::cout << "main thread make task: " << t.GetTask() << std::endl;sleep(1);}
}

 任务函数:

#pragma once
#include <iostream>
#include <string>std::string opers="+-*/%";enum{DivZero=1,ModZero,Unknown
};class Task
{
public:Task(){}Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if(data2_ == 0) exitcode_ = DivZero;else result_ = data1_ / data2_;}break;case '%':{if(data2_ == 0) exitcode_ = ModZero;else result_ = data1_ % data2_;}            break;default:exitcode_ = Unknown;break;}}void operator ()(){run();}std::string GetResult(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=";r += std::to_string(result_);r += "[code: ";r += std::to_string(exitcode_);r += "]";return r;}std::string GetTask(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};

二、c++模式封装线程库

 C++其实是提供了线程库的,但是他的底层也是用的原生线程库做封装,所以也必须指定链接。

#pragma once#include <iostream>
#include <string>
#include <ctime>
#include <pthread.h>typedef void (*callback_t)();//函数指针
static int num = 1;class Thread
{
public:static void *Routine(void *args){Thread* thread = static_cast<Thread*>(args);thread->Entery();return nullptr;}
public:Thread(callback_t cb):tid_(0), name_(""), start_timestamp_(0), isrunning_(false),cb_(cb){}void Run(){name_ = "thread-" + std::to_string(num++);start_timestamp_ = time(nullptr);isrunning_ = true;pthread_create(&tid_, nullptr, Routine, this);}void Join(){pthread_join(tid_, nullptr);isrunning_ = false;}std::string Name(){return name_;}uint64_t StartTimestamp(){return start_timestamp_;}bool IsRunning(){return isrunning_;}void Entery(){cb_();}~Thread(){}
private:pthread_t tid_;std::string name_;uint64_t start_timestamp_;//启动的时间戳bool isrunning_;//是否启动了callback_t cb_;//线程方法!
};

问题:如果我们的线程方法想带参数怎么办??

——>改一下构造函数,多增加一个类对象,将参数传递给这个类对象,然后再在方法里面将这个类对象传递给线程函数

主函数:

#include <iostream>
#include <unistd.h>
#include <vector>
#include "Thread.hpp"using namespace std;void Print()
{while(true){printf("haha, 我是一个封装的线程...\n");sleep(1);}
}int main()
{std::vector<Thread> threads;for(int i = 0 ;i < 10; i++){threads.push_back(Thread(Print));}for(auto &t : threads){t.Run();}for(auto &t : threads){t.Join();}// Thread t(Print);// t.Run();// cout << "是否启动成功: " << t.IsRunning() << endl;// cout << "启动成功时间戳: " << t.StartTimestamp() << endl;// cout << "线程的名字: " << t.Name() << endl;// t.Join();return 0;
}

三、线程安全的单例模式

3.1 单例模式和设计模式 

 单例模式是一种 "经典的, 常用的, 常考的" 设计模式.

        IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是设计模式

单例模式的特点 :

某些类, 只应该具有一个对象(实例), 就称之为单例.

例如一个男人只能有一个媳妇.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

3.2 饿汉模式和懒汉模式 

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度 

例子1:我们申请内存的时候,首先是在地址空间上申请,而当我们真正要使用的时候,才会发生缺页中断从而建立虚拟地址和物理地址的映射关系!! 

例子2:我们打开文件的时候,文件的属性必然会先被加载起来,但是文件的内容则是我们需要使用的时候才会加载进来!! 

饿汉方式实现单例模式 

template <typename T> 
class Singleton { static T data; 
public: static T* GetInstance() { return &data; } 
}; 

 只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.

懒汉方式实现单例模式 

template <typename T> 
class Singleton { static T* inst; 
public: static T* GetInstance() { if (inst == NULL) { inst = new T(); } return inst; } 
};

存在一个严重的问题, 线程不安全.

第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.

——>所以我们必须加锁!!  可是我们我们会发现其实也就第一次调用的时候可能会出现这种情况,但是后续再次调用, 基本上都是直接返回,所以加锁就没有什么意义了,还大大降低效率!!——>解决方法,外面再加一层判断!!

// 懒汉模式, 线程安全 
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; } 
}; 

注意事项:

1. 加锁解锁的位置

2. 双重 if 判定, 避免不必要的锁竞争

3. volatile关键字防止过度优化

3.3 懒汉模式的线程池修改 

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

四、STL、智能指针和线程安全

STL中的容器是否是线程安全的?

——> 不是.

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.

而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).

因此 STL默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.

对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

五、其他各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行 锁等),当其他线程想要访问数据时,被阻塞挂起。(我们使用的一般都是这个)

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁,公平锁,非公平锁?

自旋锁的介绍:

       讲个故事,张三发现明天要考试了,非常慌,于是打电话找到了李四,让李四帮他复习一下,李四说我目前还在看书,还得等我一个小时,于是这个时候张三就先去学校旁边的网吧打了一个小时的游戏,等打完回来正好李四下来了 于是一起去复习了 最后考了60分。

     第二次又考试了,这次张三还是一样打电话给李四,这个时候李四说他上个厕所就下来了,这个时候你想的是他一会就下来了!那我还是在这等等吧,就不去网吧了。

 所以这个时候张三决策的依据就是李四究竟要让他等多久

——> 当前其他申请不到锁的进程是应该阻塞还是应该重复申请,取决于其执行临界区代码的时长

 所以我们以前学的锁叫做挂起等待锁,而需要不断申请直到获得的锁叫做自旋锁!

实现方式:trylock加锁就是如果当前没有加锁成功,就直接返回! 所以我们只要在外围封装一个while循环,那么该线程就会一直申请锁直到申请成功!!

 但其实pthread库给我们实现了一个自旋锁!!

第一个就是相当于帮我们封装了这个while循环,他会一直申请直到申请到锁。

第二个就跟前面学的差不多,只要申请失败了就会返回!! 

六、读者写者问题、

6.1 引入

    读者写者问题本身也是生产消费者模型 遵循321原则,但是其中最大的一个差别就是  读和读是共享关系!!因为读的过程并不会影响到数据!!(比如我们学校的黑板报或者博客)

 线程库为我们提供了读写锁

      一般来说都是读的多写的少,所以读的竞争能力比写的竞争能力大很多,所以可能会导致写较大概率的饥饿问题!!(中性现象) 

有两种策略:读者优先和写者优先            优先就是当两者一块来的时候,让其中一方先进去

线程库的读写锁默认是读者优先!! 

6.2 读写锁接口

设置读写优先

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

/* pref 共有 3 种选择

PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况

PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁 */

 初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);  

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  

 6.3 样例代码

#include <vector> 
#include <sstream> 
#include <cstdio> 
#include <cstdlib> 
#include <cstring> 
#include <unistd.h> 
#include <pthread.h> volatile int ticket = 1000; 
pthread_rwlock_t rwlock; void * reader(void * arg) 
{ char *id = (char *)arg; while (1) { pthread_rwlock_rdlock(&rwlock); if (ticket <= 0) { pthread_rwlock_unlock(&rwlock); break; } printf("%s: %d\n", id, ticket); pthread_rwlock_unlock(&rwlock); usleep(1); } return nullptr; 
} void * writer(void * arg) 
{ char *id = (char *)arg; while (1) { pthread_rwlock_wrlock(&rwlock); if (ticket <= 0) { pthread_rwlock_unlock(&rwlock); break; } printf("%s: %d\n", id, --ticket); pthread_rwlock_unlock(&rwlock); usleep(1); } return nullptr; 
} struct ThreadAttr 
{ pthread_t tid; std::string id; 
}; std::string create_reader_id(std::size_t i) 
{ // 利用 ostringstream 进行 string 拼接 std::ostringstream oss("thread reader ", std::ios_base::ate); oss << i; return oss.str(); 
} std::string create_writer_id(std::size_t i) 
{ // 利用 ostringstream 进行 string 拼接 std::ostringstream oss("thread writer ", std::ios_base::ate); oss << i; return oss.str(); 
} void init_readers(std::vector<ThreadAttr>& vec) 
{ for (std::size_t i = 0; i < vec.size(); ++i) { vec[i].id = create_reader_id(i); pthread_create(&vec[i].tid, nullptr, reader, (void *)vec[i].id.c_str()); } 
} void init_writers(std::vector<ThreadAttr>& vec) 
{ for (std::size_t i = 0; i < vec.size(); ++i) { vec[i].id = create_writer_id(i); pthread_create(&vec[i].tid, nullptr, writer, (void *)vec[i].id.c_str()); } 
} void join_threads(std::vector<ThreadAttr> const& vec) 
{ // 我们按创建的 逆序 来进行线程的回收 for (std::vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it != 
vec.rend(); ++it) { pthread_t const& tid = it->tid; pthread_join(tid, nullptr); } 
} void init_rwlock() 
{ 
#if 0 // 写优先 pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); pthread_rwlock_init(&rwlock, &attr); pthread_rwlockattr_destroy(&attr); 
#else // 读优先,会造成写饥饿 pthread_rwlock_init(&rwlock, nullptr); 
#endif 
} int main() 
{ // 测试效果不明显的情况下,可以加大 reader_nr // 但也不能太大,超过一定阈值后系统就调度不了主线程了 const std::size_t reader_nr = 1000; const std::size_t writer_nr = 2; std::vector<ThreadAttr> readers(reader_nr); std::vector<ThreadAttr> writers(writer_nr); init_rwlock(); init_readers(readers); init_writers(writers); join_threads(writers); join_threads(readers); pthread_rwlock_destroy(&rwlock); 
} 

 makefile

main: main.cpp g++ -std=c++11 -Wall -Werror $^ -o $@ -lpthread

相关文章:

Linux:线程池和单例模式

一、普通线程池 1.1 线程池概念 线程池&#xff1a;一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价&…...

使用iis服务器模拟本地资源服务器unityaddressables热更新出错记录

editor中设置了using exculexing 模拟远程加载addressable可以实现资源热更新&#xff0c;build后的软件却没有成功。 iis服务器中mime中需要设置bundle的文件扩展名&#xff0c;时editor成功&#xff0c;build后失败 原因没有设置hash的扩展名&#xff0c;设置后editor和buil…...

TikTok广告投放优化策略:提升ROI的核心技巧

在短许多品牌和商家纷纷投入广告营销&#xff0c;争夺这片潜力巨大的市场。然而&#xff0c;在激烈的竞争环境中&#xff0c;如何精准有效地投放广告&#xff0c;优化广告效果&#xff0c;实现更高的投资回报率&#xff08;ROI&#xff09;成为了广告主关注的核心。 一. 精准受…...

Hash表

哈希表存储结构&#xff08;开放寻址法&#xff0c;拉链法&#xff09;字符串哈希方式&#xff08;添加、查找h(x)&#xff09; 常见从0~10^9映射到0~10^5就要对10^5取mod&#xff08;取模一般要质数最好&#xff09;但是可能会有冲突 1.拉链法&#xff1a;O(1)&#xff0c;每…...

题解:P10972 I-Country

题目传送门 思路 因为占据的连通块的左端点先递减、后递增&#xff0c;右端点先递增、后递减&#xff0c;所以设 f i , j , l , r , x ( 0 / 1 ) , y ( 0 / 1 ) f_{i,j,l,r,x(0/1),y(0/1)} fi,j,l,r,x(0/1),y(0/1)​ 为前 i i i 行中&#xff0c;选择 j j j 个方格&#x…...

linux常用加固方式

目录 一.系统加固 二.ssh加固 三.换个隐蔽的端口 四.防火墙配置 五.用户权限管理 六.暴力破解防护 七.病毒防护 八.磁盘加密 九.双因素认证2FA 十.日志监控 十一.精简服务 一.系统加固 第一步&#xff1a;打好系统补丁 sudo apt update && sudo apt upgra…...

笔灵ai写作技术浅析(二):自然语言处理

一、词法分析(Lexical Analysis) 1.1 概述 词法分析是NLP的第一步,主要任务是将连续的文本分割成有意义的单元(词或词组),并对这些单元进行标注,如词性标注(POS tagging)。词法分析的质量直接影响后续的句法分析和语义理解。 1.2 技术细节 1.分词(Tokenization)…...

PyCharm介绍

PyCharm的官网是https://www.jetbrains.com/pycharm/。 以下是在PyCharm官网下载和安装软件的步骤&#xff1a; 下载步骤 打开浏览器&#xff0c;访问PyCharm的官网https://www.jetbrains.com/pycharm/。在官网首页&#xff0c;点击“Download”按钮进入下载页面。选择适合自…...

深度解析:基于Vue 3与Element Plus的学校管理系统技术实现

一、项目架构分析 1.1 技术栈全景 核心框架&#xff1a;Vue 3 TypeScript UI组件库&#xff1a;Element Plus&#xff08;含图标动态注册&#xff09; 状态管理&#xff1a;Pinia&#xff08;用户状态持久化&#xff09; 路由方案&#xff1a;Vue Router&#xff08;动态路…...

Python从0到100(八十五):神经网络-使用迁移学习完成猫狗分类

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能…...

苍穹外卖 项目记录 day09 历史订单

文章目录 查询历史订单查询订单详情取消订单再来一单 查询历史订单 分页查询历史订单可以根据订单状态查询展示订单数据时&#xff0c;需要展示的数据包括&#xff1a;下单时间、订单状态、订单金额、订单明细&#xff08;商品名称、图片&#xff09; #OrderController/*** 历…...

记录 | 基于Docker Desktop的MaxKB安装

目录 前言一、MaxKBStep 1Step2 二、运行MaxKB更新时间 前言 参考文章&#xff1a;如何利用智谱全模态免费模型&#xff0c;生成大家都喜欢的图、文、视并茂的文章&#xff01; MaxKB的Github下载地址 参考视频&#xff1a;【2025最新MaxKB教程】10分钟学会一键部署本地私人专属…...

WordPress web-directory-free插件存在本地文件包含导致任意文件读取漏洞(CVE-2024-3673)

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

LLM:BERT or BART 之BERT

文章目录 前言一、BERT1. Decoder-only2. Encoder-only3. Use of Bidirectional Context4. Masked Language Model (MLM)5. Next Sentence Prediction (NSP)6. Fine-tune1、情感分析2、句对分析3、命名实体识别&#xff08;NER&#xff09; 7. BERT总结 总结 前言 NLP选手对这…...

EtherCAT主站IGH-- 18 -- IGH之fsm_mbox_gateway.h/c文件解析

EtherCAT主站IGH-- 18 -- IGH之fsm_mbox_gateway.h/c文件解析 0 预览一 该文件功能`fsm_mbox_gateway.c` 文件功能函数预览二 函数功能介绍`fsm_mbox_gateway.c` 中主要函数的作用1. `ec_fsm_mbg_init`2. `ec_fsm_mbg_clear`3. `ec_fsm_mbg_transfer`4. `ec_fsm_mbg_exec`5. `e…...

深入探讨防抖函数中的 this 上下文

深入剖析防抖函数中的 this 上下文 最近我在研究防抖函数实现的时候&#xff0c;发现一个耗费脑子的问题&#xff0c;出现了令我困惑的问题。接下来&#xff0c;我将通过代码示例&#xff0c;深入探究这些现象背后的原理。 示例代码 function debounce(fn, delay) {let time…...

【AI论文】魔鬼在细节:关于在训练专用混合专家模型时实现负载均衡损失

摘要&#xff1a;本文重新审视了在训练混合专家&#xff08;Mixture-of-Experts, MoEs&#xff09;模型时负载均衡损失&#xff08;Load-Balancing Loss, LBL&#xff09;的实现。具体来说&#xff0c;MoEs的LBL定义为N_E乘以从1到N_E的所有专家i的频率f_i与门控得分平均值p_i的…...

Gurobi基础语法之addVar 和 addVars

addVar 和 addVars作为 Gurobi模型对象中的方法&#xff0c;常常用来生成变量&#xff0c;本文介绍了Python中的这两个接口的使用 addVar addVar(lb0.0, ubfloat(inf), obj0.0, vtypeGRB.CONTINUOUS, name, columnNone) lb 和 ub让变量在生成的时候就有下界和上届&#xff0c…...

C语言学习阶段性总结(五)---函数

函数构成五要素&#xff1a; 1、返回值类型 2、函数名 3、参数列表&#xff08;输入&#xff09; 4、函数体 &#xff08;算法&#xff09; 5、返回值 &#xff08;输出&#xff09; 返回值类型 函数名 (参数列表) { 函数体&#xff1b; return 返回值&#xff1b; } void 类型…...

K8S 快速实战

K8S 核心架构原理: 我们已经知道了 K8S 的核心功能:自动化运维管理多个容器化程序。那么 K8S 怎么做到的呢?这里,我们从宏观架构上来学习 K8S 的设计思想。首先看下图: K8S 是属于主从设备模型(Master-Slave 架构),即有 Master 节点负责核心的调度、管理和运维,Slave…...

java后端之事务管理

Transactional注解&#xff1a;作用于业务层的方法、类、接口上&#xff0c;将当前方法交给spring进行事务管理&#xff0c;执行前开启事务&#xff0c;成功执行则提交事务&#xff0c;执行异常回滚事务 spring事务管理日志&#xff1a; 默认情况下&#xff0c;只有出现Runti…...

【Redis】缓存+分布式锁

目录 缓存 Redis最主要的使用场景就是作为缓存 缓存的更新策略&#xff1a; 1.定期生成 2.实时生成 面试重点&#xff1a; 缓存预热&#xff08;Cache preheating&#xff09;&#xff1a; 缓存穿透&#xff08;Cache penetration&#xff09; 缓存雪崩 (Cache avalan…...

二分查找题目:寻找两个正序数组的中位数

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;寻找两个正序数组的中位数 出处&#xff1a;4. 寻找两个正序数组的中位数 难度 8 级 题目描述 要求 给定两个大…...

网络安全 | F5-Attack Signatures详解

关注&#xff1a;CodingTechWork 关于攻击签名 攻击签名是用于识别 Web 应用程序及其组件上攻击或攻击类型的规则或模式。安全策略将攻击签名中的模式与请求和响应的内容进行比较&#xff0c;以查找潜在的攻击。有些签名旨在保护特定的操作系统、Web 服务器、数据库、框架或应…...

Redis --- 分布式锁的使用

我们在上篇博客高并发处理 --- 超卖问题一人一单解决方案讲述了两种锁解决业务的使用方法&#xff0c;但是这样不能让锁跨JVM也就是跨进程去使用&#xff0c;只能适用在单体项目中如下图&#xff1a; 为了解决这种场景&#xff0c;我们就需要用一个锁监视器对全部集群进行监视…...

LeetCode100之全排列(46)--Java

1.问题描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案 示例1 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例2 输入&#xff1a;nums [0,1] 输出&#xf…...

goframe 博客分类文章模型文档 主要解决关联

goframe 博客文章模型文档 模型结构 (BlogArticleInfoRes) BlogArticleInfoRes 结构体代表系统中的一篇博客文章&#xff0c;包含完整的元数据和内容管理功能。 type BlogArticleInfoRes struct {Id uint orm:"id,primary" json:"id" …...

【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南

文章目录 &#x1f30d;一. WEB 开发❄️1. 介绍 ❄️2. BS 与 CS 开发介绍 ❄️3. JavaWeb 服务软件 &#x1f30d;二. Tomcat❄️1. Tomcat 下载和安装 ❄️2. Tomcat 启动 ❄️3. Tomcat 启动故障排除 ❄️4. Tomcat 服务中部署 WEB 应用 ❄️5. 浏览器访问 Web 服务过程详…...

安卓日常问题杂谈(一)

背景 关于安卓开发中&#xff0c;有很多奇奇怪怪的问题&#xff0c;有时候这个控件闪一下&#xff0c;有时候这个页面移动一下&#xff0c;这些对于快速开发中&#xff0c;去查询&#xff0c;都是很耗费时间的&#xff0c;因此&#xff0c;本系列文章&#xff0c;旨在记录安卓…...

Kitchen Racks 2

Kitchen Racks 2 吸盘置物架 Kitchen Racks-CSDN博客...