Multithreading не работает должным образом
Я пишу quicksort с использованием многопоточности. Это работает, но при больших или маленьких размерах массива почему-то работает лишь один поток.
Я использовал два контейнера std::set, чтобы сохранять ид входных потоков и тех, которые задействованы в работе, а затем использовал их для вывода. Однако, как видно из вывода консоли, многопоточность работает только с массивами размером 10.000, 100.000 и 1.000.000. Почему так происходит?
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <chrono>
#include <functional>
#include <utility>
#include <ostream>
#include <random>
#include <limits>
#include <thread>
#include <mutex>
#include <queue>
#include <set>
template < class fwdIter >
using FunctionPair = std::pair< std::string, std::function<void(fwdIter, fwdIter)> >;
template < class T, class Generator, template < class > class Distribution >
auto createRandomGeneratorFunc(T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
{
static auto distrib = Distribution<T>{ min, max };
static std::random_device rd;
static auto gen = Generator(rd());
return [&]() { return distrib(gen); };
}
template <class fwdIter>
void sortTest(fwdIter first, fwdIter last);
int main(int argc, char** argv)
{
auto randomIntFunc = createRandomGeneratorFunc<int, std::mt19937, std::uniform_int_distribution>();
std::vector<size_t> vecSizes = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
for (size_t i = 0; i < vecSizes.size(); ++i)
{
std::cout << "\nTest " << i << ". Size: " << vecSizes[i] << ":" << std::endl;
std::vector<int> arr1(vecSizes[i]);
std::generate(arr1.begin(), arr1.end(), [&randomIntFunc]() { return randomIntFunc(); });
sortTest(arr1.begin(), arr1.end());
}
return 0;
}
template < class fwdIter >
std::vector< FunctionPair<fwdIter> > getVectorOfSortFuncs()
{
FunctionPair<fwdIter> pair2(std::string("QuickSortThreads "), std::function<void(fwdIter, fwdIter)>(QuickSortThreads<fwdIter>));
std::vector< FunctionPair<fwdIter> > sortFunctions;
sortFunctions.push_back(pair2);
return sortFunctions;
}
template <class fwdIter>
void sortTest(fwdIter first, fwdIter last)
{
using std::chrono::steady_clock;
using std::chrono::duration;
auto sortFunctions = getVectorOfSortFuncs<fwdIter>();
for (const auto& funcPair : sortFunctions)
{
// Sorting
auto timeBegin = steady_clock::now();
funcPair.second(first, last);
auto timeEnd = steady_clock::now();
// Print function name and work time
std::cout << " " << funcPair.first << ": "
<< (std::is_sorted(first, last) ? "sorted" : "not sorted")
<< " | " << duration<float, std::milli>(timeEnd - timeBegin).count() << "ms" << std::endl;
}
}
template < class fwdIter >
void QuickSortThreads(fwdIter first, fwdIter last)
{
const unsigned int numberOfThreads = std::thread::hardware_concurrency();
if (numberOfThreads < 1)
{
throw std::exception("Number of thread < 1");
}
std::atomic<bool> bRun(false); // threads run
std::atomic<int> nWorkThreads = 0; // number of proccessing threads
std::mutex mut;
std::condition_variable condVarNotEmpty;
std::queue< std::pair<fwdIter, fwdIter> > queueArrayParts; // queue of parts
std::vector<std::thread> threads;
// --TEST
std::set<std::thread::id> i_threads;
std::set<std::thread::id> w_threads;
queueArrayParts.push(std::pair<fwdIter, fwdIter>(first, last));
// Thread func (sort processing)
auto process = [&i_threads, &w_threads, &mut, &queueArrayParts, &nWorkThreads, &bRun, &condVarNotEmpty]() {
// --ADDED
{
std::unique_lock<std::mutex> lock(mut);
i_threads.emplace(std::this_thread::get_id()); // --TEST
}
//
while (true)
{
// --REMOVED
//i_threads.emplace(std::this_thread::get_id()); // --TEST
//
std::unique_lock<std::mutex> lock(mut); // queue mutex
condVarNotEmpty.wait(lock, [&queueArrayParts, &bRun] { return !queueArrayParts.empty() || !bRun; }); // lock
// Terminating a thread
if (queueArrayParts.empty() && !bRun)
{
std::cout << "break\n";
break;
}
++nWorkThreads;
// Get part of the array for processing
fwdIter first = queueArrayParts.front().first;
fwdIter last = queueArrayParts.front().second;
w_threads.emplace(std::this_thread::get_id()); // --TEST
// Pop this part from the queue
queueArrayParts.pop();
// Queue unlock
lock.unlock();
// Sorting
if (first != last)
{
auto pivot = *std::next(first, std::distance(first, last) / 2);
auto middle1 = std::partition(first, last,
[pivot](const auto& em) { return em < pivot; });
auto middle2 = std::partition(middle1, last,
[pivot](const auto& em) { return !(pivot < em); });
// Add new parts and start other threads
{
std::unique_lock<std::mutex> ul(mut);
queueArrayParts.push(std::pair<fwdIter, fwdIter>(first, middle1));
queueArrayParts.push(std::pair<fwdIter, fwdIter>(middle2, last));
}
condVarNotEmpty.notify_one();
condVarNotEmpty.notify_one();
}
--nWorkThreads;
// Stop sorting
// --ADDED bRun == true
if (queueArrayParts.empty() && bRun == true && nWorkThreads == 0)
{
bRun = false;
// --ADDED
condVarNotEmpty.notify_all();
//
}
}
};
// Start threads work
// --ADDED
bRun = true;
//
for (int i = 0; i < numberOfThreads; ++i)
{
threads.push_back(std::thread(process));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
// --TEST
std::cout << " Threads:\n";
std::cout << "Input threads: "; std::for_each(i_threads.cbegin(), i_threads.end(), [](auto val) { std::cout << " " << val; }); std::cout << std::endl;
std::cout << "Work threads: "; std::for_each(w_threads.cbegin(), w_threads.end(), [](auto val) { std::cout << " " << val; }); std::cout << std::endl;
}
Вывод:
Test 0. Size: 10:
Threads:
Input threads: 3536 10752 11600 15388 15640 18104 18680 20308 20348 23496 24156 24640
Work threads: 18680
QuickSortThreads : sorted | 18.6644ms
Test 1. Size: 100:
Threads:
Input threads: 1144 7896 14352 16396 19304 19384 20320 21636 21924 22496 23688 24328
Work threads: 23688
QuickSortThreads : sorted | 11.6995ms
Test 2. Size: 1000:
Threads:
Input threads: 2228 9928 15580 16008 20028 20984 23492 24004 25120 25416 26304 26584
Work threads: 24004
QuickSortThreads : sorted | 11.1096ms
Test 3. Size: 10000:
Threads:
Input threads: 10820 17024 17208 18216 18488 18920 19216 22224 22676 23124 24380 25712
Work threads: 10820 17024 17208 18216 18488 18920 19216 22224 23124 25712
QuickSortThreads : sorted | 16.4296ms
Test 4. Size: 100000:
Threads:
Input threads: 3088 5980 6556 14548 18184 18332 18448 20284 21024 21520 25548 26164
Work threads: 3088 5980 6556 18184 18332 18448 20284 21024 21520 25548 26164
QuickSortThreads : sorted | 51.7859ms
Test 5. Size: 1000000:
Threads:
Input threads: 6984 9700 10496 13228 14612 16884 18992 20828 21228 23516 24492 24508
Work threads: 13228 21228 24492 24508
QuickSortThreads : sorted | 266.152ms
Test 6. Size: 10000000:
Threads:
Input threads: 8432 8756 8928 8992 16320 17260 21464 23236 23828 25084 25848 26532
Work threads: 16320
QuickSortThreads : sorted | 2245.86ms
Test 7. Size: 100000000:
Threads:
Input threads: 1560 2040 6472 8752 10508 18308 21780 21928 23732 25048 25152 25480
Work threads: 6472
QuickSortThreads : sorted | 22395.7ms
PS: Это только часть кода. Я удалил все остальное, оставив только нужное для запуска. Тут все работает исправно, кроме самого алгоритма сортировки. Основной алгоритм взят с cppreference (к сожалению, ссылку не смог найти, возможно страница уже удалена или изменена) и переделан под мнопоточность. Целью как раз является изучение (повторение) на практике каких-либо новых фич из последних (или не совсем) стандартов плюсов. Текущий код можно запустить только с включенной поддержкой с++ 20
Ответы (1 шт):
У вас переменная bRun, используемая во всех threads, инициализирована false и нигде не устанавливается в true