簡體   English   中英

使用給定數量的線程查找兩個數字之間范圍內的所有素數

[英]Find all primes in a range between two numbers using a given amount of threads

問題是使用給定數量的線程來計算兩個數之間的所有素數。 線程應該做的工作量是均勻的。

我對 C++ 和線程都很陌生,我在讓線程首先找到素數,其次是做偶數工作時遇到了一些問題。

當我只用一個線程運行 find_prime function 時,它正在工作,但是在使用多個線程時,我得到了堆緩沖區溢出或我不太明白的東西。 我的方法可能是非常錯誤的,但我的想法是每個線程都應該同時開始搜索素數,如果他們找到了,他們應該檢查另一個線程是否已經找到它。 如果不是,則將素數添加到素數向量中。

錯誤消息很長,但摘要說:

SUMMARY: AddressSanitizer: heap-buffer-overflow (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x43c58) in __asan_memcpy
Shadow bytes around the buggy address:

  0x1c04000003b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000003c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000003d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000003e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000003f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x1c0400000400:[fa]fa 04 fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000410: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000420: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000430: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000440: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000450: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
#include <iostream>
#include <thread>
#include <vector>

using namespace std;

void find_prime(int low, int high, vector<int> &primes) {
  bool is_prime = true;
  int i, j;

  for (i = low; i <= high; i++) {
    if (i == 1 || i == 0)
      continue;

    is_prime = true;

    for (j = 2; j <= i / 2; ++j) {
      if (i % j == 0) {
        is_prime = false;
        break;
      }
    }

    if (is_prime && (std::find(primes.begin(), primes.end(), i) != primes.end()))
      continue;

    if (is_prime) {
      primes.push_back(i);
    }
  }
}

void print(std::vector<int> const &primes) {
  for (std::vector<int>::size_type i = 0; i < primes.size(); i++) {
    std::cout << primes.at(i) << ' ';
  }
}

int main() {

  int low = 0, high = 100, num_threads = 4, i;

  std::vector<int> primes;
  std::vector<thread> threads;

  for (i = 0; i < num_threads; i++) {
    std::thread t([&](int low, int high) {
      find_prime(low, high, primes);
    },
                  low, high);
    threads.push_back(std::move(t));
  }

  for (auto &thread : threads)
    thread.join();

  print(primes);
  cout << "\nSize: " << primes.size();
}

您在示例中所做的基本上是對所有線程進行相同的工作,這不是很有效:)

根據您的問題,我假設您不是在這里尋找最有效的解決方案,而是想熟悉 c++ 中的多線程。 我推薦的一般方法是將原始范圍拆分為專有范圍(例如,對於 2 個線程,將范圍 [0, 99] 拆分為 [0, 49] 和 [50, 99]),在每個范圍內找到素數在不同的線程上,將結果返回到主線程並在那里組合結果。

為了做到這一點,我建議熟悉futurespromises ,以及能夠從衍生線程返回一些東西。 這是我剛剛發現的一個站點,它解釋了它們以及 c++ 中的其他多線程概念。

上面的代碼有幾個問題。

  • 它計算范圍內所有素數的 4 倍。 所以,它比沒有線程慢得多
  • 你甚至注意到了這一點並放了一個
if (is_prime && (std::find(primes.begin(), primes.end(), i) != primes.end()))
      continue;

在您的代碼中,檢查是否已經計算了一個數字。 那么你不會存儲它。 這使它變得更慢。

  • 您將 4 步並行寫入std::vector 寫入std::vector不是線程安全的。 您需要添加std::mutex es 或其他同步機制來防止該問題。
  • 這很可能也是墜機的原因
  • 您需要將范圍(例如 0..100)分成相等的部分,例如 0..25、26..50、51..75、76..100。 而這你將給你的 4 個線程。 然后,每個線程只計算一個塊。
  • 然后,您應該使用std::promisestd::future與您的線程交換數據。
  • 您應該對索引使用無符號值

現在稍微解釋一下相等范圍的計算。 將一個范圍分成相等的部分很容易。 我們計算低和高之間的增量,然后將 integer 除以所需組的數量。 這可以在有或沒有余數的情況下工作。 我們可以通過模除法得到余數。

然后我們會得到類似的東西

    const size_t delta = high+1 - low;
    const size_t chunk = delta / numberOfGroups;
    size_t remainder = delta % numberOfGroups;

請注意:余數當然總是小於組數。 例如,如果您對 4 進行模除,則余數只能是 0,1,2,3。 再也不會了。 如果我們有剩余,那么我們將其平均分配給塊。 因此,我們可以編寫一個非常簡單的 function,如下所示:

// Calculate start and end index for all chunks
std::vector<std::pair<size_t, size_t>> calculatePairs(const size_t low, const size_t high, const size_t numberOfGroups) {

    // Here we will store the resulting pairs with start and end values
    std::vector<std::pair<size_t, size_t>> pairs{};

    // Calculate chung size and remainder
    const size_t delta = high+1 - low;
    const size_t chunk = delta / numberOfGroups;
    size_t remainder = delta % numberOfGroups;

    // Calculate the chunks start and end addresses for all chunks
    size_t startIndex{}, endIndex{};
    for (size_t i = 0; i < numberOfGroups; ++i) {

        // Calculate end address and distribute remainder equally
        endIndex = startIndex + chunk + (remainder ? 1 : 0) - 1;
        // Store a new pair of start and end indices
        pairs.emplace_back(startIndex, endIndex);
        // Nexct start index
        startIndex = endIndex + 1;
        // We now consumed 1 remainder
        if (remainder) --remainder;
    }
    return pairs;
}

這將為每個請求的組計算帶有開始結束索引的std::pair 在行

endIndex = startIndex + chunk + (remainder ? 1 : 0) - 1;

我們進行余數的分配。 如果有余數,則將當前結束索引加 1。 然后我們從余數中減去 1,因為我們只是從中分配了 1。

好的。 現在我們有了這些對。 這些高低指數對可以提供給我們的線程素數計算器。


下一個。 std::thread和/或主程序之間的數據交換:為此 C++ 提供所謂的std::promisestd::future 很多人很難理解這些。 請同時檢查 cppreference。

很簡單的說:

  • std::promise是一個“事物”,您 promise 表示線程將來會返回一個“值”。 您的線程將使用其 ``set``` 函數將值存儲在std::promise中。
  • 對於future ,您可以使用std::futureget function 檢索已存儲在promise中的值。 它也會等待,直到數據可用。

這非常簡化。 但也許它有助於理解機制。


請在下面查看您修改后的代碼

#include <iostream>
#include <thread>
#include <vector>
#include <future>
#include <utility>

// Calculate start and end index for all chunks
std::vector<std::pair<size_t, size_t>> calculatePairs(const size_t low, const size_t high, const size_t numberOfGroups) {

    // Here we will store the resulting pairs with start and end values
    std::vector<std::pair<size_t, size_t>> pairs{};

    // Calculate chung size and remainder
    const size_t delta = high+1 - low;
    const size_t chunk = delta / numberOfGroups;
    size_t remainder = delta % numberOfGroups;

    // Calculate the chunks start and end addresses for all chunks
    size_t startIndex{}, endIndex{};
    for (size_t i = 0; i < numberOfGroups; ++i) {

        // Calculate end address and distribute remainder equally
        endIndex = startIndex + chunk + (remainder ? 1 : 0) - 1;
        // Store a new pair of start and end indices
        pairs.emplace_back(startIndex, endIndex);
        // Nexct start index
        startIndex = endIndex + 1;
        // We now consumed 1 remainder
        if (remainder) --remainder;
    }
    return pairs;
}


void find_prime(const size_t low, const size_t high, std::promise<std::vector<int>>&& promisePrimes) {
    bool is_prime = true;

    std::vector<int> primes{};

    for (size_t i = low; i <= high; i++) {
        if (i == 1 || i == 0)
            continue;

        is_prime = true;

        for (size_t j = 2; j <= i / 2; ++j) {
            if (i % j == 0) {
                is_prime = false;
                break;
            }
        }

        if (is_prime) {
            primes.push_back(i);
        }
    }
    promisePrimes.set_value(std::move(primes));
}

void print(std::vector<int> const& primes) {
    for (std::vector<int>::size_type i = 0; i < primes.size(); i++) {
        std::cout << primes.at(i) << ' ';
    }
}

int main() {

    constexpr size_t low = 0, high = 100, num_threads = 4;

    
    std::vector<int> primes;
    std::vector<std::thread> threads;

    std::vector<std::pair<size_t, size_t>> pairs = calculatePairs(low, high, num_threads);

    std::vector<std::promise<std::vector<int>>> promisePrimes(num_threads);
    std::vector<std::shared_future<std::vector<int>>> futurePrimes(num_threads);

    for (size_t i = 0; i < num_threads; i++) {

        futurePrimes[i] = promisePrimes[i].get_future();

        std::thread t(find_prime, pairs[i].first, pairs[i].second, std::move(promisePrimes[i]));
        threads.push_back(std::move(t));
    }

    for (size_t i = 0; i < num_threads; i++) {
        std::vector<int> temp = futurePrimes[i].get();
        primes.insert(primes.end(), temp.begin(), temp.end());
        threads[i].join();
    }

    print(primes);
    std::cout << "\nSize: " << primes.size();
    
    return 0;
}

請注意:我使用std::shared_future ,因為我想將其存儲在std::vector中,並且無法復制“正常” std::future

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM