std :: threadを使用して足を撃つ別の方法

C ++ 11標準では、標準のスレッドサポートメカニズムが言語に導入されました(これらはしばしばストリームと呼ばれますが、ストリームという用語と混同されるため、ロシア語の転写では元の英語の用語を使用します)。 ただし、C ++のその他のメカニズムと同様に、このメカニズムには、脚を撃つためのいくつかのトリック、微妙さ、およびまったく新しい方法があります。 最近、 20のそのようなメソッドに関する記事の翻訳がHabréに掲載されましたが、このリストは網羅的ではありません。 std::threadコンストラクターでのstd::threadインスタンスの初期化に関連するこのような別のメソッドについて説明したいと思います。


std::threadを使用する簡単な例を次に示しstd::thread


 class Usage { public: Usage() : th_([this](){ run(); }) {} void run() { // Run in thread } private: std::thread th_; }; 

この最も単純な例では、コードは正しいように見えますが、奇妙な点が1つあります。コンストラクタstd::threadを呼び出した時点では、Usageクラスのインスタンスはまだ完全には構築されていません。 したがって、 Usage::run()はインスタンスに対して呼び出すことができ、そのフィールドの一部( std::threadフィールドの後に宣言されている)はまだ初期化されていないため、UBにつながる可能性があります。 これは、クラスコードが画面に収まる小さな例では非常に明らかですが、実際のプロジェクトでは、このトラップは分岐継承構造の背後に隠れています。 デモンストレーションの例を複雑にしましょう。


 class Usage { public: Usage() : th_([this](){ run(); }) {} virtual ~Usage() noexcept {} virtual void run() {} private: std::thread th_; }; class BadUsage : public Usage { public: BadUsage() : ptr_(new char[100]) {} ~BadUsage() { delete[] ptr_; } void run() { std::memcpy(ptr_, "Hello"); } private: char* ptr_; }; 

一見すると、コードも非常に正常に見えます。さらに、 ptr_初期化される前にptr_ BadUsage::run()呼び出されるように星が追加されるまで、ほとんど常に期待どおりにBadUsage::run()ます。 これを実証するには、初期化する前に小さな遅延を追加します。


 class BadUsage : public Usage { public: BadUsage() : ptr_((std::this_thread::sleep_for(std::chrono::milliseconds(1)), new char[100])) {} ~BadUsage() { delete[] ptr_; } void run() { std::memcpy(ptr_, "Hello", 6); } private: char* ptr_; }; 

この場合、 BadUsage::run()呼び出すと、 セグメンテーションエラーが発生しvalgrindは初期化されていないメモリへのアクセスについて文句を言います。


このような状況を回避するには、いくつかの解決策があります。 最も簡単なオプションは、2段階の初期化を使用することです。


 class TwoPhaseUsage { public: TwoPhaseUsage() = default; ~TwoPhaseUsage() noexcept {} void start() { th_.reset(new std::thread([this](){ run(); })); } virtual void run() {} void join() { if (th_ && th_->joinable()) { th_->join(); } } private: std::unique_ptr<std::thread> th_; }; class GoodUsage : public TwoPhaseUsage { public: GoodUsage() : ptr_((std::this_thread::sleep_for(std::chrono::milliseconds(1)), new char[100])) {} ~GoodUsage() noexcept { delete[] ptr_; } void run() { std::memcpy(ptr_, "Hello", sizeof("Hello")); } private: char* ptr_; }; // ... GoodUsage gu; gu.start(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); gu.join(); // ... 


Source: https://habr.com/ru/post/J444464/


All Articles