Deep Dive into Chromium Threading Primitives

[Chromium Browser Advent Calendar 2017](https://qiita.com/advent-calendar/2017/chromium)の23日目の記事です。

# まえおき
Advent Calendarを順に読んできたみなさんは、そろそろChromiumにcontributeしたくなってきている頃かと思います。この記事では、Chromium中で使われるスレッドまわりの内部ライブラリと、標準ライブラリとの違いを紹介します。わりと頻繁に変わるので、自分で使うときには要確認です。

## ブラウザプロセス、レンダラプロセス
Chromiumはマルチプロセス[^1]なブラウザですが、Blink部分とそれ以外[^2]、もしくはレンダラプロセス[^3]とブラウザプロセス[^4]ではだいぶ様子が異なります。今回はブラウザプロセスの話。大部分の処理をメインスレッドで行うレンダラプロセスと違って、ブラウザプロセスでは複数のスレッドを飛び回って処理を進めています。
[^1]: ブラウザプロセス、レンダラプロセスの他に、GPUプロセス、Pluginプロセスなど、他にもいくつか種類がいます。
[^2]: 例えばBlinkのWebKit由来のコードとChromiumのコードで、まだ統合できていない部分によるもの。他、GCに関わる部分。BlinkにはC++部分にもGCがありChromiumには無い。
[^3]: レンダラプロセスはHTMLやJSなどのuntrustedなコードを扱い、システムへのアクセス権を落とした状態で走る。おおむね1タブに1プロセス作られる。
[^4]: ブラウザプロセスは全体でひとつ。通常のアプリケーションの権限で走り、ファイルシステムやネットワークへのアクセスは一手に引き受ける。

## ネームドスレッドとスレッドプール
ブラウザプロセスで使用するスレッドは、[UIスレッド，IOスレッドなどの名前付きのもの](https://cs.chromium.org/chromium/src/content/public/browser/browser_thread.h?sq=package:chromium&l=66)と、スレッドプールに属するものに大別できます。UIスレッドはブラウザプロセスのメインスレッドで、ブラウザUIやChromiumのプロファイル、Extension APIの受け口などを担当しています。IOスレッドはソケットの端点やIPCの受け口で、レンダラからのリクエストの受け答えをしています。

## UIスレッドとIOスレッド
UIスレッドとIOスレッドの大きな違いは、スレッドのメッセージループの構造の違いです。
UIスレッドのメッセージループはプラットフォームのネイティブなもの[^5]。IOスレッドのメッセージループはファイルやネットワーク、IPCの完了待ちをできるように[^6]作られています。
半年ほど前までは、UI, IOに加えてFILEスレッド、DBスレッド、CACHEスレッドなど、役割別にスレッドがありましたが、今ではUIとIO以外は非推奨で、スレッドプールに統合されています。
[^5]: Windowsでは`WaitForMultipleObjectEx`と`PeekMessage`で回る伝統的なWindowsアプリのループ。Androidでは`android.os.Handler`と`android.os.Looper`、Linuxでは`glib`を使ったもの。
[^6]: WindowsではIO Completion Port, 他ではepollやkqueueをlibevent経由で使っている。

## ブロック禁止
重い処理や完了待ちが必要な処理をUIスレッドやIOスレッドでするのは禁物です。ブラウザプロセスのネームドスレッドはブラウザインスタンス全体でひとつずつだけなので、誰かが専有してしまうと、他の全ての処理が滞ってしまいます。
完了待ちが発生しうる処理は、同期版の`read`, `write`, `connect`, `accept`はもちろん、syscallに非同期版が存在しない`open`, `stat`なども含まれます[^7]。`mmap`でのファイルの読み書きは、予期せぬ場所で入出力待ちが発生する可能性があるため、基本的には使われません。
[^7]: Linuxでの/procの読み出しは、ブロックしないのでそのまま実行しています。

# Threading Primitives
## [`base::Thread`](https://cs.chromium.org/chromium/src/base/threading/thread.h?sq=package:chromium&dr=CSs&l=59), [`base::MessageLoop`](https://cs.chromium.org/chromium/src/base/message_loop/message_loop.h?type=cs&sq=package:chromium&l=75), [`base::TaskScheduler`](https://cs.chromium.org/chromium/src/base/task_scheduler/task_scheduler.h?sq=package:chromium&l=51)
スレッド周りのライブラリで比較的低レイヤにいるのがこの３つ。`base::Thread`はその名の通りスレッドの抽象化。プロダクションコードで自分で作ることはあまりなく、ユニットテストの中でスレッドが必要になることはたまにあります。
インスタンスを作って`Start()`して、`base::BindOnce`でタスクを作り、`PostTask()`でタスクを投げます。後述の`base::MessageLoop`は自動で作られます。

```c++
void DoSomething() {}

base::Thread thread;
thread.Start();
thread.task_runner()->PostTask(
    FROM_HERE,
    base::BindOnce(&DoSomething);
thread.Stop();
```

`base::MessageLoop`は、名前はメッセージループですが、実態はタスクキューです。ユーザ側が呼び出したい関数を引数付きで指定すると、`base::MessageLoop`中で順番に実行されます。こちらも大抵は他のユーティリティ経由で使うだけで、直接使うことはあまりありません。

`base::TaskScheduler`は内部にスレッドプールを持ち、`base::TaskRunner`経由で投げられたタスクを適当なスレッドで実行します。負荷に応じたスレッドの数やタスクの優先順位の調節も担当しています。

## TaskRunner, SequencedTaskRunner, SingleThreadTaskRunner
スレッドを飛び回るときに直接使うのがこれらの`base::TaskRunner`たちです。この順番に継承関係があります。C++20に入るかもしれない`executor`に近いものです。
`PostTask()`や`PostDelayedTask()`経由で渡されたタスクは、裏にいるスレッドやスレッドプール上で適当に実行されます。複数スレッドを使うクラスやコンポーネントを設計するときには、外部からTaskRunnerを指定できるようにして、テスト時にタスクの実行順制御をしやすくするのが一般的です。
`base::SingleThreadTaskRunner`に投げられたタスクは常に特定のスレッド上で実行されます。Thread Local Storageを使うコードやThread IDに依存した何かをしているときには、SingleThreadTaskRunnerを使って型レベルで制約を明示します。
`base::SequencedTaskRunner`はもう少し制約がゆるく、投げられたタスクは順番に実行され、前のタスクが終わるまでは次のタスクが実行されないことだけを保証します。個々のタスクは別々のスレッドで実行されるかもしれません。複数のタスク間で共有されるデータがある場合でも、タスクが同一のSequencedTaskRunnerで走るのならば、ロックなどでの排他制御は不要です。特に事情がなければこれを使うのがよいかと思います。
`base::TaskRunner`には上記の制約はなく、投げられたタスクが適当なスレッドで同時に実行されるのを許します。外部のデータに依存しないタスクに適しています。

## `base::Bind{Once,Repeating}()` & `base::{Once,Repeating}Callback<>`
`base::OnceCallback<>`はTaskRunnerに投げるタスクを表現するクラステンプレートで、`base::BindOnce()`はCallbackを生成する関数です。
C++11の`std::bind()`や`std::function<>`に相当するもので、Chromiumでの用途に合わせて、標準ライブラリにあるものとはちょっと違うセマンティクスや制約が入っています。

```c++
int Foo(int x, int y) { return x * y; }
base::OnceCallback<int(int)> cb = base::BindOnce(&Foo, 7);
CHECK(std::move(cb).Run(6) == 42);

base::RepeatingCallback<int(int)> cb2 = base::BindRepeating(&Foo, 123);
CHECK(cb2.Run(4) == 492);
```

この例では、関数`Foo`にBindOnceを使い、引数を一つ部分適用したオブジェクトを作っています。`Run()`は残りの引数を受け付け、保存されていた引数と一緒に`Foo`を呼び出します。

`base::RepeatingCallback<>`はコピー可能，何度でも呼び出せるタイプで、`std::function<>`に近いものです。ただし`std::function<>`と違って内部状態を参照カウント型のストレージで持っているので、`cb2`をコピーしても、内部に持っている`123`はコピーされません。
`base::OnceCallback<>`はMove-onlyかつ一度だけ呼び出せるタイプで、C++20に入るかもしれない`std::unique_function<>`相当です。`base::OnceCallback<>`も`std::unique_function<>`と違って内部状態は別なストレージに持っているので、move時にはBindされた引数それぞれはmoveされません。

TaskRunnerに投げるタスクは、引数なし，戻り値なしのOnceCallbackかRepeatingCallbackで、それぞれOnceClosure, RepeatingClosureと別名がつけられています。

スレッド間移動にTaskRunnerとBindを使うときには、RepeatingではなくOnceを使うのがおすすめです。Repeatingの方は内部で参照カウントを使っている関係上、内部ストレージに保存されたオブジェクトが削除されるスレッドが非決定的で、わかりにくいバグが入り込む余地がありますし、Onceの方は保存された引数をrvalueで渡すので、型や値渡しな引数についてはコピーが減る場合があります。また、Onceの方は後述の`base::Passed()`を使わなくてもmove-onlyな型をBindできます。

## `base::Owned()`, `base::Passed()`, `base::ConstRef()`, `base::Unretained()`, `base::RetainedRef()`
`base::BindOnce()`や`base::BindRepeating()`は、部分適用に使う値を内部に保存しますが、値の保持の仕方をいくらかカスタマイズできます。`base::ConstRef()`は`std::cref()`相当、Bindされるオブジェクトを`base::ConstRef()`で包んでおくと、内部ストレージは参照のみを保持し、コピーしません。
`base::Passed()`はmove-onlyな型を`BindRepeating()`で扱う時に必要で、内部ストレージから対象の関数に渡すときに`std::move()`します。
生ポインタを`base::Owned()`に包んで渡すと、Callbackの破棄と一緒に包まれたポインタも`delete`されます。
参照カウントを持ったオブジェクトへのポインタを`RetainedRef()`に包んで渡すと、Callbackの寿命にあわせてカウントの上げ下げをしてくれます。
`base::Unretained()`は、歴史的事情の産物です。諸事情から、Bindのthisポインタの位置に生ポインタが渡されると、参照カウントの上げ下げが試みられますが、`base::Unretained()`はこの挙動からのopt-outを指示します。

## Atomic
基本的に標準ライブラリの`std::atomic<>`を使います。歴史的経緯から独自実装のものが残っていますが、新たに何かを書くときには考慮する必要はないかと思います。

## Lock
`std::mutex`と`std::unique_lock`相当で独自実装の`base::Lock`[^10]と`base::AutoLock`があります。pthreadやC++標準ライブラリのものと比べて、特殊な部分は特にありません。独自実装を維持する理由も特にないので、いずれ`std::mutex`や`std::unique_lock`に移行することになるかと思います。

[^10]: Windowsでは[Slim Reader/Writer Locks](https://msdn.microsoft.com/library/windows/desktop/aa904937(v=vs.85).aspx), POSIXなOSではpthread_mutexで実装。