C++并行計算:OpenMP與MPI全解析

在高性能計算領域,充分利用硬件資源的并行計算技術已成為剛需。從單節點多核到跨節點集群,開發者需要掌握不同的并行編程模型。本文將系統講解兩種主流并行技術:OpenMP(共享內存多核并行)與MPI(分布式內存集群并行),從基礎語法到實戰案例,全面覆蓋其原理、實現與優化技巧,幫助開發者構建高效的并行應用。

一、并行計算基礎與兩種模型對比

1.1 并行計算的核心挑戰

隨著摩爾定律放緩,單核心性能提升受限,并行計算成為突破性能瓶頸的關鍵。并行計算的核心挑戰包括:

  • 數據劃分:如何將大規模數據均勻分配到多個計算單元
  • 通信開銷:計算單元間的數據交互成本
  • 同步機制:保證并行操作的正確性與一致性
  • 負載均衡:避免部分計算單元空閑,最大化資源利用率

1.2 兩種并行模型的本質區別

特性OpenMPMPI
內存模型共享內存(單節點內)分布式內存(跨節點)
并行單元線程(輕量級)進程(重量級)
通信方式共享變量訪問顯式消息傳遞
適用場景多核CPU單節點集群/超級計算機多節點
編程復雜度低(增量并行化)高(需設計通信邏輯)
擴展性有限(受單節點核心數限制)高(支持數千節點)
典型應用科學計算、數值模擬分布式機器學習、氣象模擬

1.3 混合并行模型(OpenMP+MPI)

在大規模集群中,通常采用混合模型:節點內用OpenMP利用多核,節點間用MPI通信。這種組合兼顧了編程效率與擴展性,是當前高性能計算的主流方案。

二、OpenMP:共享內存多核并行

