Чтобы запустить поток, нужно иметь функцию, которую он будет выполнять. Функция не обязательно должна быть особенной, поскольку в потоке можно выполнить практически любую функцию. Напишем небольшую программу-пример, которая запускает поток и ожидает его завершения:
void f(int i) { cout << i << '\n'; }
int main()
{
thread t {f, 123};
t.join();
}
Вызов конструктора std::thread
При наличии в системе нескольких ядер процессора потоки можно выполнять параллельно и конкурентно. В чем заключается разница? Если компьютер имеет всего одно ядро ЦП, то можно создать множество потоков, работающих параллельно, но не конкурентно, поскольку ядро способно запускать лишь один поток в любой момент времени. Потоки запускаются и чередуются, где каждый поток выполняется какую-то часть секунды, затем приостанавливается, после чего следующий поток получает время (для пользователей-людей кажется, что потоки выполняются одновременно). Если потокам не нужно делить одно ядро, то они могут быть запущены конкурентно и
К этому моменту мы
□
□
□
Большая часть операционных систем предоставляет возможности управления этими аспектами многопроцессорной обработки, но на текущий момент данные функции
Однако можно запускать и останавливать потоки и указывать им, когда и над чем работать и когда останавливаться. Этого должно быть достаточно для большинства приложений. В данном разделе мы создали три дополнительных потока. После этого
Читая рисунок сверху вниз, мы заметим, что в какой-то момент разбиваем рабочий поток программы на четыре потока. Мы запускаем три дополнительных потока, которые совершают некие действия (а именно, ожидают и выводят сообщения), но после их запуска основной поток, выполняющий функцию main
Когда поток завершает выполнение своей функции, он возвращает значение, возвращенное ею. Затем стандартная библиотека «делает уборку», что приводит к удалению потока из планировщика системы и, возможно, его уничтожению, но волноваться об этом не нужно.
Единственное, о чем
x.join()
для объекта другого потока, его выполнение приостанавливается до того, как будет выполнен поток x
. Обратите внимание: нас ничто не спасет при попадании потока в бесконечный цикл! Если нужно, чтобы поток продолжал существовать до тех пор, пока не решит завершиться, то можно вызвать функцию x.detach()
. После этого у нас не будет возможности управлять потоком. Независимо от принятого решения, мы thread
вызовет функцию std::terminate()
, что приведет к внезапному завершению работы приложения.В момент, когда функция main возвращает значение, приложение заканчивает работу. Однако в это же время наш открепленный поток t3
main
. Выполняем устойчивую к исключениям общую блокировку с помощью td::unique_lock и std::shared_lock
Поскольку работа потоков значительно зависит от поддержки операционной системы, а STL предоставляет хорошие интерфейсы, позволяющие абстрагироваться от операционных систем, разумно также предоставить поддержку STL для
В этом разделе мы взглянем на классы-мьютексы STL и абстракции блокировки RAII. Поэкспериментируем с ними в нашей конкретной реализации примера, а также изучим другие вспомогательные средства синхронизации, предоставляемые STL.
Как это делается