定時器的設計

定時器

  • 定時器原理
    • 如何理解定時器
    • 定時器數據結構選取
    • 定時器觸發方式
  • 定時器的實現

定時器原理

如何理解定時器

定時器在日常通常被描述為組織大量延時任務的模塊,其實從字面意思去理解的話,他就是去處理延時任務的,那么什么是延時任務呢?我們來看一張圖:
在這里插入圖片描述
在現代開發當中,同時是要處理多個任務的,我們將任務添加進某個數據結構到任務觸發之間是存在一段時間間隔的,那么就會引發一個問題,這段時間當前 CPU 核心是在去等么?

這兒肯定是不會讓 CPU 核心去等的,因為我們要保證一個原則,不過去占用一個線程,高效的處理定時任務,這也意味著再添加任務到觸發任務之間的這一段時刻,我們不可能讓線程在這兒進行等待,這樣就違背了我們高效的原則了。

所以也就有了定時器的出現,在添加任務到觸發任務之間肯定是會存在大量任務的,我們就需要將當前的大量任務組織起來,然后觸發最近將要超時的任務,這樣,就保證了當前線程并不是一味地在這兒阻塞進行等待,對于將要超時的任務他就會去進行處理。

那么定時器就可以被描述為組織大量延時任務的數據結構和觸發最近將要超時任務的機制。

定時器數據結構選取

既然定時器需要組織大量的延時任務,就需要選取一種數據結構,最為合適的幾種數據結構分別為:紅黑樹、最小堆和時間輪。

紅黑樹

根據上面的描述我們就可以這么去理解定時器,“時間”“任務”,兩者之間就成了對應的關系,某個時間點就對應了相應的任務,其實我們就可以看出來這天然的就對應這一種 [key,value] 的關系了,這種關系很容易就讓人想到了紅黑樹這中數據結構。

同時,對于添加的任務來說,肯定是存在時間順序的,因為定時器總是優先去處理最近將要么超時的任務:
在這里插入圖片描述
紅黑樹是一種自平衡的二叉查找樹,同時,他也是一種有序的數據結構,對于插入以及刪除的時間復雜度都是O(LogN)級別的,如果要實現一個定時,紅黑樹肯定是一個可以選擇的數據結構,在 C++ 的 STL 庫中存在四種紅黑樹的數據結構:map/set/multimap/multiset,我們后續定時器的實現選取的就是multimap這個結構。

選取multimap這個結構主要是基于兩點:

  • 肯定會存在同一時間觸發多個任務的場景存在,map的實現是基于一對一的關系的,而multimap是基于一對多的關系的,所以肯定是選multimap的;
  • 我們通常是獲取最近將要超時的任務,添加也是添加基于順序進行添加的,那么基于紅黑樹這種數據結構,我們會發現第一種操作的的時間復雜度是O(1)。
    在這里插入圖片描述
    Ngnix,workflow里面的定時器都是基于紅黑樹進行實現的。

最小堆

最小堆是一種特殊的完全二叉樹數據結構,其中每個父節點的值都小于或等于其子節點的值。這個性質使得最小堆的根節點始終是整個堆中的最小元素。

他的特點就在于:最小堆是一棵完全二叉樹,意味著除了最后一層外,其他層都是完全填充的,且最后一層的節點盡可能向左靠攏。而且任意一個子節點頁滿足最小堆的要求。
在這里插入圖片描述
對于最小堆來說他的查找,插入和刪除的效率也是很高的,我們如果需要插入一個元素,基于最小堆的性質,肯定是往二叉樹最高層沿著最左側添加一個節點,然后考慮是否需要上升進行調整;刪除一個元素也是,先找到這個元素,然后將其與最后一個節點進行交換,然后調整堆結構,這個可以參考之前的一篇文章:堆數據結構詳解。

堆的這種理念其實跟定時器也是比較契合的,最小堆堆頂總是最小的那個數據,也就是我們最近需要處理的任務,同時,他的插入和刪除的也是比較高的。libev,libevent 里面的定時器是基于最小堆實現的。

時間輪

紅黑樹,最小堆是按觸發時間進行順序組織,而接下來需要介紹的時間輪是按執行順序進行組織的。

對于時間輪來說,它是基于鐘表的原理來進行實現的:
在這里插入圖片描述
在這里插入圖片描述
對于Linux內核,Kafka 都是基于時間輪進行實現的。