OpenMP(Open Multi-Processing)是基于共享內存的并行編程API,通過編譯制導語句(#pragma)實現增量式并行化,無需重寫整個程序,是多核單節點并行的首選技術。

2.1 OpenMP核心原理與編譯環境

核心原理

OpenMP采用fork-join模型:

  1. 程序默認以單線程(主線程)開始執行
  2. 遇到并行區域(#pragma omp parallel)時,"fork"出多個子線程
  3. 并行區域內,所有線程協同工作
  4. 并行區域結束時,"join"所有子線程,回歸單線程執行
編譯環境配置
  • GCC/Clang:需添加-fopenmp編譯選項
    g++ -fopenmp omp_demo.cpp -o omp_demo
    
  • MSVC:默認支持OpenMP,需在項目屬性中啟用(/openmp
  • 驗證支持:通過宏_OPENMP判斷編譯器是否支持
    #ifdef _OPENMP
    #include <omp.h>
    #else
    #error "編譯器不支持OpenMP"
    #endif
    

2.2 OpenMP基礎語法:編譯制導語句

2.2.1 并行區域(parallel)

最基本的并行構造,用于創建線程組:

#include <iostream>
#include <omp.h>int main() {std::cout << "主線程開始(單線程)\n";// 創建并行區域,默認線程數=CPU核心數#pragma omp parallel{// 每個線程執行該代碼塊int tid = omp_get_thread_num();  // 獲取線程IDint nthreads = omp_get_num_threads();  // 獲取總線程數std::cout << "線程" << tid << ":進入并行區域(共" << nthreads << "個線程)\n";}  // 所有線程在此同步,然后銷毀std::cout << "主線程結束(單線程)\n";return 0;
}

注意cout輸出可能混亂(多個線程同時寫入),需同步處理。

2.2.2 循環并行化(for)

#pragma omp for用于將循環迭代分配到多個線程:

// 并行求和示例
#include <vector>
#include <iostream>
#include <omp.h>int main() {const int N = 1000000;std::vector<int> data(N, 1);  // 初始化100萬個1long long sum = 0;#pragma omp parallel for reduction(+:sum)  // reduction自動處理sum的并行累加for (int i = 0; i < N; ++i) {sum += data[i];  // 多個線程同時累加,reduction保證結果正確}std::cout << "總和:" << sum << "(預期:" << N << ")\n";return 0;
}

關鍵參數

  • reduction(op:var):對變量var執行op+/*/max等)的歸約,避免競爭
  • schedule(type, chunk):指定迭代分配方式
    • static:靜態分配(默認),適合迭代耗時均勻的場景
    • dynamic:動態分配,適合迭代耗時不均的場景
    • guided:引導式分配,初始塊大,逐漸減小
// 動態調度示例(每個線程處理100個迭代后再請求新任務)
#pragma omp parallel for schedule(dynamic, 100)
for (int i = 0; i < N; ++i) {process(data[i]);  // 假設process耗時不均
}
2.2.3 任務劃分(sections)

sections用于將不同任務分配給不同線程(非循環類并行):

// 多任務并行示例
#include <iostream>
#include <omp.h>void task1() { std::cout << "任務1執行\n"; }
void task2() { std::cout << "任務2執行\n"; }
void task3() { std::cout << "任務3執行\n"; }int main() {#pragma omp parallel sections{#pragma omp sectiontask1();#pragma omp sectiontask2();#pragma omp sectiontask3();}return 0;
}
2.2.4 同步機制

并行執行中需通過同步機制避免數據競爭:

  1. 臨界區(critical):保證代碼塊同一時間只有一個線程執行

    int counter = 0;
    #pragma omp parallel for
    for (int i = 0; i < 1000; ++i) {#pragma omp critical{counter++;  // 臨界區保護共享變量}
    }
    
  2. 原子操作(atomic):比critical更輕量,僅保護單一操作

    #pragma omp atomic
    counter++;  // 原子自增,比critical高效
    
  3. 屏障(barrier):等待所有線程到達屏障點后再繼續

    #pragma omp parallel
    {// 階段1:各自計算local_compute();#pragma omp barrier  // 等待所有線程完成階段1// 階段2:使用階段1的結果global_compute();
    }
    
  4. 線程私有變量(private):為每個線程創建獨立變量副本

    int x = 0;  // 主線程變量
    #pragma omp parallel private(x)  // 每個線程有自己的x,初始值不確定
    {x = omp_get_thread_num();  // 線程私有x賦值std::cout << "線程" << omp_get_thread_num() << "的x:" << x << "\n";
    }
    std::cout << "主線程的x:" << x << "\n";  // 仍為0
    

    相關變量類型:

    • private:線程私有,初始值未定義
    • firstprivate:繼承主線程初始值的私有變量
    • lastprivate:將最后一個迭代的私有變量值傳回主線程
    • shared:線程間共享(默認)

2.3 OpenMP實戰:并行矩陣乘法

矩陣乘法是典型的計算密集型任務,適合用OpenMP并行化:

#include <vector>
#include <iostream>
#include <omp.h>
#include <chrono>// 矩陣乘法:C = A * B
void matrix_multiply(const std::vector<std::vector<double>>& A,const std::vector<std::vector<double>>& B,std::vector<std::vector<double>>& C) {int n = A.size();int m = B[0].size();int k = B.size();// 并行化外層循環(行)#pragma omp parallel for collapse(2)  // collapse(2)將兩層循環合并并行for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {double sum = 0.0;for (int l = 0; l < k; ++l) {sum += A[i][l] * B[l][j];}C[i][j] = sum;}}
}int main() {const int size = 1000;  // 1000x1000矩陣std::vector<std::vector<double>> A(size, std::vector<double>(size, 1.0));std::vector<std::vector<double>> B(size, std::vector<double>(size, 1.0));std::vector<std::vector<double>> C(size, std::vector<double>(size, 0.0));// 并行計算并計時auto start = std::chrono::high_resolution_clock::now();matrix_multiply(A, B, C);auto end = std::chrono::high_resolution_clock::now();std::cout << "矩陣乘法耗時:"<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()<< " ms\n";std::cout << "驗證結果(應為" << size << "):" << C[0][0] << "\n";return 0;
}

優化技巧

  • 利用collapse(2)合并兩層循環,提高并行粒度
  • 調整矩陣存儲順序(如行主序轉列主序),優化緩存利用率
  • 設置OMP_NUM_THREADS環境變量控制線程數(通常設為CPU核心數)
    export OMP_NUM_THREADS=8  # 限制為8線程
    

三、MPI:分布式內存集群并行

MPI(Message Passing Interface)是分布式內存系統的標準并行編程接口,通過顯式消息傳遞實現跨節點通信,支持從多節點集群到超級計算機的大規模并行。

3.1 MPI核心原理與環境配置

核心原理

MPI采用分布式內存模型

  • 每個進程有獨立內存空間,不能直接訪問其他進程的內存
  • 進程間通過消息傳遞交換數據(發送MPI_Send/接收MPI_Recv
  • 進程通過唯一ID(rank)標識,通過通信子(communicator)組織
環境配置
  • MPI實現:主流實現包括OpenMPI、MPICH、Intel MPI
  • 安裝(Ubuntu)
    sudo apt-get install openmpi-bin openmpi-common libopenmpi-dev
    
  • 編譯命令
    mpic++ mpi_demo.cpp -o mpi_demo  # 自動鏈接MPI庫
    
  • 運行命令
    mpirun -np 4 ./mpi_demo  # 啟動4個進程運行程序
    

3.2 MPI基礎函數與核心概念

3.2.1 初始化與結束

所有MPI程序必須以初始化開始,以結束函數收尾:

#include <mpi.h>
#include <iostream>int main(int argc, char**argv) {// 初始化MPI環境MPI_Init(&argc, &argv);// 獲取進程總數int world_size;MPI_Comm_size(MPI_COMM_WORLD, &world_size);// 獲取當前進程ID(rank)int world_rank;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);std::cout << "Hello from rank " << world_rank << " of " << world_size << "\n";// 結束MPI環境MPI_Finalize();return 0;
}

核心概念:

  • MPI_COMM_WORLD:默認通信子,包含所有進程
  • rank:進程唯一標識(0到world_size-1
  • MPI_Init:解析命令行參數,初始化通信環境
  • MPI_Finalize:釋放MPI資源,所有進程必須調用
3.2.2 點對點通信(Point-to-Point)

進程間直接發送/接收消息的基礎通信方式:

// 簡單消息傳遞示例:rank 0向rank 1發送數據
#include <mpi.h>
#include <iostream>int main(int argc, char**argv) {MPI_Init(&argc, &argv);int world_rank;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);int world_size;MPI_Comm_size(MPI_COMM_WORLD, &world_size);// 確保至少有2個進程if (world_size < 2) {if (world_rank == 0) {std::cerr << "至少需要2個進程!\n";}MPI_Finalize();return 1;}const int data_size = 10;int data[data_size];if (world_rank == 0) {// 初始化數據for (int i = 0; i < data_size; ++i) {data[i] = i;}// 向rank 1發送數據:緩沖區、大小、類型、目標rank、標簽、通信子MPI_Send(data, data_size, MPI_INT, 1, 0, MPI_COMM_WORLD);std::cout << "rank 0發送了數據\n";} else if (world_rank == 1) {// 接收rank 0的數據:緩沖區、大小、類型、源rank、標簽、通信子、狀態MPI_Status status;MPI_Recv(data, data_size, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);// 從狀態中獲取實際接收的大小int recv_size;MPI_Get_count(&status, MPI_INT, &recv_size);std::cout << "rank 1接收了" << recv_size << "個數據:";for (int i = 0; i < recv_size; ++i) {std::cout << data[i] << " ";}std::cout << "\n";}MPI_Finalize();return 0;
}

關鍵參數

  • MPI_Comm:通信子(MPI_COMM_WORLD為默認全局通信子)
  • tag:消息標簽(用于區分同一對進程的不同消息)
  • MPI_Status:接收狀態,包含實際接收大小、源進程、標簽等信息
3.2.3 集合通信(Collective Communication)

同時涉及通信子中所有進程的通信操作,比點對點通信更高效:

  1. 廣播(Broadcast):從一個進程向所有其他進程發送數據

    // rank 0廣播數據到所有進程
    if (world_rank == 0) {// 準備數據
    }
    MPI_Bcast(data, size, MPI_INT, 0, MPI_COMM_WORLD);  // 從rank 0廣播
    
  2. 歸約(Reduction):將所有進程的局部數據合并為全局結果

    // 每個進程計算局部和,最終在rank 0得到全局和
    int local_sum = ...;
    int global_sum;
    MPI_Reduce(&local_sum, &global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
    

    常用歸約操作:MPI_SUM(求和)、MPI_PROD(求積)、MPI_MAX(最大值)等。

  3. 散射(Scatter):將一個進程的數組分散到所有進程

    // rank 0有一個大數組,分散到每個進程
    int sendbuf[100];  // rank 0的數組
    int recvbuf[10];   // 每個進程接收10個元素
    MPI_Scatter(sendbuf, 10, MPI_INT, recvbuf, 10, MPI_INT, 0, MPI_COMM_WORLD);
    
  4. 聚集(Gather):將所有進程的數據收集到一個進程

    // 每個進程的局部結果聚集到rank 0
    int sendbuf[10];   // 每個進程的10個元素
    int recvbuf[100];  // rank 0接收的總數組
    MPI_Gather(sendbuf, 10, MPI_INT, recvbuf, 10, MPI_INT, 0, MPI_COMM_WORLD);
    

3.3 MPI實戰:分布式素數篩選

用MPI實現并行埃拉托斯特尼篩法,篩選指定范圍內的素數:

#include <mpi.h>
#include <vector>
#include <iostream>
#include <cmath>// 局部篩選函數:找出[start, end)中的素數
std::vector<int> local_sieve(int start, int end, const std::vector<int>& small_primes) {std::vector<bool> is_prime(end - start, true);int offset = start;// 0對應start,1對應start+1,依此類推for (int p : small_primes) {// 計算p的第一個倍數 >= startint first = std::max(p * p, (start + p - 1) / p * p);for (int i = first; i < end; i += p) {is_prime[i - offset] = false;}}// 收集素數std::vector<int> primes;for (int i = 0; i < is_prime.size(); ++i) {if (is_prime[i] && (offset + i) > 1) {primes.push_back(offset + i);}}return primes;
}int main(int argc, char**argv) {MPI_Init(&argc, &argv);int world_rank, world_size;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);MPI_Comm_size(MPI_COMM_WORLD, &world_size);const int n = 1000000;  // 篩選1到n之間的素數const int sqrt_n = std::sqrt(n);std::vector<int> small_primes;std::vector<int> all_primes;if (world_rank == 0) {// 主進程計算小素數(<=sqrt(n))std::vector<bool> sieve(sqrt_n + 1, true);sieve[0] = sieve[1] = false;for (int i = 2; i <= sqrt(n); ++i) {if (sieve[i]) {small_primes.push_back(i);for (int j = i * i; j <= sqrt(n); j += i) {sieve[j] = false;}}}all_primes = small_primes;  // 先加入小素數}// 廣播小素數到所有進程int small_size = small_primes.size();MPI_Bcast(&small_size, 1, MPI_INT, 0, MPI_COMM_WORLD);if (world_rank != 0) {small_primes.resize(small_size);}MPI_Bcast(small_primes.data(), small_size, MPI_INT, 0, MPI_COMM_WORLD);// 劃分區間:每個進程處理一部分int chunk_size = (n - sqrt_n) / world_size;int start = sqrt_n + 1 + world_rank * chunk_size;int end = (world_rank == world_size - 1) ? n + 1 : start + chunk_size;// 局部篩選std::vector<int> local_primes = local_sieve(start, end, small_primes);// 收集各進程的素數到主進程if (world_rank == 0) {// 先加入主進程自己的局部素數all_primes.insert(all_primes.end(), local_primes.begin(), local_primes.end());// 接收其他進程的素數for (int rank = 1; rank < world_size; ++rank) {int size;MPI_Recv(&size, 1, MPI_INT, rank, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);std::vector<int> recv_primes(size);MPI_Recv(recv_primes.data(), size, MPI_INT, rank, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);all_primes.insert(all_primes.end(), recv_primes.begin(), recv_primes.end());}std::cout << "1到" << n << "之間共有" << all_primes.size() << "個素數\n";// 可選:排序并輸出部分素數} else {// 發送局部素數大小和數據到主進程int size = local_primes.size();MPI_Send(&size, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);MPI_Send(local_primes.data(), size, MPI_INT, 0, 1, MPI_COMM_WORLD);}MPI_Finalize();return 0;
}

優化技巧

  • 合理劃分數據區間,保證負載均衡
  • 先篩選小素數(<=sqrt(n)),再用小素數標記大區間的合數
  • 集合通信(如MPI_Bcast)比多次點對點通信更高效

四、OpenMP與MPI的混合編程

在大規模并行系統中,單一模型往往無法滿足需求。混合編程(OpenMP+MPI)結合兩者優勢:節點內用OpenMP利用多核,節點間用MPI通信,是當前高性能計算的主流方案。

4.1 混合編程的核心架構

混合編程模型架構:

  1. 啟動多個MPI進程(通常一個節點一個進程)
  2. 每個MPI進程內部通過OpenMP創建多個線程,利用節點內多核
  3. 節點間通過MPI消息傳遞通信,節點內通過共享內存通信

4.2 混合編程實戰:并行積分計算

以數值積分(計算π值)為例,展示混合編程模式:

#include <mpi.h>
#include <omp.h>
#include <iostream>
#include <cmath>// 被積函數:f(x) = 4/(1+x2),積分從0到1的結果為π
double f(double x) {return 4.0 / (1.0 + x * x);
}// 并行積分計算
double parallel_integrate(int n, int world_rank, int world_size) {double h = 1.0 / n;  // 步長int local_n = n / world_size;  // 每個MPI進程的子區間數double local_sum = 0.0;// 每個MPI進程內部用OpenMP并行化#pragma omp parallel reduction(+:local_sum){// 獲取線程ID和總線程數int thread_num = omp_get_thread_num();int num_threads = omp_get_num_threads();int thread_local_n = local_n / num_threads;// 計算線程負責的區間int start = world_rank * local_n + thread_num * thread_local_n;int end = (thread_num == num_threads - 1) ? (world_rank + 1) * local_n : start + thread_local_n;// 梯形法積分for (int i = start; i < end; ++i) {double x = h * (i + 0.5);  // 中點local_sum += f(x);}}// 所有MPI進程的局部和歸約到全局和double global_sum;MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);// 乘以步長得到積分結果return global_sum * h;
}int main(int argc, char**argv) {MPI_Init(&argc, &argv);int world_rank, world_size;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);MPI_Comm_size(MPI_COMM_WORLD, &world_size);// 設置OpenMP線程數(每個MPI進程的線程數)omp_set_num_threads(4);  // 假設每個節點4核const int n = 100000000;  // 總步數(越大精度越高)double pi = parallel_integrate(n, world_rank, world_size);if (world_rank == 0) {std::cout << "積分結果:" << pi << "\n";std::cout << "誤差:" << std::fabs(pi - M_PI) << "\n";}MPI_Finalize();return 0;
}

編譯與運行

mpic++ -fopenmp hybrid_pi.cpp -o hybrid_pi  # 混合編譯
mpirun -np 2 ./hybrid_pi  # 啟動2個MPI進程,每個進程4線程(共8核)

優勢

  • 節點內共享內存通信避免了消息傳遞開銷
  • 節點間MPI通信支持大規模擴展
  • 資源利用率更高(同時利用多核與多節點)

五、兩種模型的對比與最佳實踐

5.1 技術選型指南

場景推薦技術理由
單節點多核CPUOpenMP編程簡單,無通信開銷
多節點集群MPI支持分布式內存,擴展性好
大規模集群(>100節點)MPI+OpenMP混合兼顧節點內效率與跨節點擴展性
快速原型開發OpenMP增量并行化,開發效率高
實時性要求高的應用OpenMP線程通信延遲低
數據密集型分布式應用MPI適合處理跨節點分布的大規模數據

5.2 性能優化通用原則

  1. 最小化通信:通信是并行程序的主要開銷來源,盡量減少數據交互

    • OpenMP:減少共享變量訪問(用私有變量)
    • MPI:批量傳輸數據(減少消息數量),利用集合通信
  2. 負載均衡:確保每個計算單元(線程/進程)的工作量相近

    • OpenMP:用dynamic調度處理不均任務
    • MPI:根據節點性能分配不同負載(如強節點多分配任務)
  3. 內存局部性:提高數據在緩存中的命中率

    • 按緩存行大小對齊數據
    • 優化訪問模式(如行優先遍歷)
  4. 避免過度并行:并行粒度太小會導致調度/通信開銷超過收益

    • OpenMP:避免循環迭代次數過少的并行
    • MPI:進程數不宜遠大于節點數×核心數

5.3 常見問題與解決方案

問題OpenMP解決方案MPI解決方案
數據競爭使用critical/atomic/reduction避免共享數據,通過消息同步
負載不均schedule(dynamic)動態調度動態任務分配(如主從模式)
通信開銷大增加私有計算比例合并消息,使用非阻塞通信(MPI_Isend
擴展性受限增加節點數(需結合MPI)優化通信模式,減少全局同步
調試困難禁用并行(OMP_NUM_THREADS=1)單步調試單進程調試,逐步增加進程數

六、總結與未來展望

并行計算是高性能應用的核心技術,OpenMP與MPI分別在共享內存與分布式內存領域發揮著不可替代的作用:

  • OpenMP:以簡單易用的編譯制導語句實現多核并行,適合快速將串行程序并行化,是單節點性能優化的首選。
  • MPI:通過消息傳遞支持跨節點集群并行,擴展性強,是大規模科學計算與分布式應用的標準方案。
  • 混合模型:結合兩者優勢,在現代集群系統中提供最佳性能與擴展性。

未來,隨著異構計算(CPU+GPU+FPGA)的普及,并行編程模型將進一步發展,如OpenMP對GPU的支持(target指令)、MPI與CUDA的混合編程等。掌握OpenMP與MPI的核心思想,將為適應未來并行計算架構奠定堅實基礎。

并行計算的核心不僅是技術的應用,更是將問題分解為并行子任務的思維方式。通過不斷實踐與優化,開發者可以充分釋放硬件潛力,構建高效的并行應用。

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

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

相關文章

TCP 動態選路協議全面研究:OSPF、BGP 與 IS-IS 的比較與應用分析

一、引言&#xff1a;動態選路協議概述 在現代計算機網絡中&#xff0c;路由選擇是數據傳輸的核心功能&#xff0c;它決定了數據包從源到目的地的路徑選擇。隨著網絡規模的不斷擴大和復雜性的增加&#xff0c;靜態路由已經無法滿足網絡動態變化的需求&#xff0c;動態路由協議…

OpenCV 圖像哈希類cv::img_hash::AverageHash

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::img_hash::AverageHash是OpenCV中用于圖像哈希&#xff08;Image Hashing&#xff09;的一個類&#xff0c;屬于opencv_img_hash模塊。它實現了…

【Python-網絡爬蟲】爬蟲的基礎概念介紹

目錄 一、爬蟲的介紹 1.1 爬蟲的概念 1.2 爬蟲的作用 1. 搜索引擎數據索引 2. 商業數據采集與分析 3. 輿情監控與社交分析 4. 學術研究與數據挖掘 5. 信息聚合與服務優化 二、爬蟲的分類 三、爬蟲的基本流程 3.1 基本流程 3.2 Robots協議 一、爬蟲的介紹 1.1 爬蟲的…

力扣-31.下一個排列

題目鏈接 31.下一個排列 class Solution {public void nextPermutation(int[] nums) {//1.從右往左找第一個非逆序的數aint left nums.length - 2; //這里是為了找不到順序對的時候正好停在-1while (left > 0 && nums[left] > nums[left 1]) { //一定要取等號…

Python爬蟲實戰:研究python-nameparser庫相關技術

1. 引言 在當今數字化時代,姓名作為個人身份的重要標識,在許多領域都有著廣泛的應用需求。例如,在客戶關系管理系統中,準確解析姓名可以幫助企業更好地了解客戶背景;在學術研究中,分析作者姓名分布有助于發現研究團隊的地域特征;在社交網絡分析中,姓名信息可以輔助進行…

Android中MVI架構詳解

博主前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住也分享一下給大家 ??點擊跳轉到教程 什么是 MVI 架構? MVI (Model-View-Intent) 是一種單向數據流的架構模式,它源于響應式編程思想。在 MVI 中: 架構圖: 1、Model: 代表的是UI 狀態,它包含了…

AutoGen-AgentChat-3-人機交互

import os from dotenv import load_dotenvload_dotenv()True人機交互 在上一節“團隊”中&#xff0c;我們了解了如何創建、觀察和控制代理團隊。本節將重點介紹如何在應用程序中與團隊進行交互&#xff0c;并向團隊提供人工反饋。 您可以通過兩種主要方式從您的應用程序與團隊…

Flink Db2 CDC 環境配置與驗證

一、DB2 數據庫核心配置 1. 啟用數據庫日志記錄與CDC支持 -- 以DB2管理員身份連接數據庫 CONNECT TO mydb USER db2inst1 USING password;-- 啟用數據庫歸檔日志模式&#xff08;CDC依賴&#xff09; UPDATE DATABASE CONFIGURATION USING LOGARCHMETH1 DISK:/db2log/archive…

初識單例模式

文章目錄場景通點定義實現思路六種 Java 實現餓漢式懶漢式synchronized 方法雙重檢查鎖 Double Check Lock Volatile靜態內部類 Singleton Holder枚舉單例單例運用場景破解單例模式參考場景通點 資源昂貴&#xff1a;數據庫連接池、線程池、日志組件&#xff0c;只需要一份全…

音樂搶單源碼(連單卡單/疊加組規則/打針/多語言)

簡介&#xff1a; 測試環境&#xff1a;Nginx、PHP7.2、MySQL5.6&#xff0c;運行目錄設置為public&#xff0c;偽靜態thinkphp&#xff0c;建議開啟SSL 測試語言&#xff1a;11種 不知道誰給我的一套&#xff0c;說是買來的&#xff0c;我看了一下功能感覺也一般&#…

分類樹查詢性能優化:從 2 秒到 0.1 秒的技術蛻變之路

在電商系統中&#xff0c;分類樹查詢是一個基礎且高頻的功能&#xff0c;然而這個看似簡單的功能背后卻隱藏著不小的性能挑戰。本文將分享我們在實際項目中對分類樹查詢功能進行五次優化的全過程&#xff0c;看如何將查詢耗時從 2 秒縮短至 0.1 秒&#xff0c;為用戶提供更流暢…

Ansible 介紹及安裝

簡介 Ansible 是一款開源的自動化工具&#xff0c;廣泛應用于配置管理、應用部署、任務自動化以及多節點管理等領域。它由 Michael DeHaan 于 2012 年創建&#xff0c;ansible 目前已經已經被紅帽官方收購&#xff0c;是自動化運維工具中大家認可度最高的&#xff0c;并且上手…

超光譜相機的原理和應用場景

超光譜相機是光譜成像技術的尖端形態&#xff0c;具備亞納米級光譜分辨率與超千波段連續覆蓋能力&#xff0c;通過“圖譜合一”的三維數據立方體實現物質的精準識別與分析。其核心技術架構、應用場景及發展趨勢如下&#xff1a;一、核心技術原理1、?分光機制??干涉分光?&am…

掌握MySQL函數:高效數據處理指南

? 在 MySQL 數據庫管理系統中&#xff0c;函數扮演著極為重要的角色。它們就像是數據庫操作的得力助手&#xff0c;能夠幫助開發者高效地完成各種數據處理任務。本文將深入探討 MySQL 函數的方方面面&#xff0c;從其基本概念到實際應用&#xff0c;幫助讀者全面掌握這一強大的…

10.SpringBoot的統一異常處理詳解

文章目錄1. 異常處理基礎概念1.1 什么是異常處理1.2 為什么需要統一異常處理1.3 Spring異常處理機制2. SpringBoot默認異常處理2.1 默認錯誤頁面2.2 自定義錯誤頁面3. 全局異常處理器3.1 基礎全局異常處理器3.2 統一響應格式3.3 使用統一響應格式的異常處理器4. 自定義異常4.1 …

No Hack No CTF 2025Web部分個人WP

No Hack No CTF 2025 Next Song is 春日影 hint&#xff1a;NextJS Vulnerability at /adminCVE-2025-29927Next.js 中間件權限繞過漏洞 訪問admin路由發現跳轉利用CVE&#xff1a; curl -i \-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:m…

STM32第十八天 ESP8266-01S和電腦實現串口通信

一&#xff1a; ESP和電腦實現串口通信1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 響應 : OK 2. 連接路路由器? ATCWJAP"SSID","password" // SSID and password of router 響應 : OK 3. 查詢 ESP8266 設備的 IP 地址 ATCIFSR 響應 : CIFSR:APIP…

STM32第十七天ESP8266-01Swifi模塊

ESP8266-01S wifi模塊1&#xff1a;ESP8266是實現wifi通訊的一個模塊種類&#xff0c;有很多分類包含esp8266-12、esp8266-12E、ESP8266-01S、esp32等等。esp8266-01S由一顆esp8266作為主控再由一塊flash作為存儲芯片組成&#xff0c;帶有板載芯片供電采用3.3V電壓使用串口進行…

ProCCD復古相機:捕捉復古瞬間

在數字攝影盛行的今天&#xff0c;復古膠片相機的獨特質感和懷舊風格依然吸引著眾多攝影愛好者。ProCCD復古相機APP正是這樣一款能夠滿足用戶對復古攝影需求的應用程序。它通過模擬復古CCD數碼相機的效果&#xff0c;讓用戶在手機上也能輕松拍出具有千禧年風格的照片和視頻。無…