Участник:Qwerty787788/плюсы3сем — различия между версиями
(→binding arguments of functions) |
(→type erasure) |
||
| Строка 7: | Строка 7: | ||
== type erasure == | == type erasure == | ||
| + | http://prograholic.blogspot.ru/2011/11/type-erasure.html | ||
=== polymorphic function wrapper (aka {boost,std}::function) === | === polymorphic function wrapper (aka {boost,std}::function) === | ||
=== any_iterator === | === any_iterator === | ||
Версия 12:05, 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
http://www.dreamincode.net/forums/topic/264061-c11-fun-with-functions/ - четкая статья, обо всем написано
зачем это надо
function objects
function object binder (aka {boost,std}::bind)
C++11 anonymous functions
type erasure
http://prograholic.blogspot.ru/2011/11/type-erasure.html
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 " в любом случае.
asynchronous operations
зачем это нужно
Чаще всего - запросы в сеть, в файловую систему или к устройствам ввода/вывода. Иногда необходимо дать возможность пользователю как-то влиять на выполнение такого задания, или отображать его подробности - к примеру, отменить запрос в сеть (сберечь трафик или освободить компьютер от ожидания таймаута, когда интернета нет) или показывать прогресс (загрузки файла, к примеру).
Пример. Если в главном треде приложения обрабатывается интерфейс пользователя, выполнение запроса к БД (на удалённом сервере) прямо в этом же треде может привести к зависанию всего интерфейса. Если во время этого запроса программа ничего больше не должна делать в принципе - это допустимо (но плохо, потому что интерфейс перестаёт реагировать на любые действия, пользователь или ОС могут посчитать программу зависшей), но встречаются более сложные интерфейсы, в которых во время такого запроса можно делать что-то ещё - подкручивать настройки, например. В этом случае запрос необходимо делать асинхронно.