Участник:Qwerty787788/плюсы3сем — различия между версиями
(→mutexes (mutex, recuirsive_mutex, shared_mutex)) |
(→exception safety) |
||
Строка 17: | Строка 17: | ||
== exception safety == | == exception safety == | ||
+ | some link: [http://www.stroustrup.com/except.pdf] | ||
=== зачем это нужно === | === зачем это нужно === | ||
=== RAII === | === RAII === |
Версия 04:00, 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
зачем это нужно
Ну вроде бы очевидно: увеличить скорость выполнения программы. Однако, стоит понимать, что иногда скорость может только уменьшится (в интернете есть куча примеров).
понятие 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 " в любом случае.