c++ std::asyncで非同期実行
std::asyncで関数の非同期実行ができる。
std::asyncの戻り値であるfutureを使って、
- 関数の実行終了を待つ (wait)
- 関数の実行終了をタイムアウト時間指定で待つ (wait_for)
- 関数の実行結果を受け取る (get)
ことができる。
また、std::asyncの実行ポリシーによって
- 別スレッドで実行する
- すぐには実行せず、後で実行する
ことができる。
#include <iostream> #include <future> #include <chrono> using namespace std::literals; int main(){ { std::cout << "start " << std::chrono::system_clock::now() << '\n'; auto f = std::async(std::launch::async, [](){ std::cout << "thread start " << std::chrono::system_clock::now() << '\n'; std::this_thread::sleep_for(2s); }); // 終了を待つ f.wait(); std::cout << "end " << std::chrono::system_clock::now() << "\n\n"; } { std::cout << "start " << std::chrono::system_clock::now() << '\n'; auto f = std::async(std::launch::async, [](){ std::cout << "thread start " << std::chrono::system_clock::now() << '\n'; std::this_thread::sleep_for(2s); }); // 終了を待つ (ただし、1秒でタイムアウト) auto result = f.wait_for(1s); std::cout << ((result == std::future_status::timeout) ? "timeout" : "") << '\n'; std::cout << "end " << std::chrono::system_clock::now() << "\n\n"; } { std::cout << "start " << std::chrono::system_clock::now() << '\n'; auto f = std::async(std::launch::async, []() -> int{ std::cout << "thread start " << std::chrono::system_clock::now() << '\n'; std::this_thread::sleep_for(2s); return 123; }); // 終了を待って結果を取得 auto result = f.get(); std::cout << result << '\n'; std::cout << "end " << std::chrono::system_clock::now() << "\n\n"; } { std::cout << "start " << std::chrono::system_clock::now() << '\n'; // 遅延実行 auto f = std::async(std::launch::deferred, []() -> int{ std::cout << "thread start " << std::chrono::system_clock::now() << '\n'; std::this_thread::sleep_for(2s); return 123; }); std::this_thread::sleep_for(1s); // 終了を待って結果を取得 auto result = f.get(); std::cout << result << '\n'; std::cout << "end " << std::chrono::system_clock::now() << "\n\n"; } /* start 2022-09-12 03:37:02.2842713 thread start 2022-09-12 03:37:02.2899610 end 2022-09-12 03:37:04.2908115 start 2022-09-12 03:37:04.2909026 thread start 2022-09-12 03:37:04.2910574 timeout end 2022-09-12 03:37:05.2954522 start 2022-09-12 03:37:06.3097559 thread start 2022-09-12 03:37:06.3099622 123 end 2022-09-12 03:37:08.3127969 start 2022-09-12 03:37:08.3128716 thread start 2022-09-12 03:37:09.3226742 123 end 2022-09-12 03:37:11.3310814 */ }
std::format
std::format基本
printf()と同じ感覚で、書式と変数を指定する。置換フィールド({と}で囲まれた部分)に置換フィールド書式を指定するが、デフォルトの書式で良ければ省略できる。
#include <format> #include <iostream> int main(){ auto i{5}; auto str{"abc"}; std::cout << std::format("i={}, str={}\n'", i, str); // => "i=5, str=abc" }
置換フィールドに書式を指定する例
#include <format> #include <iostream> int main(){ auto i{15}; // 6桁の16進表現,先行0埋め std::cout << std::format("i={:#06x}\n", i); // => "i=0x000f" }
独自のフォーマットを定義する
#include <format> #include <iostream> struct coord { float x_{0.0}; float y_{0.0}; float z_{0.0}; }; // coord用のカスタムフォーマット定義 template <> struct std::formatter<coord> { constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } auto format(const coord& obj, std::format_context& ctx) { return std::format_to(ctx.out(), "x={},y={},z={}", obj.x_, obj.y_, obj.z_); } }; int main(){ coord c{10.0, 20.5, 30.0}; std::cout << std::format("{}", c); // => "x=10,y=20.5,z=30" }
c++ modules
MSVCでのサポート
https://en.cppreference.com/w/cpp/compiler_support によるとMSVCでは19.28でサポートされている。
Visual Studioの設定
https://docs.microsoft.com/ja-jp/cpp/build/reference/experimental-module?view=msvc-170 に従い、 /std:c++20 experimental:module を指定する。
モジュールのエクスポート
拡張子はixx
export module mylib; namespace mylib { export int myfunc() { return 999; } export class myclass { public: int myfunc() { return 888; } }; };
呼び出し側
import mylib; ... std::cout << mylib::myfunc() << "\n"; mylib::myclass c; std::cout << c.myfunc() << "\n";
c++ 小ネタ
if文内で初期化できる
if(auto i = 5 * -10 * 3; i < 0){ std::cout << i << " < 0" << '\n'; }
(switch文も同様)
initializer_listを関数に渡す
void disp_all(std::initializer_list<int> il){ for(auto i: il) std::cout << i << " "; } disp_all({2,4,6,8});
推測可能な場合、テンプレートの引数は省略できる
std::vector v; // 型がわからないのでコンパイルエラー
std::vector vi{1,2,3}; // 推測可能
std::vector vs{"1","2","3"}; // 推測可能
std::source_locationを使えば、FILEやLINEを使っていた処理を少しスマートに書ける。
void log(const std::string & msg, std::source_location s = std::source_location::current()) { std::cout << msg << " " << s.file_name() << " " << s.function_name() << " " << s.line() << std::endl; } int main(){ ... log("hello"); ... }
pairやtupleの要素をautoで受ける
auto coord = std::make_pair(10.2, 30.4); auto[x,y] = coord; auto t = std::make_tuple("abc", 1024, "xyz"); auto[type, length, value] = t;
std::map<int, std::string> m{{1,"abc"},{2,"def"}}; for(auto [key, value] : m) std::cout << std::format("key={} value={}\n", key, value)
for文内で要素を初期化
for(std::array a{2,4,6,8}; auto i : a) std::cout << i << " ";
std::iota()で連番を設定
std::array<int,10> a; std::iota(a.begin(), a.end(),10); // 10から始まる連番を設定 for(auto i : a) std::cout << i << '\n';
桁数の大きい数値は'(アポストロフィー)で区切れる
int i{1'234'567'890}; // int i{1234567890}; と等価
std::ranges:size()を使えば、生配列のサイズも取得できる
int array[100]; std::cout << std::ranges::size(array); // 100
畳み込み式で可変引数
#include <iostream> #include <string> using namespace std::literals; template <typename ... T> auto add(const T& ... param){ return (param + ...); } int main() { auto s = "abc"s; std::cout << add(1,2,3) << '\n'; // => 6 std::cout << add(1.1, 2.2, 3.3) << '\n'; // => 6.6 std::cout << add(1.1, 2.2, 3) << '\n'; // -> 6.4 std::cout << add("abc"s, "def"s, "xyz"s) << '\n'; // -> "abcdefxyz" }
c++ jthread
自動join
std::threadでは、スレッドが終了する前にthreadオブジェクトが破棄されるとstd::terminate()が呼ばれる。そのため、join()を呼んで、スレッドの終了を待つ必要がある。 (スレッドの管理が必要ないのであれば、detach()でもよい)
TEST(thread, cpp20) { using namespace::std::literals; int x{0}; std::thread t([&]() { std::cout << "thread start" << std::endl; while(x < 10){ std::this_thread::sleep_for(0.5s); std::cout << "x:" << x++ << std::endl; } std::cout << "thread exit" << std::endl; }); std::this_thread::sleep_for(2s); t.join(); // <<<<<<<< スレッドの終了を待つ }
std::jthreadでは、jthreadオブジェクトが破棄される際にjoin()が自動的に呼ばれるので、明示的にjoin()を呼ぶ必要はない。
TEST(jthread, cpp20) { using namespace::std::literals; int x{0}; std::jthread t([&]() { std::cout << "thread start" << std::endl; while(x < 10){ std::this_thread::sleep_for(0.5s); std::cout << "x:" << x++ << std::endl; } std::cout << "thread exit" << std::endl; }); std::this_thread::sleep_for(2s); // スレッドが終了するまで、自動的に待つ }
スレッドのキャンセル
request_stop()の呼び出しで、スレッドをキャンセルできる。
TEST(jthread_cancel, cpp20) { using namespace::std::literals; int x = 0, y = 0; std::jthread t([&](std::stop_token token) { std::cout << "thread start" << std::endl; while(x < 10){ // 停止要求があれば終了 if(token.stop_requested()){ std::cout << "stop requested" << std::endl; break; } std::this_thread::sleep_for(0.5s); std::cout << "x:" << x++ << std::endl; } std::cout << "thread exit" << std::endl; }); // スレッドに対する停止要求で呼び出されるコールバック std::stop_callback sc { t.get_stop_token(), [&] { std::cout << "stop_callback" << std::endl; y++; }}; ASSERT_EQ(x , 0); std::this_thread::sleep_for(2s); t.request_stop(); // スレッド停止要求 ASSERT_EQ(y , 1); }
std::latchによるスレッド同期
std::latchを使って、値が0になるまで待つ。
TEST(latch, cpp20) { using namespace::std::literals; const int num_threads = 5; std::latch l(num_threads); std::vector<std::jthread> threads; // 5つのスレッドを起動 for(int i = 0 ; i < num_threads ; i++) threads.push_back(std::jthread([i, &l](){ std::cout << "thread " << i << " start" << std::endl; std::this_thread::sleep_for(i * 1s); std::cout << "thread " << i << " exit" << std::endl; l.count_down(); // 1つ減らす })); l.wait(); // latchが0になるまで待つ std::cout << "all threads exit" << std::endl; }
std::counter_semaphoreによる同時実行数の制限
std::counter_semaphoreを使って、特定の処理を同時に実行できるスレッドの数を制限できる。
TEST(semaphore, cpp20) { using namespace::std::literals; const int num_threads = 10; const int num_semaphre = 3; std::counting_semaphore semaphore(num_semaphre); std::vector<std::jthread> threads; std::atomic<int> counter; // 10のスレッドを起動 for(int i = 0 ; i < num_threads ; i++) threads.push_back(std::jthread([i, num_semaphre, &semaphore, &counter](){ semaphore.acquire(); // 空きを待つ std::cout << "counter=" << ++counter << std::endl; std::cout << "thread " << i << " acquired" << std::endl; ASSERT_LE(counter, num_semaphre); // 3以下のはず std::this_thread::sleep_for(i * 1s); counter--; std::cout << "thread " << i << " exit" << std::endl; semaphore.release(); // セマフォ開放 })); }