简体   繁体   English

将工作线程与主线程同步

[英]Synchronize worker threads with a main thread

how to correctly synchronize worker threads with a main thread if a worker thread can generate another tasks?如果工作线程可以生成另一个任务,如何正确地将工作线程与主线程同步? I've used std::queue to maintain tasks guarded by mutex and atomic variable to track busy threads.我使用 std::queue 来维护由互斥锁和原子变量保护的任务来跟踪繁忙的线程。 Unfortunately I'm facing deadlocks at the end of the execution.不幸的是,我在执行结束时面临僵局。

I've extracted code from my project and created a following example (you can easily compile it with g++ or MSVC):我从我的项目中提取了代码并创建了以下示例(您可以使用 g++ 或 MSVC 轻松编译它):

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
#include <functional>
#include <stack>
#include <atomic>
#include <queue>

template <class T, class Compare>
class USort {
    using Task = std::pair<T*, T*>;
private:
    size_t m_ThreadsNum;
    std::atomic<bool> m_Finished;
    std::atomic<size_t> m_Busy;
    std::thread* m_Threads;
    std::queue<Task> m_Tasks;
    size_t m_Size;
    T* m_Data;
    Compare m_Comparator;
    std::condition_variable m_WaitFinished;
    std::condition_variable m_WaitSorter;
    std::mutex m_TaskQueueMutex;

private:
    const size_t THREAD_THRESHOLD = 1024;
    const size_t THREAD_POOL_THRESHOLD = 8192;


    bool HasTask() {
        std::unique_lock<std::mutex> lock(m_TaskQueueMutex);
        return m_Tasks.size() > 0;
    }

    bool PopTask(T** L, T** R) {
        std::unique_lock<std::mutex> lock(m_TaskQueueMutex);

        if (m_Tasks.size() == 0) {
            *L = *R = nullptr;
            return false;
        }

        *L = m_Tasks.front().first;
        *R = m_Tasks.front().second;
        m_Tasks.pop();

        return true;
    }

    void PushTask(T* L, T* R) {
        std::unique_lock<std::mutex> lock(m_TaskQueueMutex);
        m_Tasks.emplace(std::pair<T*, T*>(L, R));
        m_WaitSorter.notify_one();
    }

    void SortThread(size_t Id) {
        std::mutex sorter_mutex;
        for (;;) {
            std::unique_lock<std::mutex> lock(sorter_mutex);
            ///
            ///  ----------------------------------> some threads wait here
            /// 
            m_WaitSorter.wait(lock, [this]() { return m_Finished || HasTask(); });

            if (m_Finished) break;

            m_Busy++;

            T *left, *right;
            while (PopTask(&left, &right)) {
                Sort(left, right);
            }

            if (--m_Busy == 0) {
                m_WaitFinished.notify_one();
            }
        }
    }

    // just simulate work
    void Sort(T* Left, T* Right) {
        if (Right - Left > 10) {
            PushTask(Left, Right-10);
        }
    }

    void WaitForSortingIsFinished() {
        std::mutex finished;
        std::unique_lock<std::mutex> lock(finished);
        m_WaitFinished.wait(lock, [this]() { return m_Busy == 0 && !HasTask(); });
    }

    void FinishThreads() {
        m_Finished = true;
        m_WaitSorter.notify_all();
    }

    void ReleaseThreads() {
        if (m_Threads) {
            for (size_t i = 0; i < m_ThreadsNum; i++) {
                ///
                ///  ----------------------------------> main thread stuck here
                /// 
                m_Threads[i].join();
            }
            delete[] m_Threads;
            m_Threads = nullptr;
        }
    }

public:
    USort(size_t NumberOfThreads = 0) : m_Comparator(Compare()) {
        if (NumberOfThreads == 0) {
            static const unsigned int max_concurrency = std::thread::hardware_concurrency();
            NumberOfThreads = max_concurrency;
            if (NumberOfThreads == 0) NumberOfThreads = 4;
        }

        m_Finished = false;
        m_ThreadsNum = NumberOfThreads;
        m_Threads = nullptr;
    }

    ~USort() {
        ReleaseThreads();
    }

