Участник:Qwerty787788/плюсы3сем

Материал из Викиконспекты
Версия от 10:47, 18 января 2013; 194.85.161.2 (обсуждение) (binding arguments of functions)
Перейти к: навигация, поиск

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

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.

mutexes (mutex, recuirsive_mutex, shared_mutex)

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

зачем это нужно

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

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

как это работает

механизмы предоставляемые ОС (select, poll, epoll, kqueue, IOCP)

remote procedure call

зачем это нужно

apartment threading model

reentrancy

достоинства/недостатки apartment threading model

free threaded threading model

достоинства/недостатки free threaded threading model

пару слов про реализации (COM, CORBA, D-Bus)

fibers

зачем это нужно

coroutines

generators

как это работает

FLS (fiber local storage)

С++11

см. http://gcc.gnu.org/projects/cxx0x.html

про каждую фичу надо говорить, зачем это нужно, как это работает

C++1y and beyond

static_if

modules

concepts

ranges

зачем это нужно

как это работает