【C++多線程】C++異步線程池提交任務的寫法和解釋

// 提交任務到線程池
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if(stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;
}

基礎知識點包括:右值、右值引用、完美轉發、智能指針、Lambda表達式、可調用對象包裝器function<void()>、綁定器bind、future、packaged_task


問題1:函數名(F&& f, Args&&… args) 這里的形參是啥意思?

ThreadPool::enqueue 這個函數的入參部分:

template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>

F&& f, Args&&... args 采用了一種 完美轉發(Perfect Forwarding) 機制,主要是為了高效地傳遞參數,并保持參數的值類別(左值/右值)屬性。

1. F&& f

  • 這里 F&& 是一個 通用引用(Universal Reference),用于接收任何類型的可調用對象(callable object),包括:
    • 函數指針
    • Lambda 表達式
    • std::function
    • 具有 operator() 的仿函數(functor)
    • 綁定的成員函數 std::bind
  • F&& 作為通用引用,不僅能接受左值(Lvalue),還能接受右值(Rvalue),并且能保留參數的原始類別。

2. Args&&... args

  • Args...可變模板參數(variadic template parameter),表示可以接受多個參數。
  • Args&&... 也是通用引用,它的作用是:
    • 可以接受任意數量的參數
    • 完美轉發(Perfect Forwarding):如果 args 是左值,則保持左值;如果 args 是右值,則保持右值

3. 為什么要這樣寫?

主要是為了 提高泛型代碼的效率,并 避免不必要的拷貝

如果直接使用:

template <class F, class... Args>
auto ThreadPool::enqueue(F f, Args... args) -> std::future<typename std::result_of<F(Args...)>::type>

那么:

  • F f 會導致可調用對象(如 lambda)被拷貝,而不是以原始形式傳遞。
  • Args... args 會導致所有參數都被拷貝或切片(slicing),而不是以正確的引用形式傳遞。

使用 F&& f, Args&&... args完美轉發,可以:

  • 傳遞左值時,保留左值屬性,避免不必要的拷貝。
  • 傳遞右值時,移動而不是拷貝,提高效率。
  • 允許傳遞任意類型的參數,使 enqueue 適用于各種可調用對象。

問題2:理解 std::result_of<F(Args…)>::type

std::result_of<F(Args...)>::type 是 C++11/14 中用于推導可調用對象 FArgs... 作為參數調用后的返回類型的工具。

然而,這個特性在 C++17 被棄用,在 C++20 被移除,并由 std::invoke_result_t<F, Args...> 取代。

1?? 基本概念

std::result_of<F(Args...)>::type 解析 F 作為函數、函數指針、lambda 表達式或可調用對象,在傳入 Args... 參數后,它的返回值是什么。

通俗解釋

  • 如果 F(Args...) 是一個可調用表達式,那么 std::result_of<F(Args...)>::type 就是它的返回值類型。

問題3:make_shared<std::packaged_task<return_type()>>(std::bind(std::forward(f), std::forward(args)…))如何理解?

解析 std::make_sharedstd::packaged_task 中的用法

auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);

1. 代碼結構拆解

  • std::make_shared<T>(...):
    • <> 里是 要創建的對象類型:這里是 std::packaged_task<return_type()>
    • () 里是 構造函數參數:這里是 std::bind(...) 的返回值

2. std::packaged_task 介紹

std::packaged_task 是 C++11 引入的 任務封裝類,用于異步任務執行。它封裝一個可調用對象(函數、lambda 表達式等),并允許獲取其執行結果。