定時器觸發方式

對于服務端來說,驅動服務端業務邏輯的事件包括網絡事件、定時事件、以及信號事件;通常網絡事件和定時事件會進行協同處理;定時器觸發形式通常有兩種:

利用 IO 多路復用系統調用最后一個參數(超時),來觸發檢測定時器

理解定時器以后我們就很容易想到 IO 多路復用的最后一個參數timeout,他的取值會存在以下三種情況:

  • NULL/nullptr:select調用后進行阻塞等待,直到被監視的某個文件描述符上的某個事件就緒。
  • 0:selec調用后t進行非阻塞等待,無論被監視的文件描述符上的事件是否就緒,select檢測后都會立即返回。
  • 特定的時間值:select調用后在指定的時間內進行阻塞等待,如果被監視的文件描述符上一直沒有事件就緒,則在該時間后select進行超時返回。

我們首先的理解 recator 的本質,他是將 IO 轉化為對事件的管理,因為我們對于用戶端來說,并不知道 IO 什么時候就緒,客戶端何時發送數據,什么時候建立連接…,存在很多個不知道的情況,所以就會導致我們不知道什么時候去調用 accept/read/write,而 IO 多路復用解決的就是這個問題,當監聽事件就緒就調用 accept 建立連接通路,真正的事件就緒就調用read/write對數據進行讀寫,他本質上解決的就是一個調用時機的問題。

對于我們的定時器來說,是處理定時任務的,他其實本質上也是一個事件,如果時間到了,就去執行,其實跟 IO 多路復用的這種思想是很相似的,我們都是需要去異步處理它,所以在這兒我們會使用 IO 多路復用的最后一個參數。

利用 timerfd,將定時檢測作為 IO 多路復用當中的事件進行處理

timerfd 其實也是基于網絡 IO 的思想進行實現的,他主要是依靠以下兩個接口來進行實現的:

#include <sys/timerfd.h>int timerfd_create(int clockid, int flags);

timerfd_create 用于創建一個定時器文件描述符(timer file descriptor),它允許應用程序通過文件描述符來監控定時器事件,解析如下:

  • clockid:指定定時器使用的時鐘源,常見選項包括:
    CLOCK_REALTIME:系統實時時間(可受系統時間調整影響);
    CLOCK_MONOTONIC:單調時鐘(不受系統時間調整影響,適合測量時間間隔);
    CLOCK_BOOTTIME(Linux 2.6.39 后支持):類似 CLOCK_MONOTONIC,但包含系統休眠時間。
  • flags:控制文件描述符的行為,可選值:
    TFD_NONBLOCK:設置文件描述符為非阻塞模式。
    TFD_CLOEXEC:設置文件描述符為 close-on-exec(執行 exec 時自動關閉)。

返回值:

  • 成功時返回一個文件描述符(fd),可用于后續的 timerfd_settime 和 read 操作。
  • 失敗時返回 -1,并設置 errno(如 EINVAL 表示無效參數)。
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

timerfd_settime 用于設置或修改 timerfd 計時器的到期時間和間隔周期。它是 timerfd 系列系統調用的一部分,需要配合 timerfd_create 創建的定時器文件描述符使用,參數解析:

  • fd:由 timerfd_create 創建的定時器文件描述符;
  • flags:控制標志位,可以是以下值之一:
    0:使用相對時間(以當前時間為基準)
    TFD_TIMER_ABSTIME:使用絕對時間(以 Epoch 時間為基準)
  • new_value:指向 itimerspec 結構的指針,指定新的定時器設置;
struct itimerspec {struct timespec it_interval;  /* 定時器間隔周期 */struct timespec it_value;     /* 首次到期時間 */
};struct timespec {time_t tv_sec;                /* 秒 */long tv_nsec;                 /* 納秒 */
};
  • old_value:指向 itimerspec 結構的指針,用于存儲之前的定時器設置(可為 NULL)。

返回值:

  • 成功時返回 0
  • 失敗時返回 -1 并設置 errno

定時器的實現

接下來我們通過紅黑樹的方式來實現一個定時器:

#ifndef __TIMER__
#define __TIMER__#include <map>
#include <functional>
#include <chrono>// TimerNode 結點,延時任務
class TimerNode
{
public:friend class Timer;TimerNode(uint64_t timeout, std::function<void()> cb): timeout_(timeout), callback_(std::move(cb)){}private:uint64_t timeout_;std::function<void()> callback_;
};class Timer
{
public:// 添加延時任務TimerNode* AddTimeout();// 刪除延時任務void DelTimeout();// 獲取延時時間int WaitTime();// 處理延時任務void HandleTimeout();private:std::multimap<uint64_t, TimerNode *> timer_map_; // 前面代表超時時間,后面代表超時任務
};#endif

添加延時任務、

static uint64_t GetCurrentTime()
{using namespace std::chrono;return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
}
// 添加延時任務
TimerNode *AddTimeout(uint64_t diff, std::function<void()> cb)
{// 根據 diff 創建延時任務auto node = new TimerNode(GetCurrentTime() + diff, std::move(cb));if (timer_map_.empty() || node->timeout_ < timer_map_.rbegin()->first){auto it = timer_map_.insert(std::make_pair(node->timeout_, std::move(node)));return it->second;}else{auto it = timer_map_.emplace_hint(timer_map_.crbegin().base(), std::make_pair(node->timeout_, std::move(node)));return it->second;}
}
  • 我們的延時任務通過 timer_map_ 進行管理,添加延時任務其實就是將延時任務放進 timer_map_ 中進行管理,我們的每一個延時任務都需要被處理,被添加以后我們如何去進行處理就需要使用到回調函數,將回調函數作為 TimerNode 的第二個結點的原因也在這兒,我們可以更為直接的對延時任務進行處理;
  • 對于延時任務,我們以當前時間+延時時間,表示延時的時間,在這兒進行添加的時候我們就需要注意,我們添加的延時任務不一定是按順序排布的,但是紅黑樹這個結構肯定是按順序進行排布的,所以添加的延時任務時我們就可以進行優化,如果對應的添加的延時任務的延時時間是小于紅黑樹最后一個節點的,我們調用 insert 函數插入,因為要重新進行排序,如果對應的添加的延時任務的延時時間是大于紅黑樹最后一個節點的,我們直接找到尾結點,調用 emplace_hint 函數直接構造一個節點即可,這樣是可以提高效率的。
  • 最終返回值肯定當前的這個延時任務。

刪除延時任務

// 刪除延時任務
void DelTimeout(TimerNode *node)
{auto it = timer_map_.equal_range(node->timeout_);for (auto iter = it.first; iter != it.second; iter++){if (iter->second == node){timer_map_.erase(iter);break;}}
}

刪除延時任務應該注意的地方就在同一時間我們可能會插入多個延時任務,那么再刪除的時候就應該將這個時間點對應的已經處理的延時任務刪除,就使用到了 equal_range 這個接口,他用于在有序范圍內查找等于給定值的所有元素的范圍。它返回一個 std::pair,其中 first 指向第一個不小于給定值的元素,second 指向第一個大于給定值的元素。剛好適用于當前場景。

獲取延時時間

// 獲取延時時間
int WaitTime()
{auto iter = timer_map_.begin();if (iter == timer_map_.end()){return -1;}uint64_t diff = iter->first - GetCurrentTime();return diff > 0 ? diff : 0;
}

獲取延時時間其實就是獲取到當前延時任務的一個時間,這個就不多做解釋了。

處理延時任務

// 處理延時事件
void HandleTimeout()
{auto iter = timer_map_.begin();while (iter != timer_map_.end() && iter->first <= GetCurrentTime()){iter->second->callback_();iter = timer_map_.erase(iter);}
}

處理我們對應的延時任務,就是調用對應的回調函數,處理完成以后將這個延時任務刪除就可以了。

整體代碼如下:

#ifndef __TIMER__
#define __TIMER__#include <map>
#include <functional>
#include <chrono>// TimerNode 結點,延時任務
class TimerNode
{
public:friend class Timer;TimerNode(uint64_t timeout, std::function<void()> cb): timeout_(timeout), callback_(std::move(cb)){}private:// int id_;uint64_t timeout_;               // 什么時候觸發std::function<void()> callback_; // 異步任務,回調函數
};class Timer
{
public:static uint64_t GetCurrentTime(){using namespace std::chrono;return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();}// 添加延時任務TimerNode *AddTimeout(uint64_t diff, std::function<void()> cb){// 根據 diff 創建延時任務auto node = new TimerNode(GetCurrentTime() + diff, std::move(cb));if (timer_map_.empty() || node->timeout_ < timer_map_.rbegin()->first){auto it = timer_map_.insert(std::make_pair(node->timeout_, std::move(node)));return it->second;}else{auto it = timer_map_.emplace_hint(timer_map_.crbegin().base(), std::make_pair(node->timeout_, std::move(node)));return it->second;}}// 刪除延時任務void DelTimeout(TimerNode *node){auto it = timer_map_.equal_range(node->timeout_);for (auto iter = it.first; iter != it.second; iter++){if (iter->second == node){timer_map_.erase(iter);break;}}}// 獲取延時時間int WaitTime(){auto iter = timer_map_.begin();if (iter == timer_map_.end()){return -1;}uint64_t diff = iter->first - GetCurrentTime();return diff > 0 ? diff : 0;}// 處理延時事件void HandleTimeout(){auto iter = timer_map_.begin();while (iter != timer_map_.end() && iter->first <= GetCurrentTime()){iter->second->callback_();iter = timer_map_.erase(iter);}}private:std::multimap<uint64_t, TimerNode *> timer_map_; // 前面代表超時時間,后面代表超時任務// std::unordered_map<int, TimerNode *> map_; // id與延時任務對應的關系Timer() = default;Timer(const Timer &) = delete;Timer &operator=(const Timer &) = delete;Timer(Timer &&) = delete;Timer &operator=(Timer &&) = delete;~Timer(){for (auto &pair : timer_map_){delete pair.second;}}
};#endif

當前定時器存在一個可以優化的點,就在于我們需要讓他能夠全局被進行使用,不希望每次不同對象去使用都去進行創建,在這里可以將他設置成為單例的,提供給全局進行使用,優化后代碼如下:

#ifndef __TIMER__
#define __TIMER__#include <map>
#include <functional>
#include <chrono>// TimerNode 結點,延時任務
class TimerNode
{
public:friend class Timer;TimerNode(uint64_t timeout, std::function<void()> cb): timeout_(timeout), callback_(std::move(cb)){}private:// int id_;uint64_t timeout_;               // 什么時候觸發std::function<void()> callback_; // 異步任務,回調函數
};class Timer
{
public:// 單例模式static Timer* GetInstance(){static Timer instance;return &instance;}static uint64_t GetCurrentTime(){using namespace std::chrono;return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();}// 添加延時任務TimerNode *AddTimeout(uint64_t diff, std::function<void()> cb){// 根據 diff 創建延時任務auto node = new TimerNode(GetCurrentTime() + diff, std::move(cb));if (timer_map_.empty() || node->timeout_ < timer_map_.rbegin()->first){auto it = timer_map_.insert(std::make_pair(node->timeout_, std::move(node)));return it->second;}else{auto it = timer_map_.emplace_hint(timer_map_.crbegin().base(), std::make_pair(node->timeout_, std::move(node)));return it->second;}}// 刪除延時任務void DelTimeout(TimerNode *node){auto it = timer_map_.equal_range(node->timeout_);for (auto iter = it.first; iter != it.second; iter++){if (iter->second == node){timer_map_.erase(iter);break;}}}// 獲取延時時間int WaitTime(){auto iter = timer_map_.begin();if (iter == timer_map_.end()){return -1;}uint64_t diff = iter->first - GetCurrentTime();return diff > 0 ? diff : 0;}// 處理延時事件void HandleTimeout(){auto iter = timer_map_.begin();while (iter != timer_map_.end() && iter->first <= GetCurrentTime()){iter->second->callback_();iter = timer_map_.erase(iter);}}private:std::multimap<uint64_t, TimerNode *> timer_map_; // 前面代表超時時間,后面代表超時任務// std::unordered_map<int, TimerNode *> map_; // id與延時任務對應的關系Timer() = default;Timer(const Timer &) = delete;Timer &operator=(const Timer &) = delete;Timer(Timer &&) = delete;Timer &operator=(Timer &&) = delete;~Timer(){for (auto &pair : timer_map_){delete pair.second;}}
};#endif

這就是當前整個定時器的設計,他主要就是用來去處理延時任務的,當我們需要處理大量延時任務的時候,使用定時器就會比較合適。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87153.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87153.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87153.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大模型-分布式論文一瞥

1分離式架構 1.1 DistServe DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving 講的是一個將prefill和decoding分…

02.SpringBoot常用Utils工具類詳解

文章目錄 1. BeanUtils詳解1.1 什么是BeanUtils&#xff1f;1.2 主要的BeanUtils實現1.2.1 Spring BeanUtils1.2.2 Apache Commons BeanUtils1.2.3 其他實現 1.3 Spring BeanUtils詳細使用1.3.1 基本用法1.3.2 指定忽略屬性1.3.3 批量拷貝&#xff08;列表轉換&#xff09; 1.4…

Golang快速開發框架——項目立項與系統配置讀取組件viper(一)

Golang快速開發框架——項目立項與系統配置讀取組件viper&#xff08;一&#xff09; 背景 知識分享之Golang篇是我在日常使用Golang時學習到的各種各樣的知識的記錄&#xff0c;將其整理出來以文章的形式分享給大家&#xff0c;來進行共同學習。歡迎大家進行持續關注。 知識分…

打造可觀測的 iOS CICD 流程:調試、追蹤與質量保障全記錄

隨著iOS項目復雜度增加&#xff0c;團隊越來越依賴自動化構建、自動化測試等CI/CD流程來保證產品質量。但CI/CD環境下&#xff0c;很多線下調試手段無法直接使用&#xff0c;比如&#xff1a; 無法手動連真機跑Instruments測試包只在分發后才能拿到崩潰模擬器上表現和真機不一…

C++11中 <cinttypes>的入門與精通

文章目錄 一、<cinttypes> 是什么1. 固定寬度的整數類型2. 整數操作函數3. 格式化輸入輸出宏 二、深入理解 <cinttypes>1. 固定寬度整數類型的使用2. 整數操作函數的使用3. 格式化輸入輸出宏的使用 三、實踐和技巧1. 使用固定寬度整數類型的最佳實踐2. 使用整數操作…

Pytorhc Lightning進階:一篇實例玩轉Pytorhc Lightning 讓訓練更高效

Pytorhc Lightning進階&#xff1a;一篇實例玩轉Pytorhc Lightning 讓訓練更高效 Pytorhc Lightning 主要包含以下幾大類&#xff0c;主要圍繞以下講解&#xff1a; 模型&#xff0c;PyTorch Lightning 的核心是繼承 pl.LightningModule數據&#xff0c;數據模塊繼承pl.Light…

大模型算法面試筆記——注意力Transformer流程/面試題篇

學習資料來源于字母站大學 1 Transformer架構 基于編碼器-解碼器的架構來處理序列對。跟使用注意力的seq2seq不同&#xff0c;Transformer是基于純注意力。 2 注意力 2.1 自注意力機制 使用注意力&#xff1a;需要根據整個序列進行預測&#xff0c;對于同一input&#xf…

Rust 定義與實例化結構體

文章目錄 Rust 定義與實例化結構體5.1 結構體的定義與意義5.2 結構體實例化5.2.1 基本實例化5.2.2 可變性規則5.2.3 字段初始化簡寫5.2.4 結構體更新語法 5.3 特殊結構體類型5.3.1 元組結構體&#xff08;Tuple Struct&#xff09;5.3.2 類單元結構體&#xff08;Unit-Like Str…

ELK日志分析系統(filebeat+logstash+elasticsearch+kibana)

一、ELK 平臺介紹 1、ELK 概述 日志主要包括系統日志、應用程序日志和安全日志。系統運維和開發人員可以通過日志了解服務器軟硬件信息、檢查配置過程中的錯誤及錯誤發生的原因。經常分析日志可以了解服務器的負荷&#xff0c;性能安全性&#xff0c;從而及時采取措施糾正錯誤。…

JS基礎4—jQuery

jQuery常用內容 jQuery 介紹jQuery 獲取方式基本選擇器 (最常用)層級選擇器 (基于元素間關系)過濾選擇器 (基于特定條件) jQuery事件綁定jQuery 方法調用jQuery遍歷jQuery 獲取與設置jQuery 添加與刪除jQuery CSS 類jQuery - AJAX 總結 jQuery 介紹 jQuery 是一個輕量級、快速…

時鐘周期是什么?

