0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: с++, std::async, std::launch::async, std::future, std::future::get, shared state, с++ 11, синхронная асинхронность, окончание выполнение потока</p>
1
<p>Теги: с++, std::async, std::launch::async, std::future, std::future::get, shared state, с++ 11, синхронная асинхронность, окончание выполнение потока</p>
2
<p>Наверняка все, кто изучал старый добрый стандарт C++11, знают о существовании в стандартной библиотеке вызова<strong>std::async</strong>, который позволяет выполнить некий код асинхронно (более точно - поведение указывается первым параметром вызова).</p>
2
<p>Наверняка все, кто изучал старый добрый стандарт C++11, знают о существовании в стандартной библиотеке вызова<strong>std::async</strong>, который позволяет выполнить некий код асинхронно (более точно - поведение указывается первым параметром вызова).</p>
3
<p>Согласно документации, вызов с параметром<strong>std::launch::async</strong>обещает выполнить пользовательский код в отдельном потоке. Посмотрим на приведённый ниже код.</p>
3
<p>Согласно документации, вызов с параметром<strong>std::launch::async</strong>обещает выполнить пользовательский код в отдельном потоке. Посмотрим на приведённый ниже код.</p>
4
#include <future> #include <iostream> #include <thread> int main(int argc, char* argv[]) { int count = 10; std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 2; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); return 0; }<p>В строках 8-13 запускаем асинхронное выполнение простой<strong>lambda</strong>-функции, которая должна вывести на экран цифру "1" каждую миллисекунду десять раз. В строках 14-19 запускаем выполнение аналогичной функции, но на этот раз она будет выводить на экран цифру "2". Что можно ожидать на экране по окончанию выполнения программы?</p>
4
#include <future> #include <iostream> #include <thread> int main(int argc, char* argv[]) { int count = 10; std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 2; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); return 0; }<p>В строках 8-13 запускаем асинхронное выполнение простой<strong>lambda</strong>-функции, которая должна вывести на экран цифру "1" каждую миллисекунду десять раз. В строках 14-19 запускаем выполнение аналогичной функции, но на этот раз она будет выводить на экран цифру "2". Что можно ожидать на экране по окончанию выполнения программы?</p>
5
<h2>Кто сказал, что "результат не определён"?</h2>
5
<h2>Кто сказал, что "результат не определён"?</h2>
6
<p>Идея такой гипотезы заключается в том, что оба потока будут выполняться параллельно, поэтому вывод на экран перемешается. Мы можем увидеть на экране, например, такую последовательность:</p>
6
<p>Идея такой гипотезы заключается в том, что оба потока будут выполняться параллельно, поэтому вывод на экран перемешается. Мы можем увидеть на экране, например, такую последовательность:</p>
7
<p>Звучит логично, но эта гипотеза неверна. На самом деле на экран гарантированно будет выведена последовательность:</p>
7
<p>Звучит логично, но эта гипотеза неверна. На самом деле на экран гарантированно будет выведена последовательность:</p>
8
<h2>Почему? Что произошло?</h2>
8
<h2>Почему? Что произошло?</h2>
9
<p>А произошла принудительная синхронизация двух потоков. Выполнение второго потока (с выводом цифры "2") гарантированно начнётся только после того, как первый поток закончит своё выполнение.</p>
9
<p>А произошла принудительная синхронизация двух потоков. Выполнение второго потока (с выводом цифры "2") гарантированно начнётся только после того, как первый поток закончит своё выполнение.</p>
10
<h2>Кто догадается, почему?</h2>
10
<h2>Кто догадается, почему?</h2>
11
<p>На самом деле не всё так просто. Но достаточно задуматься, про что мы забыли в этом примере? А забыли мы про то, что в качестве результата вызов<strong>std::async</strong>возвращает<strong>std::future</strong>. Если бы мы написали наш пример следующим образом, то результат на экране стал бы действительно неопределённым:</p>
11
<p>На самом деле не всё так просто. Но достаточно задуматься, про что мы забыли в этом примере? А забыли мы про то, что в качестве результата вызов<strong>std::async</strong>возвращает<strong>std::future</strong>. Если бы мы написали наш пример следующим образом, то результат на экране стал бы действительно неопределённым:</p>
12
#include <future> #include <iostream> #include <thread> int main(int argc, char* argv[]) { int count = 10; auto future1 = std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); auto future2 = std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 2; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); return 0; }<p>Вот теперь на экране действительно может быть любая последовательность из перемешанных двадцати цифр 1 и 2. Почему результат так кардинально изменился, стоило нам только лишь сохранить<strong>std::future</strong>, которое вернул вызов<strong>std::async</strong>?</p>
12
#include <future> #include <iostream> #include <thread> int main(int argc, char* argv[]) { int count = 10; auto future1 = std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); auto future2 = std::async(std::launch::async, [&count] { for(int i=0; i<count; ++i) { std::cout << 2; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); return 0; }<p>Вот теперь на экране действительно может быть любая последовательность из перемешанных двадцати цифр 1 и 2. Почему результат так кардинально изменился, стоило нам только лишь сохранить<strong>std::future</strong>, которое вернул вызов<strong>std::async</strong>?</p>
13
<h2>Как говорится, всё законно, всё по стандарту</h2>
13
<h2>Как говорится, всё законно, всё по стандарту</h2>
14
<p>Стандарт гарантирует, что окончание выполнение потока, запущенного вызовом<strong>std::async</strong>, синхронизировано с вызовом получения результата<strong>std::future::get</strong>или с освобождением общего состояния (<strong>shared state</strong>) - области памяти, ответственной за передачу результата между<strong>std::async</strong>и<strong>std::future</strong>.</p>
14
<p>Стандарт гарантирует, что окончание выполнение потока, запущенного вызовом<strong>std::async</strong>, синхронизировано с вызовом получения результата<strong>std::future::get</strong>или с освобождением общего состояния (<strong>shared state</strong>) - области памяти, ответственной за передачу результата между<strong>std::async</strong>и<strong>std::future</strong>.</p>
15
<p>В первом примере автоматическое удаление временного объекта<strong>std::future</strong>, который был возвращён из первого вызова<strong>std::async</strong>, приводит к освобождению общего состояния и автоматической синхронизации двух потоков. Просто не сохранив результат вызова<strong>std::async</strong>, мы получили ожидание - второй поток не начнёт выполнение до окончания выполнения первого потока.</p>
15
<p>В первом примере автоматическое удаление временного объекта<strong>std::future</strong>, который был возвращён из первого вызова<strong>std::async</strong>, приводит к освобождению общего состояния и автоматической синхронизации двух потоков. Просто не сохранив результат вызова<strong>std::async</strong>, мы получили ожидание - второй поток не начнёт выполнение до окончания выполнения первого потока.</p>
16
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
16
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
17
17