std::packaged_task<return_type()> task(func);
  • 作用:將 func 綁定到 task,稍后可以執行 task() 來調用 func,并通過 std::future 獲取結果。
  • 適用場景
    • 異步任務執行(如 std::threadstd::async
    • 任務隊列(線程池)

3. std::bind 介紹

std::bind 用于綁定函數和參數,返回一個可調用對象

std::bind(f, args...)
  • 作用:
    • 預綁定 f 的參數 args...
    • 返回一個可調用對象(類似 lambda
    • 適用于回調函數和延遲執行
示例
#include <iostream>
#include <functional>int add(int a, int b) { return a + b; }int main() {auto bound_func = std::bind(add, 10, 20);std::cout << bound_func() << std::endl; // 輸出 30
}

4. 為什么要用 std::forward?

enqueue 函數中使用 std::forward 的主要目的是實現完美轉發(Perfect Forwarding),確保參數 fargs... 能夠保持它們原本的值類別(左值或右值),從而避免不必要的拷貝或移動,提高程序的性能。

詳細解析:
1. F&& fArgs&&... args 萬能引用(Universal References)
  • F&&Args&&... 并不是普通的右值引用,而是模板參數推導下的萬能引用
  • fargs... 被傳入時,它們可能是左值(lvalue)或者右值(rvalue)。
  • 如果直接傳遞這些參數,不加 std::forward,可能會導致不必要的拷貝或移動,影響性能。
2. 為什么需要 std::forward
  • std::bind(std::forward<F>(f), std::forward<Args>(args)...) 這部分代碼中:

    • std::forward<F>(f) 確保 f 被正確地轉發:
      • 如果 f 是左值,則 std::forward<F>(f) 也是左值。
      • 如果 f 是右值,則 std::forward<F>(f) 也是右值(即 std::move(f))。
    • std::forward<Args>(args)... 也是同理,保證每個參數 args... 都按照它原本的類別傳遞。
  • 如果不使用 std::forward

    • std::bind(f, args...) 會導致所有參數都被按值拷貝,或者被錯誤地轉換為左值,可能會產生不必要的性能開銷。

5. 代碼運行流程

auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
執行步驟
  1. std::bind(std::forward<F>(f), std::forward<Args>(args)...)

    • 綁定 fargs...,返回一個可調用對象
  2. std::make_shared<std::packaged_task<return_type()>>(...)

    • 創建一個 std::packaged_task<return_type()>,用綁定的函數進行初始化
    • std::make_shared 進行 一次性分配內存(包括控制塊+對象),提高效率
  3. 返回一個 std::shared_ptr<std::packaged_task<return_type()>>

    • task->operator()() 執行任務
    • task->get_future() 獲取任務結果

問題4:如何理解tasks.emplace(task { (*task)(); })?

在這段代碼中,tasks.emplace([task]() { (*task)(); }) 是一個關鍵操作,它將一個可調用對象(lambda 表達式)加入到 tasks 隊列中。我們可以拆解它的邏輯來理解它的作用。

1. 理解 tasks

tasks 是一個任務隊列,通常是 std::queue<std::function<void()>> 類型的變量,用于存儲需要執行的任務。

std::queue<std::function<void()>> tasks;

因為 std::function<void()> 能夠存儲任意的可調用對象(如函數、lambda、函數對象等),所以我們可以把需要執行的任務封裝進 std::function<void()>,然后存入隊列。


2. 理解 task

auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);

共享智能指針shared_ptr 是一個模板類

// shared_ptr<T> 類模板中,提供了多種實用的構造函數, 語法格式如下:
std::shared_ptr<T> 智能指針名字(創建堆內存);

std::make_shared 是 C++11 標準引入的一個函數模板,用于創建 std::shared_ptr 對象。

這里的 task 是一個 std::shared_ptr<std::packaged_task<return_type()>>類型的對象,其中 std::packaged_task<return_type()> 封裝了一個可調用對象(比如函數、lambda、成員函數等),當 (*task)() 被調用時,它會執行 f(args...),并將結果存入一個 std::future 供外部使用。


3. 為什么要使用智能指針?

在這段代碼中,使用 智能指針 (shared_ptr) 主要是為了 管理任務的生命周期,具體來說,有以下幾個關鍵原因:

1. 確保任務 (packaged_task) 的生命周期

addTask 函數中,任務 (packaged_task<returntype()>) 被封裝到一個 shared_ptr 里:

auto task = make_shared<packaged_task<returntype()>> (bind(forward<F>(f), forward<Args>(args)...));

然后,它被存入任務隊列:

queue_Tasksqueue.emplace([task]() {(*task)(); });

這里之所以使用 shared_ptr,主要是為了 確保 task 在隊列中仍然有效,即:

  • task 可能會在 queue_Tasksqueue 中等待執行,而 addTask 可能已經執行完畢并返回。
  • 如果 task局部變量,那么在 addTask 結束時,它會被銷毀,導致 懸空指針或未定義行為
  • shared_ptr 允許 task 在多個地方安全共享,即使 addTask 結束,task 仍然存在,直到任務真正執行完畢。

