C++ : 線程庫

C++ : 線程庫

  • 一、線程thread
    • 1.1 thread類
      • 1.1.1 thread對象構造函數
      • 1.1.2 thread類的成員函數
      • 1.1.3 線程函數的參數問題
    • 1.2 this_thread 命名空間域
      • 1.2.1 chrono
  • 二、mutex互斥量庫
    • 2.1 mutex的四種類型
      • 2.1.1 mutex 互斥鎖
      • 2.2.2 timed_mutex 時間鎖
      • 2.2.3 recursive_muetx 遞歸鎖
      • 2.2.4 recursive_timed_muetx 時間遞歸鎖
    • 2.2 RAII的加鎖策略
      • 2.2.1 lock_guard 作用域鎖
      • 2.2.2 unique_lock 獨占鎖
  • 三、condition_variable 條件變量
    • 3.1 wait系列函數
    • 3.2 notify系列函數
    • 3.3 簡單示例
  • 四、atomic 原子性操作庫
    • 4.1 atomic使用
    • 4.2 CAS

一、線程thread

操作線程需要頭文件,頭文件包含線程相關操作,內含兩個內容:

  • thread類:操作線程的基本類
  • this_thread命名空間域:用于操作當前線程

1.1 thread類

1.1.1 thread對象構造函數

  1. 調用無參的構造函數
    ??thread提供了無參的構造函數,調用無參的構造函數創建出來的線程對象沒有關聯任何線程函數,即沒有啟動任何線程。
    ??由于thread提供了移動賦值函數,因此當后續需要讓該線程對象與線程函數關聯時,可以以帶參的方式創建一個匿名對象,然后調用移動賦值將該匿名對象關聯線程的狀態轉移給該線程對象。
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t1;//...t1 = thread(func, 10);t1.join();return 0;
}

使用場景
??實現線程池的時候就是需要先創建一批線程,但一開始這些線程什么也不做,當有任務到來時再讓這些線程來處理這些任務。


  1. 調用帶參的構造函數
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);

參數說明:

  • fn:可調用對象,比如函數指針、仿函數、lambda表達式、被包裝器包裝后的可調用對象等。
  • args…:調用可調用對象fn時所需要的若干參數。

調用帶參的構造函數創建線程對象,能夠將線程對象與線程函數fn進行關聯。

void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t2(func, 10);t2.join();return 0;
}

  1. 調用移動構造函數
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t3 = thread(func, 10); //右邊值對象t3.join();return 0;
}

1.1.2 thread類的成員函數

成員函數功能描述
join對該線程進行等待,在等待的線程返回之前,調用 join 函數的線程將會被阻塞
joinable判斷該線程是否已經執行完畢,如果是則返回 true,否則返回 false
detach將該線程與創建線程進行分離,被分離后的線程不再需要創建線程調用 join 函數等待
get_id獲取該線程的 id
swap將兩個線程對象關聯線程的狀態進行交換

joinable函數同樣也可以判斷線程是否有效,下面情況都是線程無效的情況:

  • 采用無參構造函數構造的線程對象。(該線程對象沒有關聯任何線程)
  • 線程對象的狀態已經轉移給其他線程對象。(已經將線程交給其他線程對象管理)
  • 線程已經調用join或detach結束。(線程已經結束)

1.1.3 線程函數的參數問題

??線程函數的參數是以值拷貝方式拷貝到線程空間中的,就算線程函數的參數為引用類型,在線程函數中修改后也不會影響到外部實參,因為其實際引用的是線程棧中的拷貝,而不是外部實參。

#include <thread>
void ThreadFunc1(int& x)
{x += 10;
}int main()
{int a = 10;// 在線程函數中對a修改,不會影響外部實參,因為:線程函數參數雖然是引用方式,但其實際引用的是線程棧中的拷貝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;
}

??要通過線程函數的形參改變外部的實參,**下面有三種方式:
1. 借助std::ref函數
??線程函數的參數類型為引用類型時,如果要想線程函數形參引用的是外部傳入的實參,而不是線程棧空間中的拷貝,那么在傳入實參時需要借助ref函數保持對實參的引用。

#include <thread>
void ThreadFunc1(int& x)
{x += 10;
}int main()
{int a = 10;// 如果想要通過形參改變外部實參時,必須借助std::ref()函數thread t2(ThreadFunc1, std::ref(a));t2.join();cout << a << endl;
}

2. 地址的拷貝
??將線程函數的參數類型改為指針類型,將實參的地址傳入線程函數,此時在線程函數中可以通過修改該地址處的變量,進而影響到外部實參。

#include <thread>
void ThreadFunc2(int* x)
{*x += 10;
}int main()
{int a = 10;// 地址的拷貝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}

3. 借助lambda表達式
?? 將lambda表達式作為線程函數,利用lambda函數的捕捉列表,以引用的方式對外部實參進行捕捉,此時在lambda表達式中對形參的修改也能影響到外部實參。

#include <thread>int main()
{int a = 10;// 借助lambda表達式thread t3([&a]{a+=10;});t3.join();cout << a << endl;return 0;
}

1.2 this_thread 命名空間域

std::this_thread 是一個命名空間,用于訪問當前線程。它提供了一組函數來操作當前線程。

函數名功能描述
yield當前線程"放棄"執行,讓操作系統調度另一線程繼續執行
get_id返回當前線程的 ID
sleep_until讓當前線程休眠到一個具體時間點
sleep_for讓當前線程休眠一個時間段

get_idyield都可以直接執行,不用傳入參數。而后兩個函數與時間相關,要用到C++封裝的時間庫chrono

1.2.1 chrono

是一個頭文件,內包含chrono命名空間域,該域內部封裝了各種時間的相關操作。

  1. std::chrono::system_clock:系統時鐘,表示從 Unix 紀元開始的時間(1970 年 1 月 1 日 00:00:00 UTC)。
  2. std::chrono::steady_clock:穩定時鐘,表示從程序啟動開始的時間。

這兩個時鐘都有一個now成員函數,返回當前的時間。但是system_clock會受到系統時鐘影響,如果用戶調整了系統時間,就有可能造成時間錯誤,而穩定時鐘不受系統時鐘影響。如下:

auto t1 = std::chrono::system_clock::now();
auto t2 = std::chrono::steady_clock::now();

這兩個函數都返回一個time_point類型,表示當前時間點。

duration用于表示一個時間段,這個類的用法比較復雜,因此C++封裝了一些可以直接使用的類:

  • std::chrono::nanoseconds (納秒)
    std::chrono::microseconds (微秒)
    std::chrono::milliseconds (毫秒)
    std::chrono::seconds (秒)
    std::chrono::minutes (分鐘)
    std::chrono::hours (小時)

這些類都是typedef后的duration,如果想要表示一個時間段,直接傳數字即可:

auto dur1 = std::chrono::seconds(3); // 3秒
auto dur2 = std::chrono::minutes(5); // 5分鐘

sleep_until需要傳入一個時間點time_point,比如想要睡眠10秒,就可以用當前時間 + 10秒得到一個時間點,再用sleep_until完成睡眠:

auto t1 = std::chrono::steady_clock::now(); // 獲取當前時間	
auto dur = std::chrono::seconds(10); // 獲取十秒時間段
auto t2 = t1 + dur; // 時間點 + 時間段 = 時間點,十秒后std::this_thread::sleep_until(t2); // 睡眠到 10 秒后

sleep_for需要傳入一個時間段duration,同樣的睡眠十秒:

auto dur = std::chrono::seconds(10); // 獲取十秒時間段
std::this_thread::sleep_for(dur); // 睡眠到 10 秒后

二、mutex互斥量庫

mutex的種類:

  1. mutex:互斥鎖
  2. recursive_muetx:遞歸鎖
  3. timed_mutex:時間鎖
  4. recursive_timed_muetx:時間遞歸鎖

兩種基于RAII的加鎖策略:

  1. lock_guard:作用域鎖
  2. unique_lock:獨占鎖

2.1 mutex的四種類型

2.1.1 mutex 互斥鎖

mutex鎖是C++11提供的最基本的互斥量,mutex對象之間不能進行拷貝,也不能進行移動。
成員函數如下:

成員函數功能描述
lock對互斥量進行加鎖
unlock對互斥量進行解鎖,釋放互斥量的所有權
try_lock對互斥量嘗試進行加鎖

線程函數調用lock時,可能會發生以下三種情況:

  • 如果該互斥量當前沒有被其他線程鎖住,則調用線程將該互斥量鎖住,直到調用unlock之前,該線程一致擁有該鎖。
  • 如果該互斥量已經被其他線程鎖住,則當前的調用線程會被阻塞。
  • 如果該互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

線程調用try_lock時,類似也可能會發生以下三種情況:

  • 如果該互斥量當前沒有被其他線程鎖住,則調用線程將該互斥量鎖住,直到調用unlock之前,該線程一致擁有該鎖。
  • 如果該互斥量已經被其他線程鎖住,則try_lock調用返回false,當前的調用線程不會被阻塞。
  • 如果該互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

簡單實例:

int num = 0;void test(int n, std::mutex& mtx)
{for (int i = 0; i < n; i++){mtx.lock();num++;mtx.unlock();}
}int main()
{std::mutex mtx;std::thread t1(test, 2000, std::ref(mtx));std::thread t2(test, 2000, std::ref(mtx));t1.join();t2.join();std::cout << "num = " << num << std::endl;return 0;
}

2.2.2 timed_mutex 時間鎖

時間鎖就是限定每次申請鎖的時長,如果超過一定時間沒有申請到鎖,就返回。
成員函數:

成員函數功能描述
lock對互斥量進行加鎖
unlock對互斥量進行解鎖,釋放互斥量的所有權
try_lock對互斥量嘗試進行加鎖
try_lock_until如果到指定時間還沒申請到鎖就返回false,申請到鎖返回true
try_lock_for如果一段時間內沒申請到鎖就返回false,申請到鎖返回true
  • try_lock_for:接受一個時間范圍,表示在這一段時間范圍之內線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間之內還是沒有獲得鎖),則返回false。
  • try_lock_untill:接受一個時間點作為參數,在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間點到來時還是沒有獲得鎖),則返回false。

簡單示例:

int num = 0;void test(int n, std::timed_mutex& mtx)
{while (n){bool ret = mtx.try_lock_for(std::chrono::microseconds(1));if (ret){num++;n--;mtx.unlock();}else{std::cout << "加鎖超時" << std::endl;}}
}int main()
{std::timed_mutex mtx;std::thread t1(test, 2000, std::ref(mtx));std::thread t2(test, 2000, std::ref(mtx));t1.join();t2.join();std::cout << "num = " << num << std::endl;return 0;
}

2.2.3 recursive_muetx 遞歸鎖

recursive_mutex叫做遞歸互斥鎖,該鎖專門用于遞歸函數中的加鎖操作。

  • 如果在遞歸函數中使用mutex互斥鎖進行加鎖,那么在線程進行遞歸調用時,可能會重復申請已經申請到但自己還未釋放的鎖,進而導致死鎖問題。
  • 而recursive_mutex允許同一個線程對互斥量多次上鎖(即遞歸上鎖),來獲得互斥量對象的多層所有權,但是釋放互斥量時需要調用與該鎖層次深度相同次數的unlock。

簡單實例:

int num = 0;void test(int n, std::recursive_mutex& mtx)
{if (n <= 0)return;mtx.lock();test(n - 1, mtx);mtx.unlock();
}int main()
{std::recursive_mutexmtx;std::thread t1(test, 2000, std::ref(mtx));t1.join();std::cout << "num = " << num << std::endl;return 0;
}

如果是使用mutex.lock()時,申請不到鎖,不論是誰占有這把鎖,都會陷入阻塞,直到鎖被釋放。recursive_mutex則會記錄是誰占有這把鎖,在recursive_mutex.lock()時,會檢查申請鎖的線程和占有鎖的線程是不是同一個,如果是同一個,則直接申請成功,因此可以避免遞歸死鎖。

2.2.4 recursive_timed_muetx 時間遞歸鎖

與時間和遞歸鎖效果相加。


2.2 RAII的加鎖策略

2.2.1 lock_guard 作用域鎖

lock_guard是C++11中的一個模板類,其定義如下:

template <class Mutex>
class lock_guard;

C++是用的是RAII方法進行加鎖:

  • 在需要加鎖的地方,用互斥鎖實例化一個lock_guard對象,在lock_guard的構造函數中會調用lock進行加鎖。
  • 當lock_guard對象出作用域前會調用析構函數,在lock_guard的析構函數中會調用unlock自動解鎖

簡單示例:

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;mutex mtx;
bool Stop()
{return false;
}void func()
{// 保護一段匿名的代碼區間{lock_guard<mutex> lg(mtx); // 調用構造函數構造if (!Stop())return; // 調用析構函數析構}}// 生命周期結束后調用析構函數析構
int main()
{func();return 0;
}

模擬實現lock_guard:

namespace JRH
{template<class Mutex>class lock_guard{public:lock_guard(Mutex& mtx):_mtx(mtx){_mtx.lock(); // 加鎖}~lock_guard(){_mtx.unlock();}// 防拷貝lock_guard(const Mutex&) = delete;lock_guard& operator=(const Mutex&) = delete;private:Mutex& _mtx;};
}

1.lock_guard類中包含一個鎖成員變量(引用類型),這個鎖就是每個lock_guard對象管理的互斥鎖。
2.調用lock_guard的構造函數時需要傳入一個被管理互斥鎖,用該互斥鎖來初始化鎖成員變量后,調用互斥鎖的lock函數進行加鎖。
3.lock_guard的析構函數中調用互斥鎖的unlock進行解鎖。
4.需要刪除lock_guard類的拷貝構造和拷貝賦值,因為lock_guard類中的鎖成員變量本身也是不支持拷貝的。(防拷貝)

2.2.2 unique_lock 獨占鎖

lock_guard的可操作性很低,只有構造和析構兩個函數,也就是只有自動釋放鎖的能力。而unique_lock功能更加豐富,而且可以自由操作鎖。

unique_lock在構造時,可以傳入一把鎖,在構造的同時會對該鎖進行加鎖。在unique_lock析構時,判斷當前的鎖有沒有加鎖,如果加鎖了就先釋放鎖,后銷毀對象。

而在構造與析構之間,也就是整個unique_lock的生命周期,可以自由的加鎖解鎖:

  • lock:加鎖
    unlock:解鎖
    try_lock:如果沒上鎖就加鎖,上鎖了就返回
    try_lock_until:如果到指定時間還沒申請到鎖就返回false,申請到鎖返回true
    try_lock_for:如果一段時間內沒申請到鎖就返回false,申請到鎖返回true

提供了以上五個接口,也就是說可以作用于前面的任何一款鎖。另外的unique_lcok還允許賦值operator=,調用賦值時,如果當前鎖沒有持有鎖,那么直接拷貝。如果當前鎖持有鎖,那么把鎖的所有權轉移給新的unique_lcok,自己不再持有鎖。


三、condition_variable 條件變量

談到鎖,自然也要談條件變量,這是線程同步的重要手段,C++將條件變量放在頭文件<condition_variable>中。

條件變量庫有很多函數,但總體分為兩類函數,分別是wait系列函數和notify系列函數。

3.1 wait系列函數

wait函數提供了兩個不同版本的接口:

void wait (unique_lock<mutex>& lck);template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);

其有兩個重載,第一個只有一個參數,也就是我剛剛提到的只要傳入一個unique_lock<mutex>。第二個重載允許傳入第二個參數pred,這是一個可調用對象,用于作為條件變量的判斷值。

wait的第二個參數要求是一個可調用對象,返回值類型偽bool,作用如下:

  • 返回true:表示條件成立,wait直接返回,不進入等待隊列
  • 返回false:表示條件不成立,wait阻塞,進入等待隊列直到被喚醒

為什么調用wait系列函數時需要傳入一個互斥鎖

  • 因為wait系列函數一般是在臨界區中調用的,為了讓當前線程調用wait阻塞時其他線程能夠獲取到鎖,因此調用wait系列函數時需要傳入一個互斥鎖,當線程被阻塞時這個互斥鎖會被自動解鎖,而當這個線程被喚醒時,又會自動獲得這個互斥鎖。
  • 因此wait系列函數實際上有兩個功能,一個是讓線程在條件不滿足時進行阻塞等待,另一個是讓線程將對應的互斥鎖進行解鎖。

wait_for和wait_until函數

  • wait_for:進入條件變量的等待隊列,一定時間后如果沒有被喚醒,則不再等待返回false
  • wait_until:進入條件變量的等待隊列,到指定時間后如果沒有被喚醒,則不再等待返回false
  • 這兩個與時間相關的等待,分別要傳入時間段duration和時間點time_point。至于為什么要傳入一把鎖,這屬于并發編程的知識,就不在博客中講解了。

3.2 notify系列函數

條件變量下可能會有多個線程在進行阻塞等待,這些線程會被放到一個等待隊列中進行排隊。
notify系列成員函數的作用就是喚醒等待的線程,包括notify_one和notify_all。

  • notify_one:喚醒等待隊列中的首個線程,如果等待隊列為空則什么也不做。
  • notify_all:喚醒等待隊列中的所有線程,如果等待隊列為空則什么也不做。

3.3 簡單示例

下面我們通過 實現兩個線程交替打印1-100數 的例子來更好的認識條件變量

int n = 0;
bool flag = true;std::mutex mtx;
std::condition_variable cv; // 條件變量void func(bool run) // run用于標識是否輪到當前線程輸出
{while (n < 100){std::unique_lock<std::mutex> lock(mtx);while (flag != run) // 使用while代替if,防止偽喚醒cv.wait(lock);  // 沒輪到當前線程,進入條件變量等待std::cout << n << std::endl;n++;flag = !flag;cv.notify_one();}
}int main()
{std::thread t1(func, true);  // falg == true 輸出偶數std::thread t2(func, false); // falg == false 輸出奇數t1.join();t2.join();return 0;
}

while循壞防止偽喚醒。


四、atomic 原子性操作庫

在多線程情況下要加鎖,就是因為很多操作不是原子性的。但是有一些簡單的操作,比如num++,每次都加鎖解鎖,性能必然會降低。因此C++又提供了原子庫,其實現了簡單操作的原子化,一些簡單的++,–等操作都實現了原子化,可以在不加鎖的情況下保證線程安全,需要頭文件<atomic>

4.1 atomic使用

在C++11中,程序員不需要對原子類型變量進行加鎖解鎖操作,線程能夠對原子類型變量互斥的
訪問。更為普遍的,程序員可以使用atomic類模板,定義出需要的任意原子類型。

atomic<類型> 變量名;

簡單示例:

std::atomic<int> num = 0;void test(int n)
{for (int i = 0; i < n; i++){num++;}
}int main()
{std::thread t1(test, 2000);std::thread t2(test, 2000);t1.join();t2.join();std::cout << "num = " << num << std::endl;return 0;
}

atomic類成員函數::

  • operator++operator--,自增自減的操作是原子的。
  • fetch_* :
    • fetch_add:原子性,增加指定的值
    • fetch_sub:原子性,減少指定的值
    • fetch_and:原子性,與指定值按位與
    • fetch_or:原子性,與指定值按位或
    • fetch_xor:原子性,與指定值按位異或
std::atomic<int> num = 3;
num.fetch_add(5);
例如fetch_add,用于實現對一個原子類型增加指定值,
以上代碼完成了3 + 5的計算,且過程是原子性的
  • store :用于設定原子類型為指定值
std::atomic<int> num = 3;
num.store(100);
--num.store(100)相當于num = 100,但是過程是原子性的
  • load用于獲取原子類型當前的值,也是原子的。

  • operator T是隱式類型轉換,也就是從atomic轉化為T類型,此時就可以把原子類型當作一般類型來使用了,不過要注意的是,隱式轉換后就是一般類型,不再具有原子性。


4.2 CAS

CAS(Compare and Set)是一種原子操作,用于實現并發編程中的無鎖同步。它通過比較內存位置的當前值與預期值,如果相等則更新為新值,從而避免競態條件。CAS操作廣泛應用于多線程環境。

C++之所以可以實現變量的原子操作,是基于CAS的原子操作,這是一個硬件級別的操作,其涉及三個操作數:

  • 內存位置
  • 預期值
  • 更新值

操作流程為:讀取內存位置的當前值,判斷是否與預期值相等,如果相等,將其變為更新值,如果不相等,返回當前值。

下面是一個CAS的偽代碼:

boolean CAS(內存R, 寄存器A, 寄存器B) {if (R == A) {  // 比較內存位置R的當前值是否等于寄存器A中的預期值R = B;     // 如果相等,則將內存位置R更新為寄存器B中的新值return true;  // 返回操作成功}return false;  // 否則返回操作失敗
}
  • 參數說明
    • 內存R:共享變量的內存位置。
    • 寄存器A:預期值(expected value)。
    • 寄存器B:新值(new value)。
  • 工作流程
    1. 首先,讀取內存位置RRR的當前值。
    2. 比較當前值是否等于預期值AAA
    3. 如果相等,原子性地將RRR更新為BBB,并返回true
    4. 如果不相等,說明RRR已被其他線程修改,操作失敗,返回false
  • 原子性保證:在實際硬件中,CAS操作由一條CPU指令(如x86的CMPXCHG)實現,確保整個比較和更新過程不可中斷。

CAS操作的優缺點
CAS操作在并發編程中高效,但也存在一些限制:

  • 優點:避免線程阻塞,減少上下文切換開銷,適用于高并發場景(如計數器或鎖實現)。
  • 缺點
    • ABA問題:如果RRR的值從AAA變為BBB后又變回AAA,CAS會誤以為值未變,可能導致邏輯錯誤。解決方法包括添加版本號(如Java的AtomicStampedReference)。
    • 僅支持單個變量的原子操作,對多變量復合操作需額外機制(如循環CAS)。
    • 在高競爭環境下,頻繁失敗會消耗CPU資源。

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

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

相關文章

idea的使用小技巧,個人向

idea的使用小技巧&#xff0c;個人向 一、前言二、過程1、顯示內存的使用情況2、去掉xml文件中的黃色背景3、顯示所有打開文件4、顯示工具欄到菜單下面5、使用JDK8 一、前言 每次重裝idea都需要重新設置一下&#xff0c;這里做個記錄。 這些技巧只是個人感覺的好用 演示用的…

debian及衍生發行版apt包管理常見操作

好的&#xff0c;這是 Debian 及其衍生版&#xff08;如 Ubuntu&#xff09;使用的 apt 包管理器的常用命令速查表。 一點說明&#xff1a;apt 是新一代的命令行工具&#xff0c;整合了 apt-get 和 apt-cache 的常用功能&#xff0c;并提供了更友好的交互體驗。本表主要使用現…

vue調用函數

好的&#xff0c;我們來講解如何在 Vue 模板中調用函數。您提供的代碼是一個非常棒的、很實用的例子。 在 Vue 模板中&#xff0c;你可以在兩個主要地方調用函數&#xff1a; 文本插值中&#xff1a;像 {{ formatDate(date) }} 這樣&#xff0c;函數的返回值會作為文本被渲染到…

前端常用構建工具介紹及對比

打包構建工具是現代軟件開發中必不可少的,它們幫助開發者自動化構建、打包、部署等流程,提升開發效率。不過,不同時期構建工具略有差異。 每個構建工具都有其擅長的領域,我們需要知道其優勢,才能在我們實際開發中選擇合適的構建工具進行構建處理。 1. Gulp Gulp 是一個…

Web后端開發-SpringBootWeb入門、Http協議、Tomcat

文章目錄Web后端開發簡介SpringBootWeb入門HTTP協議HTTP-概述HTTP-請求協議HTTP-響應協議HTTP-協議解析Web服務器-Tomcat簡介基本使用SpringBootWeb入門程序解析Web后端開發簡介 SpringBootWeb入門 package com.wuxuan.javaweb_wushuang.controller;import org.springframework…

物聯網通信技術全景剖析:從LoRa到5G的深度對比與選型指南

物聯網通信技術全景剖析&#xff1a;從LoRa到5G的深度對比與選型指南在萬物互聯時代&#xff0c;選擇合適的通信技術如同為設備構建“神經網絡”。本文將深入解析七大主流物聯網通信技術&#xff0c;助您在技術選型中精準決策。一、低功耗廣域網&#xff08;LPWAN&#xff09;技…

俄羅斯方塊AI深度解析:從算法原理到實現細節

俄羅斯方塊AI深度解析:從算法原理到實現細節 前言 俄羅斯方塊,這個誕生于1984年的經典游戲,至今仍然是人工智能研究領域的熱門課題。當簡單的幾何形狀在網格中不斷下落時,看似簡單的規則背后卻隱藏著復雜的策略決策問題。本文將深入剖析一個基于Python實現的俄羅斯方塊AI…

Spring Boot 框架創建一個簡單的后端接口,并介紹如何使用 Apifox 連接該接口

目錄 一、配置 二、使用 IntelliJ IDEA 創建 Spring Boot 項目 1.打開 IntelliJ IDEA&#xff0c;選擇 File > New > Project 2.在左側面板選擇 Spring Initializr&#xff0c;項目名稱設置為HelloWorldAPI 3.點擊 Create 完成項目創建 三、創建控制器類 四、運行項…

CICD[導航]、docker+gitlab+harbor+jenkins從安裝到部署

一、安裝 CICD[軟件安裝]&#xff1a;docker安裝gitlab-CSDN博客 CICD[軟件安裝]&#xff1a;ubuntu安裝jenkins-CSDN博客 CICD[軟件安裝]&#xff1a;ubuntu安裝私有鏡像倉庫-Harbor-CSDN博客 CICD[軟件安裝]&#xff1a;ubuntu24安裝Docker-CSDN博客 二、鏡像執行 CICD[…

深度學習圖像分類數據集—蘑菇識別分類

該數據集為圖像分類數據集&#xff0c;適用于ResNet、VGG等卷積神經網絡&#xff0c;SENet、CBAM等注意力機制相關算法&#xff0c;Vision Transformer等Transformer相關算法。 數據集信息介紹&#xff1a;蘑菇識別分類&#xff1a;[Agaricus, Amanita, Boletus, Cortinarius, …

iOS 多線程導致接口亂序?抓包還原 + 請求調度優化實戰

在一次性能優化過程中&#xff0c;我們將 iOS App 內多處請求改為并行處理&#xff0c;以提高頁面加載速度。但上線后卻收到部分用戶反饋&#xff1a;進入頁面后數據加載錯亂&#xff0c;有時展示前一次頁面內容&#xff0c;有時同一個接口請求重復返回不同內容。 日志僅顯示正…

PDFBox 在 Linux 報 “No glyph for U+535A (博)” —— 一次子集化踩坑與完整排查清單

PDFBox 在 Linux 報 “No glyph for U535A (博)” —— 一次子集化踩坑與完整排查清單關鍵詞&#xff1a;PDFBox、PDType0Font、子集嵌入&#xff08;subset embedding&#xff09;、SimHei、思源黑體、字體回退1. 背景業務場景 后端使用 Apache PDFBox 填充含 AcroForm 的中文…

網安系列【8】之暴力破解入門

文章目錄 引用資料一 什么是暴力破解&#xff1f;二 暴力破解的工作原理三 暴力破解的類型3.1 傳統暴力破解3.2 字典攻擊3.3 混合攻擊3.4 彩虹表攻擊 四 暴力破解實戰演示4.1 環境和工具4.2 破解操作 五 防御暴力破解的策略六 暴力破解的相關法律七 延伸學習總結 引用資料 Bur…

使用tensorflow的線性回歸的例子(四)

與經典線性回歸比較 import matplotlib.pyplot as plt %matplotlib inline import tensorflow as tf import numpy as np from sklearn.linear_model import LinearRegression #from sklearn.datasets.samples_generator import make_regression Xdata np.array([4.0, …

服務器中故障轉移機制是指什么意思?

在企業服務器和數據中心當中&#xff0c;電源冗余機制和故障轉移機制是保障系統高可用性和穩定性的重要組成部分&#xff0c;電源故障轉移系統可以幫助企業有效減少服務器因為硬件故障導致業務中斷的情況&#xff0c;本文就來詳細了解一下服務器中故障轉移機制。服務器中的故障…

rook-ceph的osd沒有啟動rook-ceph-osd-prepare狀態異常處理

rook-ceph搭建好ceph之后&#xff0c;查看ceph集群狀態&#xff0c;發現三節點只有兩個osd狀態正常注&#xff1a;這里是已經恢復后的截圖。 使用kubectl get pod -n rook-ceph查看pod都是處于運行狀態 rook-ceph-osd-prepare也都是Completed沒問題&#xff0c;實際使用kubectl…

ubuntu手動編譯VTK9.3 Generating qmltypes file 失敗

?在Ubuntu上手動編譯VTK 9.3時&#xff0c;可能會遇到 Generating qmltypes file失敗的問題。這個問題通常與VTK在處理Qt依賴時發生的錯誤有關。以下是解決該問題的詳細步驟和相關解釋。一、確保系統依賴正確安裝在編譯VTK之前&#xff0c;需要確保所有依賴項已經正確安裝&…

計算機科學導論(1)哈佛架構

文章目錄一、哈佛架構的定義與起源二、哈佛架構的核心組成與工作原理1. **物理結構&#xff1a;獨立的存儲與總線**2. **工作流程&#xff1a;并行處理的實現**三、哈佛架構與馮諾依曼架構的對比四、哈佛架構的優缺點分析1. **優勢**2. **局限性**五、哈佛架構的實際應用場景1.…

VBScript 安裝使用教程

一、VBScript 簡介 VBScript&#xff08;Visual Basic Scripting Edition&#xff09;是微軟推出的一種輕量級腳本語言&#xff0c;語法類似于 Visual Basic&#xff0c;廣泛應用于系統管理、自動化腳本、網頁客戶端&#xff08;IE 專屬&#xff09;以及 Windows 批處理等場景…

RSTP 拓撲收斂機制

RSTP拓撲收斂觸發條件 RSTP中檢測到拓撲變化只有一個標準&#xff1a;以一個非邊緣端口遷移到Forwarding狀態 收斂過程 為本交換設備的所有非邊緣指定端口和根端口啟動TC While Timer&#xff0c;該計時器是Hello Time&#xff08;默認2s&#xff09;的兩倍&#xff0c;然后…