2017-01-26 10 views
1

я реализовал немного многопоточных приложений, которая делает следующее:Неблокирующая нитей остановка - или почему станд :: atomic_flag замедлиться мой код

MainThread

Основной поток запускает таймер используя setitimer и запускает до 8 потоков. Таймер из основного потока используется для повторного чтения из> файла (каждые 0,25 с). Когда таймер вызывается 20 раз (после ~ 5 секунд), я хочу, чтобы остановить потоки и получить количество выполненных вычислений каждой нитью.

MainThread.h

class MainThread { 
    private: 
    int counter; 
    ThreadManager tm; 
    bool registerTimer(double seconds); 
    void startTimerWithInterval(double interval); 
    void read() { 
     /** 
     * If counter >= 20, call stopWorker on all threads 
     */ 
     tm.stopWorkers(); 
    } 
    public: 
    MainThread():counter(0){} 
} 

WorkerThreads

Выполните некоторые дорогие вычисления Whithin петли бесконечности. После определенного количества вычислений поток должен хранить количество выполненных вычислений . Это значение (количество вычислений) должно быть довольно точным, поэтому я думаю, что мне нужно остановить потоки (довольно) немедленно.

ThreadClass.h

class WorkerThread { 
    private: 
    /** 
    * ... 
    */ 
    std::atomic_flag keep_Running = ATOMIC_FLAG_INIT; 

    static void* run(void* args) { 
     ((WorkerThread*)args)->process(); 
     pthread_exit(nullptr); 
     return nullptr; 
    } 

    public: 
    /** 
    * ... 
    */ 
    bool startWorker() { 
     keep_Running.test_and_set(); 
     bool result = (pthread_create(&thread, pthread_attr, run, this) == 0); 
     if(!result) { 
     keep_Running.clear(); 
     } 
     return result; 
    } 
    void stopWorker() { 
     keep_Running.clear(); 
    } 
    bool keepRunning() { 
     return keep_Running.test_and_set(); 
    } 
    virtual void process() = 0; 
}; 

ComputationThread.h

class ComputationThread : public WorkerThread { 
    public: 
    virtual void process() override { 
     /** 
     * Perform computations with ~400MB data 
     * check every 16B, whether keepRunning still true 
     */ 
     bool keep_running = true; 
     while(keep_running) { 
     /** 
     * Process 4B 
     */ 
     keep_running = keepRunning(); 
     } 
    } 
}; 

Если я использую какой-то флаг, чтобы отслеживать состояние движения в потоке, я должен сделать это поточно флаг , не так ли? Я пробовал std::atomic_flag, потому что он должен быть заблокированным и иметь атомарные операции, но это приводит к резко падать производительности. Мой вопрос в том, приводит ли std::atomic_flag к снижению производительности, или это потому, что я слишком часто выполняю проверку? Кто-нибудь знает лучший способ?

Прежде чем вы спросите, я должен использовать pthread вместо std::thread, чтобы назначить поток указанному ядру в создании потока (используя pthread_attrib_t).

+0

Зачем вам нужно назначать поток конкретному ядру? – Slava

+0

Потому что есть разные ядра – Hymir

+0

OS делает это само по себе, вы только делаете что-то хуже – Slava

ответ

0

std::atomic_flag::test_and_set() включает аргумент по умолчанию std::memory_orderorder = memory_order_seq_cst

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

...

Всего последовательное упорядочение требует полного забора памяти инструкции процессора на всех многоядерных системах. Это может стать узким местом производительности, поскольку оно заставляет доступ к зараженной памяти распространяться на каждое ядро.

Этот флаг для memory_order будет вызывать каждый поток для выполнения своих операций памяти для test_and_set в порядке, загрузка и сохранение в последовательной памяти, которая собирается быть медленнее, каждый поток будет тратить время ожидания на других потоках для выполнения операций с памятью.

+0

Каждый рабочий поток имеет свой собственный std :: atomic_flag. Я просто хочу убедиться, что workthread не читает неопределенное состояние, потому что запись из основного потока. Будет ли лучший подход к использованию мьютекса или другого std :: memory_order? – Hymir

+0

@Hymir Что произойдет, если вы прочитаете неопределенное состояние? – Slava

+0

Ну, я не знаю, что произойдет, если один поток читает, а другой пишет bool. Интересный факт: если я просто использую bool, программа не заканчивается. Я просто прочитал: std :: atomic_flag заблокирован и атомарен, и я подумал: «Ну, это звучит потрясающе». – Hymir

1

Не использовать std::atomic_flag.

Подразумевается как примитивный примитив низкого уровня atomic и поэтому имеет очень ограниченный интерфейс.
Главное его ограничение состоит в том, что вы можете только проверить его значение, установив его в true в одном вызове atomic с именем test_and_set()
Это операция Read-Modify-Write (RMW), которая выполняет дорогостоящую синхронизацию между всеми ядрами. Поскольку вы вызываете это на каждой итерации цикла, он значительно замедляется.

Используйте обычный atomic<bool> и установите его, как только вы закончите. Таким образом, внутри цикла вам нужно только прочитать его, что является нагрузкой atomic, и это переводит на обычную операцию mov. Установка определенного порядка памяти не повлияет на производительность (по крайней мере, на X86).

+0

Это звучит потрясающе! Спасибо за разъяснение. Я считаю, что даже лучший подход заключается в том, чтобы выполнить поточную копию текущего значения суммы в методе stopWorker. Поэтому нам нужно только проверять bool один раз за цикл, и мы все равно получаем правильный результат! – Hymir

+0

@Hymir Мне было бы интересно получить некоторые отзывы об общем эффекте производительности, когда 'atomic_flag' больше не используется. – LWimsey

+0

Испытания выполняются. Я получу данные в понедельник! – Hymir