C C++ Linux

pthread_cancelすると何が行われるのか

投稿日:

仕事でのメモ。

pthread_cancel

スレッドをキャンセルしたい、でも穏やかに終了できない。そんなときにpthread_cancel。

もちろん、避けられるときは避けたい。
結局はselect等でキャンセル用のfdを一緒に待って、そちらのfdからパケットが来た場合はスレッドをreturnさせるのが一番安全な手段ではある。
std::threadオブジェクトのメンバ関数としてcancelが提供されていないことからもそれが伺える。

しかし、そもそもスレッドをサイクリックにくるくる回してselectを呼べるように必ず設計するかというと、必ずしもそうできない場合もあるだろう。その場合はやむを得ない。

pthread_cancelで何が行われるのか

シグナル送出

LinuxのMan pageによると、以下の記述がある。

注意

Linux では、キャンセルはシグナルを使って実装されている。NPTL スレッド実装では、 最初のリアルタイムシグナル (つまり、シグナル 32)がこのために使用される。 LinuxThreads では、リアルタイムシグナルが利用可能な場合は2 番目のリアルタイム シグナルが使用され、そうでない場合は SIGUSR2 が使用される。

なるほどシグナルとのこと。

シグナルが創出されるということは、スレッド生成によって生まれたスタックに積まれたauto変数のデストラクタはコールされないのではないか?
もしそうだとしたら非常に使い勝手が悪い。
例えばスレッド間での動機をRAIIオブジェクトでラップしたmutex、std::lock_guard等で行った場合は、mutexはロックされたまま抜けてしまうことになる。デストラクタ内でmutexを触って資源の開放を行う場合には、別スレッドがロックしたままキャンセルしてしまい、デッドロックしてしまうのではないか?

もちろんpthread_mutex等の場合は論外であるが、RAIIオブジェクトも安全でないとなると、非常に困る。

というわけで、念のため実際にソースコードでシグナルを送出している部分を調べてみることとする。

ソースコードを読む

ソースは以下を参照した。
業務で使用しているglibcのpthreadの実装(nptl)を読む。
以下、念のためソースのコピペは行わずに読んでいく。

native posix thread library のpthread_cancel.cを参照すると、43行目〜97行目のようにSIGCANCELをいじっていそうな部分がある。

ここでは、threadのcancelstateがenable、かつasynchronousなキャンセルが行える様になった場合、自身のpidを取得してSIGCANCELを送出しているようだ。

SIGCANCELはnptlの初期化処理に於いてsigactionに対するハンドラを登録している。

ハンドラの内部でcancelstateと非同期キャンセルをスピンでチェックし、__do_cancel()を呼び出す。

__do_cancel()内部では__pthread_unwind関数によってthreadをunwindしている。このときにcleanup_pushで登録したアドレスにjmpすることで、ユーザ定義のクリーンアップ処理がコールされるという仕組みのようだ。

__pthread_unwind関数内部では、extern宣言された_Unwind_ForcedUnwind関数をコールする。この関数はnptl内部で定義されてないことから、別ライブラリの関数であることが推測される。

ちょっと探すと・・・あった。libunwind。

結局のところ、pthread_cancelが送出されたら、シグナルが呼び出されるものの、スタックのアンワインドが行われることになりそうだ。
(forced_unwindというのが少し気になるが)

pthread_cancel時の資源開放

ここで気になるのが各種資源開放である。
当初の予想では、「シグナルを創出するということは、C++でのauto変数のデストラクタは呼ばれないのではないか」と言うものだったが、glibcのpthreadの実装ではスタックのunwindは行われるように見える。

実際に動作させて確認してみよう。

今回の検証に使用した環境は以下

processor       : 0
model name      : Intel(R) Atom(TM) CPU  C2750  @ 2.40GHz
processor       : 1
model name      : Intel(R) Atom(TM) CPU  C2750  @ 2.40GHz
Linux hostname 4.4.88-mainline-rev1 #1 SMP Wed Sep 13 23:49:03 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
Ubuntu 16.04.1 LTS \n \l
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

ソースコード

  1 #include <thread>
  2 #include <pthread.h>
  3 #include <stdio.h>
  4 #include <unistd.h>
  5 
  6 class Foo {
  7  public:
  8   Foo() = default;
  9   ~Foo() {
 10     printf("foo destruct.\n");
 11   }
 12 };
 13 
 14 int main() {
 15   std::thread th1([] () ->void {
 16     Foo foo;
 17     sleep(100);
 18     printf("not reach.");
 19   });
 20   printf("cancel.\n");
 21   pthread_cancel(th1.native_handle());
 22   printf("cancel done.\n");
 23   th1.join();
 24   printf("join  done.\n");
 25   return 0;
 26 }

難しいことは何もない。
sleepはキャンセルポイントのため、pthread_cancel時は17行目で抜ける。18行目は呼ばれないはずだ。

fooのデストラクトが行われるのはpthread_cancel後直ちにか、joinしたあとになるだろう。

コンパイル+実行結果は以下。

user@hostname:~/$ g++ main.cpp -pthread -std=c++11
user@hostname:~/$ ./a.out 
cancel.
cancel done.
foo destruct.
join  done.

なるほど、join後の資源回収時にデストラクタが呼ばれるわけか。

結果

  • pthread_cancel時にはシグナルが送出される。
  • 上記シグナルはpthread内部でハンドルして、スタックのアンワインドの準備が行われる
  • 実際のスタックのアンワインドは、threadのjoin時に行われる。
  • したがって、join時にスレッドのスタック内のauto変数のデストラクタがコールされる。

スレッド内での各種ロック資源は、RAIIオブジェクト化することで、中途半端な開放を防ぐことができそうだ。

とは言え

なるべくpthread_cancelに頼らない穏やかな終了を目指そう!

-C, C++, Linux
-,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

関連記事

Ubuntu 16.04 LTS でのDocker実行環境(docker-engine + docker-compose)の構築

今回は、Ubuntu 16.04 LTS でのDocker実行環境の構築を行います。 目次1 背景2 注意3 docker-engineのインストール4 docker-composeのインストール5 …

Dockerを使用した簡単なC++実行環境の構築

今回は、C++の機能を調査するための簡単なテスト環境を、Dockerを用いて構築する手順を解説します。 目次1 要件2 仕様3 実装3.1 元になるイメージ3.2 Dockerfileの作成3.3 実 …

Docker上でWordPressとnginx-proxyを連携(SSL対応)

今回は、Docker上でWordpressサーバを構築し、SSLに対応したnginx-proxyとの連携を行います。 追記(2017/10/08):作成したdocker-compose.ymlをGit …

cmake, googletestを利用したC++開発環境の構築テスト

自学用メモ。 業務で使用しているcmakeの勉強がてら、業務に近いソースツリーを作成した。 googletestを用いた単体テストも合わせて書けるような構成になっている。 テンプレートとして、今後のC …

no image

cmakeでのDEBUG/RELEASEモード別・サブディレクトリ別のコンパイルオプションの分け方

仕事で使うので備忘録として。 対象となるソースコードツリーは、拙作のcppのプロジェクトのテンプレートで行う shinjikirino/cpp_project_templatehttps://gith …