Участник:Qwerty787788/плюсы3сем — различия между версиями
(→exception safety) |
D-side (обсуждение | вклад) (→зачем это нужно) |
||
Строка 28: | Строка 28: | ||
== multithreading == | == multithreading == | ||
=== зачем это нужно === | === зачем это нужно === | ||
− | Ну вроде бы очевидно: увеличить скорость выполнения программы. Однако, стоит понимать, что иногда скорость может | + | Ну вроде бы очевидно: увеличить скорость выполнения программы. Однако, стоит понимать, что иногда скорость может даже уменьшиться (в интернете есть куча примеров). |
+ | |||
+ | Более подробно - это способ задействовать больше аппаратуры для выполнения алгоритма в современных компьютерах, которые хвастаются многоядерностью/многопроцессорностью - так достигается увеличение скорости выполнения. Есть множество задач, которые хорошо делятся на несколько независимых друг от друга. Например, quicksort - после разделения элементов на группы, каждую из них можно сортировать независимо. Но тут важно не переборщить с его использованием, поскольку создание отдельного потока (thread, треда) тоже требует некоторых ресурсов, и сортировать в отдельном треде группы, скажем, по 2 элемента уже очень нерационально, это может как раз замедлить сортировку. | ||
+ | |||
=== понятие race condition === | === понятие race condition === | ||
Стандартный пример, как не нужно использовать multithreading. Пусть есть некоторые общие данные, а два потоки одновременно пытаются выполнить с ними какие-то операции. Например, есть переменная x = 0, два потока пытаются увеличить ее на 1. В итоге может получится как 1, так и 2 (так и вообще непонятно что, см. [http://en.wikipedia.org/wiki/Race_condition]). Вывод: нужно lock'ать данные перед тем, как их использовать. | Стандартный пример, как не нужно использовать multithreading. Пусть есть некоторые общие данные, а два потоки одновременно пытаются выполнить с ними какие-то операции. Например, есть переменная x = 0, два потока пытаются увеличить ее на 1. В итоге может получится как 1, так и 2 (так и вообще непонятно что, см. [http://en.wikipedia.org/wiki/Race_condition]). Вывод: нужно lock'ать данные перед тем, как их использовать. |
Версия 10:22, 18 января 2013
Содержание
- 1 binding arguments of functions
- 2 type erasure
- 3 signals (aka listeners aka observers)
- 4 exception safety
- 5 UNIX-signals (это не тоже самое что signals из пункта 3), hardware interrupts
- 6 multithreading
- 7 asynchronous operations
- 8 remote procedure call
- 9 fibers
- 10 С++11
- 11 C++1y and beyond
- 12 ranges
binding arguments of functions
зачем это надо
function objects
function object binder (aka {boost,std}::bind)
C++11 anonymous functions
type erasure
polymorphic function wrapper (aka {boost,std}::function)
any_iterator
boost::any
signals (aka listeners aka observers)
зачем это нужно
примеры почему наивная реализация не всегда хорошо работает
понятие reentrancy
пару слов про существующие реализации (boost::signals, boost::signals2, Qt Signals)
exception safety
some link: [1]
зачем это нужно
RAII
ошибки и как их можно обрабатывать (propagation to caller, assertion, logging, precess termination)
UNIX-signals (это не тоже самое что signals из пункта 3), hardware interrupts
зачем это нужно
reentrancy
multithreading
зачем это нужно
Ну вроде бы очевидно: увеличить скорость выполнения программы. Однако, стоит понимать, что иногда скорость может даже уменьшиться (в интернете есть куча примеров).
Более подробно - это способ задействовать больше аппаратуры для выполнения алгоритма в современных компьютерах, которые хвастаются многоядерностью/многопроцессорностью - так достигается увеличение скорости выполнения. Есть множество задач, которые хорошо делятся на несколько независимых друг от друга. Например, quicksort - после разделения элементов на группы, каждую из них можно сортировать независимо. Но тут важно не переборщить с его использованием, поскольку создание отдельного потока (thread, треда) тоже требует некоторых ресурсов, и сортировать в отдельном треде группы, скажем, по 2 элемента уже очень нерационально, это может как раз замедлить сортировку.
понятие race condition
Стандартный пример, как не нужно использовать multithreading. Пусть есть некоторые общие данные, а два потоки одновременно пытаются выполнить с ними какие-то операции. Например, есть переменная x = 0, два потока пытаются увеличить ее на 1. В итоге может получится как 1, так и 2 (так и вообще непонятно что, см. [2]). Вывод: нужно lock'ать данные перед тем, как их использовать.
atomic operations
В 11х плюсах, чтобы не получались "race condition" есть atomic operations библиотека. Например, можно писать такой код:
atomic<int> counter(0);
++counter // используется одновременно в нескольких потоках
Эта конструкция является thread-safe, т. е. не нужно делать lock на counter.
mutex - объект, который используется для определения, делает ли с некоторым объектом что-то другой поток.
Пример использования. Пусть есть map и треды, которые одновременно в него что-то пишут. Тогда создадим mutex и будем его lock'ать перед тем как использовать map:
std::map<std::string, std::string> some_map; std::mutex some_mutex; void write_to_map(std::string key, std::string value) { some_mutex.lock(); some_map[key]=value; some_mutex.unlock(); }
recursive_mutex позволяет треду lock'ать его несколько раз. Пример:
void f1() { _mutex.lock(); // some code _mutex.unlock(); } void f2() { _mutex.lock(); f1(); // deadlock here! _mutex.unlock(); }
Если в данном коде использовать обычный mutex, то программа зависнет (получит deadlock), так как f1 не сможет взять lock и будет ждать. При использовании recursive_mutex все будет нормально (поскольку к объекту обращаются с одного и того же треда, f1 сможет взять lock). Снимать lock нужно столько же раз, сколько и брать, иначе другие потоки не смогут работать с данными.
shared_mutex - boost'овая хрень. Читать тут: [3]. Видимо смысл в том, что в обычных мютексах мы не могли читать одновременно из двух потоков, т.к. нужно было взять lock (вдруг кто-то третий начнет изменять данные). А тут можно.
condition_variable
Пусть есть тред t1, который что-то делает. Пусть другие треды должны ждать, пока t1 не закончит выполнение. Чтобы это сделать используются conditional_variable. Пример:
std::condition_variable cond_var; std::mutex m; int sum; bool done = false; std::thread th1([&]() { std::unique_lock<std::mutex> lock(m); for (int i = 0; i < 5; ++i) { sum++; } done = true; cond_var.notify_one(); }); std::thread th2([&]() { std::unique_lock<std::mutex> lock(m); if (!done) cond_var.wait(m); std::cout << sum; }); int main() { th1.join(); th2.join(); }
Важно, что второй тред уже взял lock на m. Т. е. cond_var.wait(m) делает следующие: отдает lock, ждет, когда кто-то сделает cond_var.notify_one() или cond_var.notify_all(), забирает lock обратно, продолжает работу программы. notify_all отличается от notify_one тем, что он будит сразу все треды, которые ждут, а не только один (случайный?).
понятие dead lock
Первый тред взял lock на A, второй на B. Первый хочет что-то сделать с B, второй с A. Оба ждут.
futures
Если тред бросает исключение, но не ловит его, скорее всего, вся программа упадет. Чтобы такого не случилось, можно использовать futures/promises. А вообще future используется для получение некоторого значения, которое должен вернуть тред. Пример:
auto promise = std::promise<std::string>(); auto producer = std::thread([&] { promise.set_value("Hello World"); }); auto future = promise.get_future(); auto consumer = std::thread([&] { std::cout << future.get(); }); producer.join(); consumer.join();
Из примера вроде понятно, что происходит. future_get() ждет, пока другой тред не вернет какое-нибудь значение. Если что-то случилось, то можно в promise положить ошибку:
promise.set_exception(runtime_error(“message”));
и нормально ее обработать в другом треде.
TLS (thread local storage)
Если хочется иметь для каждого треда свои отдельные данные, то в 11х плюсах есть THL. Пример:
thread_local int n; void f() { std::cout<< ++n << " "; } int main { std::thread t1(f); std::thread t2(f); t1.join(); t2.join(); }
Программа выведет "1 1 " в любом случае.