Win API Многопоточность: ReleaseSemaphore не изменяет счётчик

Я хочу создать массив массивов (std::vector<std::vector<uint32_t>> arrays), каждый из них заполнить числами. Также должен быть массив Win API потоков (std::vector<HANDLE> threads). Потоков должно быть вдвое больше, чем массивов (в коде ниже массивов 2, а потоков 4). Мне нужно использовать семафоры для синхронизации. Что я хочу реализовать: сначала сортируются 2 массива, но создаются сразу 4 потока, 3 и 4 потоки останавливаются перед функцией WaitForSingleObject(hSemaphore, INFINITE), потому что значение счётчика обнуляется (создаю семафор так: CreateSemaphoreW(nullptr, arrays.size(), threads.size(), nullptr)). Я помещаю эту функцию в отдельный поток manage_thread по следующей причине (установлено экспериментально): в функцию sort_arr поток, сортирующий 3-й массив, может "попасть" раньше потока, сортирующего 1-й, в то время как в arrays всего 2 массива. Пока 3 и 4 потоки ждут, главный поток вставляет 3 и 4 массивы, после чего освобождает hSemaphore, увеличивая счётчик и пропуская потоки. 3 и 4 потоки сортируют 3 и 4 массивы. Но что получается у меня: программа не завершается, останавливаясь в цикле функции manage_threads при i = 2. Я подозреваю, что неправильно использую ReleaseSemaphore(hSemaphore, arrays.size(), nullptr), но, к сожалению, не могу самостоятельно найти ошибку :(

#include <Windows.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

std::vector<std::vector<uint32_t>> arrays(2);
HANDLE hSemaphore;
std::vector<HANDLE> threads(arrays.size() * 2);

DWORD WINAPI sort_arr(LPVOID lpParam) {
    
    auto i = reinterpret_cast<size_t>(lpParam);
    decltype(auto) arr = arrays[i];
    std::ranges::sort(arr);

    return EXIT_SUCCESS;
}

DWORD WINAPI manage_threads(LPVOID) {
    for (size_t i = 0; i < threads.size(); ++i) {
        WaitForSingleObject(hSemaphore, INFINITE);
        threads[i] = CreateThread(nullptr, 0, sort_arr, (LPVOID)(i), 0, nullptr);
    }
    WaitForMultipleObjects(threads.size(), threads.data(), true, INFINITE);
    return EXIT_SUCCESS;
}

int main() {

    const auto max = 10u;
    const auto range = std::views::iota(1u, max + 1) | std::views::reverse;
    const auto print_arr = [](const auto& arr) { for (auto i : arr) std::cout << i << ' '; std::cout << '\n'; };

    for (auto& arr : arrays)
        std::ranges::copy(range.begin(), range.end(), std::back_inserter(arr));

    hSemaphore = CreateSemaphoreW(nullptr, arrays.size(), threads.size(), nullptr);
    auto manage_thread = CreateThread(nullptr, 0, manage_threads, nullptr, 0, nullptr);

    for (size_t i = arrays.size(); i < threads.size(); ++i) {
        arrays.emplace_back();
        std::ranges::copy(range.begin(), range.end(), std::back_inserter(arrays[i]));
    }

    ReleaseSemaphore(hSemaphore, arrays.size(), nullptr);

    WaitForSingleObject(manage_thread, INFINITE);

    std::ranges::for_each(arrays, print_arr);
}

Ответы (1 шт):

Автор решения: Fluffle Puff

Спасибо всем, кто обратил внимание на мой вопрос! Вот исправленный код:

#include <Windows.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

std::vector<std::vector<uint32_t>> arrays(10);
HANDLE hSemaphore;
std::vector<HANDLE> threads(arrays.size() * 2);

DWORD WINAPI sort_arr(LPVOID lpParam) {
    
    auto i = reinterpret_cast<size_t>(lpParam);
    decltype(auto) arr = arrays[i];
    std::ranges::sort(arr);

    return EXIT_SUCCESS;
}

DWORD WINAPI manage_threads(LPVOID) {
    for (size_t i = 0; i < threads.size(); ++i) {
        WaitForSingleObject(hSemaphore, INFINITE);
        threads[i] = CreateThread(nullptr, 0, sort_arr, (LPVOID)(i), 0, nullptr);
    }
    WaitForMultipleObjects(threads.size(), threads.data(), true, INFINITE);
    return EXIT_SUCCESS;
}

int main() {

    const auto max = 10u;
    const auto range = std::views::iota(1u, max + 1) | std::views::reverse;
    const auto print_arr = [](const auto& arr) { for (auto i : arr) std::cout << i << ' '; std::cout << '\n'; };

    for (auto& arr : arrays)
        std::ranges::copy(range.begin(), range.end(), std::back_inserter(arr));

    hSemaphore = CreateSemaphoreW(nullptr, arrays.size(), threads.size(), nullptr);
    auto manage_thread = CreateThread(nullptr, 0, manage_threads, nullptr, 0, nullptr);

    for (size_t i = arrays.size(); i < threads.size(); ++i) {
        arrays.emplace_back();
        std::ranges::copy(range.begin(), range.end(), std::back_inserter(arrays[i]));
    }

    ReleaseSemaphore(hSemaphore, arrays.size() / 2, nullptr);

    WaitForSingleObject(manage_thread, INFINITE);

    std::ranges::for_each(arrays, print_arr);
}

Проблема была действительно в вызове функции ReleaseSemaphore. До исправлений она увеличивала счётчик на array.size(), после исправлений стала увеличивать счётчик на array.size() / 2. Я пока ещё не понимаю, каким образом организована работа со счётчиком внутри семафоров, поэтому не могу представить, почему предыдущий вариант не сработал. Как это выглядит в моей голове: счётчик остановлен на 0, после ReleaseSemaphore(hSemaphore, arrays.size(), nullptr) он равен 4, остальные потоки спокойно проходят. Но действительность отличается...

→ Ссылка