3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

マルチスレッド環境で遭遇したshared_ptrの注意点

Posted at

 先日、C++でマルチエージェントシステムを書いているときに、ん?となったので記事にしました。

ソースコード

 問題となったコードは以下のようなものです。

std::weak_ptr<sample> self = agent;
std::thread t([self]() {
    for(auto s = self.lock(); s && s->predicate(); s = self.lock()) {
        //do something 
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
});
t.detach();

 非同期にエージェントを一定間隔で更新して、スレッドの外では例えばエージェントの状態を表示する、というようなコードです。

問題

 このままではスレッド外でagentの所有権が手放された場合メモリリークを起こす可能性があります。問題の部分がfor文の更新式です。
 具体的に説明する前にまずはstd::weak_ptr::lockの定義を確認しましょう。

shared_ptr<T> lock() const noexcept;

https://cpprefjp.github.io/reference/memory/weak_ptr/lock.html より引用

 上記の通りlock()が返す値はstd::shared_ptr<T>の一時オブジェクトです。つまりlock()で作られたオブジェクトの寿命は式の評価が終了するまで、ということです。shared_ptrへの代入では自身の持つリソースを放棄してから代入しますが、その間にも別のshared_ptrがそのリソースを保持しているため破棄されずに残ってしまいます。

解決策

 解決策は所有しているオブジェクトを使った後すぐに放棄してしまうことです。
 コードは以下のようになります。

std::weak_ptr<sample> self = agent;
std::thread t([self]() {
    for(auto s = self.lock(); s && s->predicate(); s = self.lock()) {
        //do something 
        s.reset(); //abandon
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
});
t.detach();

s.reset()はsleepの前後どちらでも動作しますが、後ろに置いた場合不必要にオブジェクトの寿命が延ばされることになるので、前に置いた方がよいと思います。

 

というわけで、マルチスレッド環境で遭遇したshared_ptrの注意点でした。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?