    void Sort(T* Data, size_t Size) {
        // build thread pool
        m_Threads = new std::thread[m_ThreadsNum];
        for (size_t i = 0; i < m_ThreadsNum; i++) {
            m_Threads[i] = std::thread(&USort::SortThread, this, i);
        }

        // process data
        PushTask(Data, Data + Size - 1);
        WaitForSortingIsFinished();
        FinishThreads();
    }

};

template <class T, class Compare>
void usort(T* Data, size_t Size, size_t NumberOfThreads = 0) {
    USort<T, Compare> mt_sorter(NumberOfThreads);
    mt_sorter.Sort(Data, Size);
}


const size_t ARR_SIZE = 0x00010000;


struct comp {
    bool operator()(const int& L, const int& R) const {
        return L < R;
    }
};

int main()
{
    int* arr = new int[ARR_SIZE];
    for (int i = 0; i < ARR_SIZE; i++) {
        arr[i] = rand() % 3200000;
    }

    usort<int, comp>(arr, ARR_SIZE, 16);

    delete[] arr;

    return 0;
}

The thing is, that in my example threads aren't always finished.问题是,在我的示例中,线程并不总是完成。 From time to time some thread pending in m_WaitSorter.wait() and therefore main thread pending in m_Threads[i].join();不时有一些线程在m_WaitSorter.wait()挂起,因此主线程在m_Threads[i].join();挂起m_Threads[i].join(); . . Where is the flaw in the logic.逻辑漏洞在哪里。 Why the calling to FinishThreads() doesn't finish all threads?为什么调用FinishThreads()没有完成所有线程?

EDIT: Basically I'd like to implement multithread sorting algorithm.编辑:基本上我想实现多线程排序算法。

  1. The main thread creates thread pool, push first task(sort whole array) to a task queue and waits for sorting to be finished主线程创建线程池,将第一个任务(排序整个数组)推送到任务队列并等待排序完成
  2. The pool thread takes task, divide it to smaller tasks(1-3).池线程接受任务,将其划分为较小的任务(1-3)。 One of this task is immediatelly processed by the current pool thread, others are push to the queue此任务之一由当前池线程立即处理,其他任务被推入队列
  3. The pool thread musn't finish until the whole data set is sorted(there are no task in the queue and all pool threads are pending)池线程必须在整个数据集排序后才能完成(队列中没有任务,所有池线程都在等待)
  4. When the sorting is finished the main thread should be woken排序完成后,应唤醒主线程
  5. Main thread should finish pending threads主线程应该完成挂起的线程

So for this, from my perspective, I need two conditional_variabes with predicate "all threads are pending && has no task in queue" in main thread and "has task in queue || finish thread" in pool thread.因此,为此,从我的角度来看,我需要两个conditional_variabes,主线程中的谓词“所有线程都挂起&& 队列中没有任务”,池线程中的“队列中有任务|| 完成线程”。

OK, I've read the documentation through carefully and found a bug in my code.好的,我已经仔细阅读了文档并在我的代码中发现了一个错误。 Calls to notify_one() , notify_all() and wait() have to be controlled via the same mutext.notify_one()notify_all()wait()调用必须通过相同的互斥体进行控制。 With that in mind I've update and little bit simplified my code:考虑到这一点,我已经更新并稍微简化了我的代码:

    bool WaitAndPopTask(T** L, T** R) {
        std::unique_lock<std::mutex> lock(m_TaskQueueMutex);
        m_WaitSorter.wait(lock, [this]() { return m_Finished || !m_Tasks.empty(); });

        if (m_Finished) return false;

        m_Busy++;

        *L = m_Tasks.front().first;
        *R = m_Tasks.front().second;
        m_Tasks.pop();

        return true;
    }

    void SortThread(size_t Id) {
        for (;;) {
            T *left, *right;
            if (!WaitAndPopTask(&left, &right)) break;

            Sort(left, right);

            std::lock_guard<std::mutex> lk(m_TaskQueueMutex);
            if (--m_Busy == 0 && m_Tasks.empty()) {
                FinishThreads();
            }
        }
    }

    void Sort(T* Data, size_t Size) {
        // build thread pool
        m_Threads = new std::thread[m_ThreadsNum];
        for (size_t i = 0; i < m_ThreadsNum; i++) {
            m_Threads[i] = std::thread(&USort::SortThread, this, i);
        }

        // process data
        PushTask(Data, Data + Size - 1);
        ReleaseThreads();
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM