HTML Diff
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 &lt;future&gt; #include &lt;iostream&gt; #include &lt;thread&gt; int main(int argc, char* argv[]) { int count = 10; std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 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 &lt;future&gt; #include &lt;iostream&gt; #include &lt;thread&gt; int main(int argc, char* argv[]) { int count = 10; std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 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 &lt;future&gt; #include &lt;iostream&gt; #include &lt;thread&gt; int main(int argc, char* argv[]) { int count = 10; auto future1 = std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); auto future2 = std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 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 &lt;future&gt; #include &lt;iostream&gt; #include &lt;thread&gt; int main(int argc, char* argv[]) { int count = 10; auto future1 = std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 1; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); auto future2 = std::async(std::launch::async, [&amp;count] { for(int i=0; i&lt;count; ++i) { std::cout &lt;&lt; 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