11
16

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 5 years have passed since last update.

ワーカースレッドを開始・終了するときの意外な落とし穴に気をつけよう

Last updated at Posted at 2014-01-21

これは私がはまった落とし穴を再現するデモ・プログラムです。

test.cpp
#include <thread>
#include <iostream>
#include <unistd.h>
#include <atomic>

using namespace std;

class my_service
{
public:
    void run() {
        cout << "run()" << endl;
        worker_ = thread([this]() {
            cout << "thread started." << endl;
            canceled_ = false;
            for (int i = 0; i < 10 && !canceled_; i ++) {
                cout << i << endl;
                sleep(1);
            }
        });
    }

    void stop() {
        cout << "stop()" << endl;
        canceled_ = true;
        worker_.join();
    }

private:
    thread worker_;
    std::atomic<bool> canceled_;     // atomic<T>型に修正しました
};

int main()
{
    for (int i = 0; i < 2; ++ i) {
        my_service service;
        service.run();
        sleep(0);   // [ここに注目]
        service.stop();
    }

    return 0;
}

実行結果

run()
stop()
thread started.
0
1
2
3
4
5
6
7
8
9
run()
stop()
thread started.
0
1
2
3
4
5
6
7
8
9

あくまで説明用なので、便利なライブラリーは使わず直接std::threadを使っています。

main() 関数の中で run() して 直後に stop() しているため、スレッドは1回も実行されないつもりでしたが、実は スレッドが実行開始される前にstop()が走ってしまうため、期待通りの動作になりません。

sleep(0)をsleep(1)とかに変えるとちゃんとstop()でスレッドが止まります。

スレッドの処理内容によっては最悪デッドロックになることもあり、実際のプログラム中では気づきにくいので要注意です。

ビルド方法

g++ -std=c++11 -Wall -pthread -g test.cpp

私なりに問題を解決したバージョンを作ってみました。
(このコードは間違っています。詳しくはコメントをご覧ください。)

test2.cpp
#include <thread>
#include <iostream>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <unistd.h> // for sleep

using namespace std;

class my_service
{
public:
    void run() {
        cout << "run()" << endl;

        std::unique_lock<std::mutex> lk(m);
        std::condition_variable cv;

        worker_ = thread([this, &cv]() {
            cout << "thread started." << endl;

            canceled_ = false;

            cv.notify_one();

            for (int i = 0; i < 10 && !canceled_; i ++) {
                cout << i << endl;
                sleep(1);
            }
        });

        cv.wait(lk);   // ここは間違っているのでマネをしないでください!
    }

    void stop() {
        cout << "stop()" << endl;

        canceled_ = true;
        worker_.join();
    }

private:
    thread worker_;
    std::mutex m;
    std::atomic<bool> canceled_;
};

int main()
{
    for (int i = 0; i < 2; ++ i) {
        my_service service;
        service.run();
        sleep(0);
        service.stop();
    }

    return 0;
}

実行結果

run()
thread started.
stop()
run()
thread started.
stop()

実行結果を見ると、処理の順番がかわっています。

コメントありがとうございます。
つっこみ歓迎です!

11
16
6

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
11
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?