時鐘周期&#xff08;Clock Cycle&#xff09;是什么&#xff1f; 時鐘周期&#xff08;Clock Cycle&#xff09;是計算機系統中一個最基礎的時間單位&#xff0c;也稱為時鐘節拍或時鐘周期時間&#xff08;Clock Period&#xff09;。它由系統時鐘發生器產生的一個周期性脈沖…

如何用SEO優化長尾關鍵詞?

內容概要 在SEO優化領域&#xff0c;長尾關鍵詞扮演著至關重要的角色&#xff0c;它們能有效提升網站在搜索引擎中的可見度和流量轉化率。本文將全面解析如何通過系統方法優化長尾關鍵詞&#xff0c;涵蓋從基礎理論到實戰應用的完整流程。核心內容包括利用專業工具進行關鍵詞挖…

電子面單系統開發全解析

一、如果要做電子面單系統&#xff0c;怎么做&#xff1f; 開發電子面單系統是一項復雜且涉及多方面考量的工程&#xff0c;涵蓋需求分析、系統架構設計、技術選型、接口對接、安全性保障、第三方服務選擇以及部署與維護等關鍵環節。 電子面單系統開發步驟 需求分析&#xf…

UE5 - 制作《塞爾達傳說》中林克的技能 - 18 - 磁力抓取器

讓我們繼續《塞爾達傳說》中林克技能的制作!!! UE版本:5.6.0 VS版本:2022 本章節的核心目標:磁力抓取器 先讓我們看一下完成后的效果: 18_磁力抓取器 大綱如下: 引言功能架構與核心邏輯物理材質與場景配置代碼實現:從識別到操控操作說明1.引言 在《塞爾達傳說》中,林…

基于ApachePOI實現百度POI分類快速導入PostgreSQL數據庫實戰

目錄 前言 一、百度POI分類簡介 1、數據表格 2、分類結構 二、從Excel導入到PG數據庫 1、Excel解析流程 2、數據入庫 3、入庫成果及檢索 三、總結 前言 在上一篇博文中&#xff0c;我們對高德POI分類進行了深入剖析 并對Excel 中 POI 分類數據的存儲結構特點進行了詳細介…

學習經驗分享【41】YOLOv13:基于超圖增強自適應視覺感知的實時目標檢測

YOLO算法更新速度很快&#xff0c;已經出到V13版本&#xff0c;后續大家有想發論文或者搞項目可更新自己的baseline了。 摘要&#xff1a;YOLO 系列模型憑借其卓越的精度和計算效率&#xff0c;在實時目標檢測領域占據主導地位。然而&#xff0c;YOLOv11 及早期版本的卷積架構&…

Handling outliers in non-blind image deconvolution論文閱讀

Handling outliers in non-blind image deconvolution 1. 研究目標與實際意義2. 創新方法:基于EM的異常值建模2.1 新模糊模型2.1.1 目標函數2.2 EM框架:迭代優化二元掩碼2.2.1 E步:計算后驗權重 E [ m x ] E[m_x] E[mx?]2.2.2 M步:加權正則化反卷積2.3 優化加速技術2.3.1…

Redis 功能擴展:Lua 腳本對 Redis 的擴展

Redis 是一個高性能的內存數據庫&#xff0c;支持多種數據結構&#xff0c;如字符串、哈希、列表、集合和有序集合。為了增強其功能&#xff0c;Redis 引入了 Lua 腳本支持&#xff0c;使開發者可以編寫自定義的腳本&#xff0c;確保操作的原子性并提高復雜操作的性能。本文將詳…

七天學完十大機器學習經典算法-06.支持向量機(SVM):分類邊界的藝術——深入淺出指南

接上一篇《七天學完十大機器學習經典算法-05.從投票到分類&#xff1a;K近鄰(KNN)算法完全指南》 想象你要在操場上為兩個班級劃活動區域&#xff0c;如何畫出一條最公平的分界線&#xff1f;這條線不僅要分開兩班學生&#xff0c;還要讓兩個班都離分界線盡可能遠——這就是支持…

python如何安裝PyQt6-stubs依賴包

PyQt6-stubs 是為 PyQt6 提供類型提示&#xff08;Type Hints&#xff09;和 IDE 智能補全支持的第三方補丁包&#xff0c;特別適用于 PyCharm、VS Code 等現代 IDE。它對開發者在編碼時幫助極大。 一、安裝方法 需要提前安裝好git&#xff0c;然后克隆PyQt6-stubs源碼&#xf…