Как подсчитать выполненные задания в пуле потоков, не разделяя ни одной переменной? - PullRequest
2 голосов
/ 14 апреля 2019

У меня есть пул потоков, который принимает задания (указатели функций + данные), передавая их рабочему потоку для завершения.Некоторые задания получают указатель на счетчик завершения std::atomic<uint32>, который они увеличивают по завершении, поэтому основной поток, создающий эти задания, может знать, сколько из этих заданий завершено.

Проблема, однако, заключается в том, что12+ потоков конкурируют на одном uint32.Я проделал большую работу по разделению заданий и их данных по строкам кэша, так что это единственный источник разногласий, который я хочу устранить, но я не уверен, как лучше всего решить эту конкретную проблему.

Какой самый простой способ собрать количество выполненных заданий без разделения одного uint32 между несколькими потоками?

(Ничего страшного, если основной поток должен обновлять свой кеш при проверке этого количества, я только хочу избежать загрязнения кеша рабочих потоков. Кроме того, рабочим потокам не нужно знать счет,они только увеличивают его, в то время как основной поток может только читать его.)

update:

В настоящее время я пробую идею не делить ни одного счетчика ввсе, но есть счетчик для каждого рабочего потока, который основной поток может сложить при проверке.Идея состоит в том, что основной поток платит основную цену (что хорошо, так как он все равно ожидает "присоединения").

Вот код, который у меня в уродливом виде готовится за 10 минут

class Promise {
    friend ThreadPool;
public:

    ~Promise() {
        // this function destroys our object's memory underneath us; no members with destructors
        m_pool->_destroyPromise(this);
    }

    void join() {
        while (isDone() == false) {
            if(m_pool->doAJob() == false){
                // we've no jobs to steal, try to spin a little gentler
                std::this_thread::yield();
            }
        }
    }

    void setEndCount(uint32 count) {
        m_endCount = count;
    }

    bool isDone() {
        return m_endCount == getCount();
    }

    uint32 getCount() {
        uint32 count = 0;
        for (uint32 n = 0; n < m_countCount; ++n) {
            count += _getCountRef(n)->load();
        }
        return count;
    }

    uint32 getRemaining() {
        return m_endCount - getCount();
    }

private:
    // ThreadPool creates these as a factory
    Promise(ThreadPool * pool, uint32 countsToKeep, uint32 endCount, uint32 countStride, void * allocatedData)
        : m_pool(pool)
        , m_endCount(endCount)
        , m_countCount(countsToKeep)
        , m_countStride(countStride)
        , m_perThreadCount(allocatedData)
    {};

    // all worker IDs start at 1, not 0, only ThreadPool should use this directly
    std::atomic<uint32> * _getCountRef(uint32 workerID = 0) {
        return (std::atomic<uint32>*)((char*)m_perThreadCount + m_countStride * workerID);
    }

    // data
    uint32 m_endCount;
    uint32 m_countCount; // the count of how many counts we're counting
    uint32 m_countStride;
    ThreadPool * m_pool;
    void * m_perThreadCount; // an atomic count for each worker thread + one volunteer count (for non-worker threads), seperated by cacheline
};

обновление два:

Тестирование, похоже, работаетнеплохо.К сожалению, это довольно большая структура, число рабочих потоков 64 байта * (для меня это увеличение КБ), но скорость обмена составляет около 5% + для работ, которые я обычно использую.Я думаю, это может сработать на данный момент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...