2. 避免 packaged_task 的拷貝

packaged_task 不允許拷貝,因為它內部維護了一個 future,拷貝可能導致 future 的所有權問題。
因此,shared_ptr 允許 在多個地方(任務隊列和執行線程)安全傳遞 task,而無需拷貝。
所以,如果直接使用packaged_task<returntype()> task(bind(forward<F>(f), forward<Args>(args)...));是不行的,packaged_task類型的task不能拷貝,所以只能用智能指針來管理task。


3. 線程安全,避免懸空任務

任務隊列中的任務可能會在不同線程中執行:

queue_Tasksqueue.emplace([task]() {(*task)(); });
  • 任務 task 可能會在 多個線程之間傳遞,如果沒有 shared_ptr,就需要手動管理其生命周期,容易導致 內存泄漏或懸空指針
  • shared_ptr 讓任務對象在最后一個線程執行完畢后才會被銷毀,確保 線程安全

4. 簡化資源管理,避免 new/delete

如果不使用 shared_ptr,可能需要手動 new 一個 packaged_task,然后在任務執行完后手動 delete,容易出錯:

packaged_task<returntype()>* task = new packaged_task<returntype()>(bind(...));
queue_Tasksqueue.emplace([task]() {(*task)(); delete task; });

這樣管理資源容易導致:

  • 內存泄漏(忘記 delete)。
  • 懸空指針delete 過早執行)。
  • 代碼可讀性降低

使用 shared_ptr 讓 C++ 自動管理 packaged_task,避免手動 new/delete 的復雜性。


總結

使用 shared_ptr 管理 packaged_task 的生命周期,帶來的好處有:

  1. 保證任務的生命周期:即使 addTask 結束,任務仍然有效,直到被執行完畢。
  2. 避免 packaged_task 的拷貝問題:確保 future 正確管理。
  3. 線程安全:任務隊列中的任務可能在多個線程中共享,shared_ptr 確保不會提前銷毀。
  4. 避免手動 new/delete,減少資源管理的復雜性,提高代碼健壯性。

4. 為什么要用Lambda包裝std::shared_ptr<std::packaged_task<returntype()>>類型的task?

1. 直接傳入 task 會導致類型不匹配

線程池的任務隊列 queue_Tasksqueue 是:

queue<function<void(void)>> queue_Tasksqueue;

即,任務隊列存儲的是 std::function<void()> 類型的可調用對象

task 的類型是:

std::shared_ptr<std::packaged_task<returntype()>>

問題:

  • shared_ptr<packaged_task<returntype()>> 不能隱式轉換為 std::function<void()>
  • std::function<void()> 需要一個 可調用對象(比如函數、Lambda),但 shared_ptr 本身不是可調用對象

如果你嘗試這樣做:

queue_Tasksqueue.emplace(task);  // ? 編譯錯誤

編譯器會報錯,因為 queue_Tasksqueue.emplace() 需要一個 std::function<void()>,但 shared_ptr<packaged_task<returntype()>> 不是一個可調用對象


2. 為什么用 lambda 可以解決問題?

我們可以用 Lambda task 變成一個可調用對象

queue_Tasksqueue.emplace([task]() { (*task)(); });

Lambda 的作用:

  1. 捕獲 taskshared_ptr,保證 task 在任務隊列中仍然有效,不會被提前銷毀。
  2. 使 task 變成一個可調用對象,因為 (*task)(); 相當于 task->operator()(),執行 packaged_task 任務。

這樣,Lambda 的類型就變成了 std::function<void()>,可以安全地存入 queue_Tasksqueue


3. 用 std::bind 也可以

除了 Lambda,你也可以用 std::bind

queue_Tasksqueue.emplace(std::bind(&std::packaged_task<returntype()>::operator(), task));

但 Lambda 更直觀,而且 std::bind 可能在一些情況下會導致額外的拷貝,因此 Lambda 是最佳選擇


4. 總結
寫法是否可行原因
queue_Tasksqueue.emplace(task);? 錯誤shared_ptr<packaged_task> 不是可調用對象,不能隱式轉換為 std::function<void()>
queue_Tasksqueue.emplace([task]() { (*task)(); });? 正確Lambda 讓 task 變成可調用對象,并確保生命周期管理
queue_Tasksqueue.emplace(std::bind(&std::packaged_task<returntype()>::operator(), task));? 正確std::bind 也可以包裝 task,但不如 Lambda 直觀
核心結論
  • shared_ptr<packaged_task> 不是可調用對象,不能直接存入 std::function<void()>
  • Lambda 讓 task 變成可調用對象,并確保其生命周期正確管理。
  • std::bind 也能解決問題,但 Lambda 更直觀。

所以,使用:

queue_Tasksqueue.emplace([task]() { (*task)(); });

最安全、最直觀的解決方案。🚀


ThreadPool::addTask(function<void()>) 這個函數中,不能使用 引用 (const function<void()>& task) 主要是因為 任務需要被存入隊列并在稍后執行,而 std::function<void()> 可能會封裝臨時對象(如 Lambda)。然而,對于 packaged_task,情況有所不同。


5. 既然packaged_task 不能被拷貝,那可以用引用傳遞嗎?

1. packaged_task 不能直接用引用

ThreadPool::addTask(F&& f, Args&&... args) 這個 模板方法 中:

template<typename F, typename... Args>
future<typename result_of<F(Args...)>::type> addTask(F&& f, Args&&... args)
{using returntype = typename result_of<F(Args...)>::type;// `packaged_task` 綁定函數和參數auto task = make_shared<packaged_task<returntype()>> (bind(forward<F>(f), forward<Args>(args)...));future<returntype> res = task->get_future();mutex_queuemutex.lock();queue_Tasksqueue.emplace([task]() { (*task)(); });mutex_queuemutex.unlock();cv_ConditionVariable.notify_one();return res;
}

在這里,我們使用了 shared_ptr<packaged_task<returntype()>>,為什么 不能用引用packaged_task<returntype()>&)呢?

(1) packaged_task 不能被拷貝

std::packaged_task 不能被拷貝,因為它內部包含 std::future,拷貝會導致 future 結果管理混亂。所以,如果嘗試這樣做:

queue_Tasksqueue.emplace(*task);  // 直接存入隊列

會編譯失敗,因為 packaged_task 沒有拷貝構造函數

(2) 不能直接使用引用

如果 task引用 (packaged_task<returntype()>&) 傳遞,而不是 shared_ptr

packaged_task<returntype()> task(bind(forward<F>(f), forward<Args>(args)...));
queue_Tasksqueue.emplace([&task]() { task(); });

那么:

  • task局部變量,在 addTask 結束時會被銷毀。
  • 但任務隊列 queue_Tasksqueue 里存儲的 Lambda 可能會task 被銷毀后才執行,導致 懸空引用,程序崩潰。
(3) 為什么 shared_ptr 可以解決問題
auto task = make_shared<packaged_task<returntype()>> (bind(forward<F>(f), forward<Args>(args)...));
queue_Tasksqueue.emplace([task]() { (*task)(); });

這里使用 shared_ptr 解決了問題:

  • shared_ptr 允許 task 在隊列和執行線程之間共享生命周期
  • 只有當任務被執行完畢shared_ptr 計數歸零時,packaged_task 才會被銷毀,避免了懸空指針問題。

2. function<void()>packaged_task 的區別
類型可拷貝適合用 shared_ptr 嗎?適合用引用嗎?
std::function<void()>? 可以拷貝? 不需要,它本身就是拷貝管理? 不能用引用,可能封裝臨時對象
std::packaged_task<R()>? 不能拷貝? 需要 shared_ptr 管理生命周期? 不能用引用,可能被提前銷毀

3. 總結
  • 為什么 std::function<void()> 不能用引用 (&task)?

    • 任務可能是 臨時對象(如 Lambda)。
    • 任務需要 拷貝存入隊列,引用會導致懸空引用。
  • 為什么 std::packaged_task<R()> 需要 shared_ptr

    • packaged_task 不能拷貝,但需要跨線程傳遞。
    • shared_ptr 確保 task 在隊列里存活到執行,防止懸空指針。

因此,在 ThreadPool 里:

  • std::function<void()> 使用值傳遞,因為它可以安全拷貝。
  • std::packaged_task<R()> 使用 shared_ptr,避免生命周期管理問題。

直接使用引用 (&) 會導致線程安全問題或懸空指針,因此不適合在這里使用。

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

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

相關文章

CSS 屬性選擇器詳解

CSS 屬性選擇器詳解 引言 CSS(層疊樣式表)是網頁設計中的重要組成部分,它用于控制網頁元素的樣式和布局。屬性選擇器是CSS選擇器的一種,它允許開發者根據元素的特定屬性來選擇和樣式化元素。本文將詳細講解CSS屬性選擇器的概念、語法以及常用屬性選擇器的使用方法。 一、…

二維前綴矩陣

1.大衣的旅行 #include<bits/stdc.h> #define int long long using namespace std; int t; int n,m,k; bool check(int mid,vector<vector<int>>pre,vector<vector<int>>a) {for(int i1; i<n; i){for(int j1; j<m; j){//枚舉以老師房間為…

python-leetcode 56.電話號碼的字母組合

題目&#xff1a; 給定一個僅包含數字的2-9的字符串&#xff0c;返回所有它可能表示的字母組合&#xff0c;答案可以按任意順序返回 給出數字到字母的映射如下&#xff08;與電話按鍵相同&#xff09;&#xff0c;注意1不對應任何字母 方法一&#xff1a;深度優先搜索&#x…

keepalived應用

Keepalived 是一個基于 VRRP&#xff08;虛擬路由冗余協議&#xff09;實現的高可用解決方案&#xff0c;常用于構建高可用性的服務器集群&#xff0c;特別是在負載均衡場景中&#xff0c;可確保服務的不間斷運行。以下為你詳細介紹它&#xff1a; 0主要功能 高可用性&#x…

5.0 VisionPro調用USB相機的方法與步驟說明(一)

本文介紹如何在C#中調用visionPro以處理USB相機采集到的圖片。示例如下: 主要思路如下: 1. 使用AForge來打開以及采集usb相機照片。 usb相機處于一直運行狀態。每隔100ms采集一次照片。且觸發一次事件。 public void Start() { this.videoSourcePlayer.Stop(); …

論文閱讀:Deep Hybrid Camera Deblurring for Smartphone Cameras

今天介紹一篇 ACM SIGGRAPH 2024 的文章&#xff0c;關于手機影像中的去模糊的文章。 Deep Hybrid Camera Deblurring for Smartphone Cameras Abstract 手機攝像頭盡管取得了顯著的進步&#xff0c;但由于傳感器和鏡頭較為緊湊&#xff0c;在低光環境下的成像仍存在困難&am…

Linux中的基本指令(下)

目錄 mv指令 more指令 less指令 head指令 tail 指令 繼續理解文件 重定向和追加重定向操作 理解管道 find指令 whereis 指令 bc指令 uname ?r指令 grep 指令 關機 擴展命令 zip/unzip 指令 tar指令 關于rzsz 系統間的文件互傳 接上&#xff01; mv指令 m…

Unity大型游戲開發全流程指南

一、開發流程與核心步驟 1. 項目規劃與設計階段 需求分析 明確游戲類型&#xff08;MMORPG/開放世界/競技等&#xff09;、核心玩法&#xff08;戰斗/建造/社交&#xff09;、目標平臺&#xff08;PC/移動/主機&#xff09;示例&#xff1a;MMORPG需規劃角色成長樹、副本Boss…

Unity WebGL IIS報錯無法使用

Unity WebGL IIS報錯無法使用 原因1&#xff1a;WebGL文件夾無訪問權限 右鍵WebGL文件夾-屬性 點擊安全-編輯-添加 輸入ever點擊確定-應用即可

【JDK17】開源應用服務器大比對

接著 next-public 源代碼分析&#xff0c;Java 應用服務器選用 jetty。但是之前普遍使用 Tomcat&#xff0c;那為什么要用 jetty 么&#xff0c;除了這兩個&#xff0c;Java 應用服務器開源現狀并不了解&#xff0c;故而又是一篇科普性的筆記&#xff0c;以下是 又小又快的 Jav…

docker-compose install nginx(解決fastgpt跨區域)

CORS前言 CORS(Cross-Origin Resource Sharing,跨源資源共享)是一種安全措施,它允許或拒絕來自不同源(協議、域名、端口任一不同即為不同源)的網頁訪問另一源中的資源。它的主要作用如下: 同源策略限制:Web 瀏覽器的同源策略限制了從一個源加載的文檔或腳本如何與另一…

算法刷題記錄——LeetCode篇(4) [第301~400題](持續更新)

(優先整理熱門100及面試150&#xff0c;不定期持續更新&#xff0c;歡迎關注) 322. 零錢兌換 給你一個整數數組 coins &#xff0c;表示不同面額的硬幣&#xff1b;以及一個整數 amount &#xff0c;表示總金額。 計算并返回可以湊成總金額所需的最少的硬幣個數。如果沒有任何…

vulnhub靶場之loly靶機

前言 挑戰攻克該靶機30分鐘 靶機&#xff1a;loly靶機&#xff0c;IP地址為192.168.10.11 攻擊&#xff1a;kali&#xff0c;IP地址為192.168.10.6 靶機和攻擊機都采用VMware虛擬機&#xff0c;都采用橋接網卡模式 文章涉及的靶機及工具&#xff0c;都可以自行訪問官網或者項…

Deepseek API+Python測試用例一鍵生成與導出-V1.0.2【實現需求文檔圖片識別與用例生成自動化】

在測試工作中&#xff0c;需求文檔中的圖片&#xff08;如界面設計圖、流程圖&#xff09;往往是測試用例生成的重要參考。然而&#xff0c;手動提取圖片并識別內容不僅耗時&#xff0c;還容易出錯。本文將通過一個自研小工具&#xff0c;結合 PaddleOCR 和大模型&#xff0c;自…

Excel(函數篇):COUNTIF與CONUTIFS函數、SUMIF與SUMIFS函數、ROUND函數、MATCH與INDEX函數、混合引用與條件格式

目錄 COUNTIF和COUNTIFS函數COUNTIF函數COUNTIFS函數SUMIF和SUMIFS函數SUMIF函數SUMIFS函數SUMIFS函數與控件實現動態年月匯總ROUND、ROUNDUP、ROUNDDOWN函數單元格混合引用條件格式與公式,標記整行數據MATCH和INDEX函數COUNTIF和COUNTIFS函數 COUNTIF函數 統計下“蘇州”出現…

上位機數據可視化:使用QtCharts繪制波形圖

工程配置 CMake文件 find_package(Qt5 COMPONENTS Charts REQUIRED)target_link_libraries(zhd-desktop PRIVATE Qt5::Charts)包含頭文件以及名稱空間&#xff08;這個很重要&#xff0c;沒有包含名稱空間編譯器會提示找不到相關的類型&#xff09; #include <QtCharts&g…

S32K144入門筆記(十三):LPIT的API函數解讀

目錄 1. SDK中的函數 2. API函數的釋義 2.1 獲取默認參數 2.2 初始化 2.3 啟動與停止 2.4 計數值的設置于讀取 2.5 中斷API 1. SDK中的函數 在使用SDK的非抽象驅動函數時&#xff0c;函數的定義與聲明在文件lpit_driver.c和lpit_driver.h中&#xff0c;一共有19個函數&a…

CSS - Pseudo-classes(偽類選擇器)

目錄 一、介紹二、常用種類三、案例實現案例一&#xff1a;a標簽使用link/visited/hover/active案例二&#xff1a;表單元素使用focus/disabled案例三、通過其余偽類實現元素靈活選中 一、介紹 CSS 偽類&#xff08;Pseudo-classes&#xff09; 用于定義元素的特定狀態或結構位…

http proxy的原理是什么

Http代理的原理 代理服務器會自動提取請求數據包中的HTTP請求數據發送給服務端&#xff0c;并將服務端的HTTP響應數據轉發給發送請求的客戶端&#xff0c;HTTP代理服務器使用的端口通常是8080。 對于Web客戶端來說&#xff0c;代理扮演的服務器角色&#xff0c;接收請求&…

Ubuntu22.04虛擬機里安裝Yolov8流程

1. 安裝pytorch sudo apt install nvidia-cuda-toolkit nvcc --version # 官方適配地址&#xff1a;https://download.pytorch.org/whl/torch/import torch print(torch.__version__) print(torch.cuda.is_available())2. 安裝環境 # cuDNN 安裝&#xff1a;https://develop…