2014-01-13 4 views
1

Что такое наиболее эффективный способ вернуть значение из потока в C++ 11?Возвращает значение из потока в C++ 11

vector<thread> t(6); 

for(int i = 0; i < 6; i++) 
    t[i] = thread(do_c); 

for(thread& t_now : t) 
    t_now.join(); 

for(int i = 0; i < 6; i++) 
    cout << /*the return of function do_c*/ 

Кроме того, если изменение пойдет на пользу производительности, не стесняйтесь рекомендовать другой поток, чем std::thread.

+0

Вы случайно слово? Вернуть значение из потока, не так ли? –

+0

? что вы имеете в виду? да – Luka

+0

да, но я хочу самый эффективный способ – Luka

ответ

4

Захватывая нить, и для ее завершения требуется много сотен машинных циклов. Но это только начало. Контекст переключается между потоками, которые должны произойти, если потоки делают что-нибудь полезное, будут многократно потреблять еще много сотен машинных циклов. Контекст выполнения всех этих потоков будет потреблять много байтов памяти, что, в свою очередь, испортит многие линии кеша, что помешает усилиям ЦП для еще одного большого количества сотен машинных циклов.

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

Во всех других случаях, многозадачный по своей сути неэффективен во всех областях, кроме одного: реактивности.Задача может очень быстро и точно реагировать на внешнее событие, которое в конечном итоге происходит от некоторого внешнего компонента H/W (будь то внутренние часы для таймеров или ваш контроллер WiFi/Ethernet для сетевого трафика).

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

В двух словах, искусство многозадачности программирования сводится к тому:

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

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

Мера эффективности многозадачной будет в конечном счете, зависит от количества внешних событий данная программа, как ожидается, чтобы справиться с одновременно и в пределах данного набора пределов реактивности.

Наконец-то я прихожу к вашему конкретному вопросу.

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

В вашем распоряжении много мощных инструментов синхронизации, которые позволят вам реагировать на кучу асинхронных событий из одного контекста задачи с оптимальной эффективностью (практически) без затрат.
Как правило, блокировка ожидает нескольких входов, например, unix-приправленный select() или Microsoft WaitForMultipleEvents().

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

Таким образом, мой ответ: не беспокойтесь об оптимизации настройки нити. Это не проблема.

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

+0

Спасибо за объяснение, neko-san. Это заставило меня передумать и решило не использовать нитки. – Luka

+0

@ Luka рада помочь вам (и вашему компилятору и вашим ЦП) много ненужных страданий;) –

+0

Прошу прощения, я поместил ваше сообщение по ошибке, пожалуйста, отредактируйте его, чтобы его увеличить ... – Luka

6

Прежде всего std::thread не возвращает значение, но функция, которая передается ему по конструкции, может очень хорошо это сделать.

Невозможно получить доступ к возвращаемому значению функции из объекта std::thread, если вы не сохранили его каким-то образом после вызова функции в потоке.

Простым решением является, например, передать ссылку в поток и сохранить результат в памяти, на которую указывает ссылка. С нитями, хотя нужно быть осторожным, чтобы не вводить гонку данных .

Рассмотрим простую функцию:

int func() { 
    return 1; 
} 

И этот пример:

std::atomic<int> x{0}; // Use std::atomic to prevent data race. 

std::thread t{[&x] { // Simple lambda that captures a reference of x. 
    x = func();  // Call function and assign return value. 
}}; 

/* Do something while thread is running... */ 

t.join(); 

std::cout << "Value: " << x << std::endl; 

Теперь, вместо того чтобы справиться с этой параллельности низкоуровневого набивать себе, что вы можете использовать Standard Library как кто-то (как всегда) уже разрешил это для вас. Существует std::packaged_task и std::future, который предназначен для работы с std::thread для данного конкретного типа проблем. Они также должны быть столь же эффективным как обычное решение в большинстве случаев.

Вот эквивалентный пример использования std::packaged_task и std::future:

std::packaged_task<int()> task{func}; // Create task using func. 
auto future = task.get_future();  // Get the future object. 

std::thread t{std::move(task)};  // std::packaged_task is move-only. 

/* Do something while thread is running... */ 

t.join(); 

std::cout << "Value: " << future.get() << std::endl; // Get result atomically. 

Не всегда предполагать то, что является менее эффективным только потому, что он рассматривается как «высокий уровень».

+0

Вопрос, который приходит мне на ум: что * функциональное * требование оправдывало бы использование механизма, спроектированного с параллельной обработкой значительных объемов данных, чтобы получить простое значение возврата целочисленного потока? Похож на то, что я называю синтаксическим программным обеспечением. Такие решения прекрасно сочетаются с витринами C++ 11, но они *** скрывают значительное потребление ресурсов под обледенением синтаксического сахара IMHO. –

+0

@kuroineko Все зависит от домена, который я предполагаю. Для разработки приложений общего назначения я бы пошел со стандартизованными классами и функциями для максимальной понятности среди коллег, а не для индивидуальных решений. Существуют ли какие-либо функциональные требования для оправдания использования абстракций? Ну, я не могу думать ни о чем. Но отсутствие функциональных требований для оправдания кода абстрагирования для лучшей ремонтопригодности не делает его менее важным. Нефункциональные требования также необходимы. – Snps

+0

Как я вижу, это вопрос нахождения сладкого пятна между простотой использования и расточительными, если не потенциально опасными привычками. В этом конкретном случае я думаю, что стоит немного подумать о цене комфорта.Я разработал встроенное программное обеспечение в течение десятилетия или около того, поэтому, вероятно, потому, что так много ресурсов инвестировано, чтобы избежать такого небольшого усилия, как правило, заставляя меня нервничать :). –

Смежные вопросы