70
Reconsidering Multithreadin g Design Patterns @atsukanrock Aug. 2, 2014 めめめめめめ #5

Reconsidering Multithreading Design Patterns

Embed Size (px)

DESCRIPTION

Reconsidering Multithreading Design Patterns

Citation preview

Page 1: Reconsidering Multithreading Design Patterns

ReconsideringMultithreadingDesign Patterns

@atsukanrockAug. 2, 2014

めとべや東京 #5

Page 2: Reconsidering Multithreading Design Patterns

@atsukanrock

Loves:• C#• ASP.NET• Azure•DDD (Domain-Driven Design)

http://atsukanrock.hatenablog.com/https://github.com/atsukanrock/

Page 3: Reconsidering Multithreading Design Patterns

@atsukanrock

Loves:• C#• ASP.NET• Azure•DDD (Domain-Driven Design)

http://atsukanrock.hatenablog.com/https://github.com/atsukanrock/

http://jigokuno.com/?eid=162

Page 4: Reconsidering Multithreading Design Patterns

というわけで•文字ばっかですごめんなさい• 絵を描く時間がなかった。。。

•一緒にコード見ましょう

Page 5: Reconsidering Multithreading Design Patterns

このセッションのゴール

Page 6: Reconsidering Multithreading Design Patterns

この辺を伝えたい•NET における並列プログラミング•スレッドの排他制御•スレッドの協調•デザインパターン一覧• Producer-Consumer pattern•そして Cloud へ

Page 7: Reconsidering Multithreading Design Patterns

.NET における並列プログラミン

Page 9: Reconsidering Multithreading Design Patterns

進化してきた• Thread• ThreadPool• Asynchronous Programming Model  

(APM)• Begin/EndInvoke

• Event-based Asynchronous Pattern• BackgroundWorker

• Task-based Asynchronous Pattern• Reactive Extensions (Rx)• async/await

Page 10: Reconsidering Multithreading Design Patterns

async/await 様最高•書き方が超シンプルになる• シングルスレッドに見えるのに非同期にできる

• I/O 待ちの一本道処理はこれで勝つる• ネットワーク• ファイル• データベース ( ネットワーク + ファイル )

•→Web 系のほとんどをカバー

Page 11: Reconsidering Multithreading Design Patterns

async/await 様最高•… んだけれども並列処理は不得手•書くとしたら :• Parallel.ForEach ?• Task.Run からの Task.WhenAll ?• Select(async el => …) からの Task.WhenAll ?

• “Select は非同期時代の ForEach”• http://neue.cc/2013/12/04_435.html

•とりあえず書けるけど…• デッドロック怖い• スレッド数が増えすぎて OutOfMemory になった

らどうしよう

Page 12: Reconsidering Multithreading Design Patterns

async/await 様にも弱点が…•並列処理は async/await 様でも助けてくれない•とは言え今はマルチコア CPU の時代• CPU バウンドな処理は並列でやると早くなる、実際

正しい知識とデザインパターンで戦うのです !!

Page 13: Reconsidering Multithreading Design Patterns

CPU バウンドな処理って?•画像処理•大量のテキスト処理•音声処理•ビデオ処理

Page 14: Reconsidering Multithreading Design Patterns

というわけで•ここから、 CPU バウンドな処理に役立つ並列プログラミングを学びます

Page 15: Reconsidering Multithreading Design Patterns

スレッドの排他制御

Page 16: Reconsidering Multithreading Design Patterns

lock って書いたら•スレッド排他制御できる•↓ みたいな感じで lock の後の {} 内に入れるのは 1 スレッドだけ

lock (_lockObj){ _queue.Enqueue(item); Monitor.PulseAll(_lockObj);}

Page 17: Reconsidering Multithreading Design Patterns

lock イメージ図•スレッド 1 と 2 が _lockObj のロックを取ろうとしている

_lockObj

スレッド 1

スレッド 2

メソッド 1

メソッド 2

Page 18: Reconsidering Multithreading Design Patterns

lock イメージ図•スレッド 2 がロックを取った•スレッド 1 はロックが取れなかったので待たされている

_lockObj

スレッド 1

スレッド 2

メソッド 1

メソッド 2

Page 19: Reconsidering Multithreading Design Patterns

lock イメージ図•スレッド 2 が lock の後ろの {} を抜けた•スレッド 1 にチャンスが回ってきた!

Lock Object

スレッド 1

スレッド 2

メソッド 1

メソッド 2

Page 20: Reconsidering Multithreading Design Patterns

lock イメージ図•スレッド 1 がロックを取った

Lock Object

メソッド 1

メソッド 2

Page 21: Reconsidering Multithreading Design Patterns

lock イメージ図•スレッド 1 も lock{} 抜けた

Lock Object

メソッド 1

メソッド 2

Page 22: Reconsidering Multithreading Design Patterns

スレッドの協調

Page 23: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•Monitor.Wait メソッドで、 lock{} の中で他のスレッドにロックを譲る•↓ みたいな感じlock (_lockObj){ while (_queue.Count >= _capacity) { Monitor.Wait(_lockObj); } _queue.Enqueue(item); Monitor.PulseAll(_lockObj);}

Page 24: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•Monitor.Pulse(All) メソッドで、 Wait中の他のスレッドを起こして、ロック取得競争に仲間入りさせる•実は先スライドのコードにありました↓lock (_lockObj){ while (_queue.Count >= _capacity) { Monitor.Wait(_lockObj); } _queue.Enqueue(item); Monitor.PulseAll(_lockObj);}

Page 25: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 1 と 2 が _lockObj のロックを取り合って

_lockObj

スレッド 1

スレッド 2

メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

Page 26: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 2 がロック取得後、 Wait に到達した•スレッド 1 は待たされている_lockObj

スレッド 1

スレッド 2

メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

Page 27: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 2 が _lockObj の Wait セットに入ると、他スレッドにロック取得のチャンスが回ってくる

_lockObj

スレッド 1

スレッド 2

メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド2

Page 28: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 1 がロックを取得した•スレッド 2 は Wait セットで待っている

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド2

Page 29: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 1 が Pulse に到達した•スレッド 2 は Wait セットで待っている

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド2

Page 30: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•Wait セットのスレッド 2 に起きろPulse が届く

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド2

Page 31: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 2 がロック取得待ちに戻る•ロックはまだスレッド 1 が持っている

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド 2

Page 32: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 1 が lock{} を抜けた• _lockObj のロックが取得可能になった

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド 2

Page 33: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 2 がロックを再取得した•スレッド 2 の処理は Wait の後から再開される _lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド 2

Page 34: Reconsidering Multithreading Design Patterns

Wait / Pulse(All)•スレッド 2 が lock{} を抜けた• _lockObj のロックは解放される

_lockObj

スレッド 1メソッド 1

Monitor.Pulse(_lockObj)

メソッド 2

Monitor.Wait(_lockObj)

_lockObj の Wait セット

スレッド 2

Page 35: Reconsidering Multithreading Design Patterns

Pulse と PulseAll の違い• Pulse は Wait セットにいる n 個のスレッドのうち 1 つしか起こさない• PulseAll は Wait セット内の全てのスレッドを起こす

• Pulse の方が高速と言われるが、 Pulse回数が足りていないと寝っぱなしになるリスクが

•個人的には常に PulseAll 推奨

Page 36: Reconsidering Multithreading Design Patterns

一応他にも•↓ みたいなんあるけど• Manual/AutoResetEvent• Mutex

•Wait / Pulse(All) を上手く使えば大体の協調は実現できる• Manual/AutoResetEvent は

• 重たい(らしい)• 1 プロセス内で取れる数に上限がある(あった。今は不

明)• 使いまくったら突然死

• Mutex はそもそもプロセスまたいだ協調に使うもの• 他プロセスから見えるものが軽いわけない

Page 37: Reconsidering Multithreading Design Patterns

デザインパターン一覧

Page 38: Reconsidering Multithreading Design Patterns

何かいろいろある• Single Threaded Execution• Immutable•Guarded Suspension• Balking

Page 39: Reconsidering Multithreading Design Patterns

何かいろいろある• Producer-Consumer• Read-Write Lock• Thread-Per-Message•Worker Thread

Page 40: Reconsidering Multithreading Design Patterns

何かいろいろある• Future• Two-Phase Termination• Thread-Specific Storage• Active Object

Page 41: Reconsidering Multithreading Design Patterns

説明しきれんけど•大体のパターンは普段使い or 簡単に理解できるので、個人的に最も重要かつ美しいと感じている Producer-Consumerだけに絞って解説

Page 42: Reconsidering Multithreading Design Patterns

説明しきれんけど• Single Threaded Execution• 単に lock{} のこと

• Immutable• オブジェクトの状態がコンストラクタの後では変

えられないなら、マルチスレッドで排他制御 / 協調なしで扱っても安全• お手本 : System.String クラス

•Guarded Suspension• Producer-Consumer の特殊系とみなせる

• Balking• Producer-Consumer の特殊系とみなせる

Page 43: Reconsidering Multithreading Design Patterns

説明しきれんけど• Producer-Consumer• この後どっぷり説明

• Read-Write Lock• ReaderWriterLock クラスってのが .NET 1.0 の頃

から(!) BCL にある。要は DB と同じ

• Thread-Per-Message• 普通そう組むやろ

•Worker ThreadThreadPool のことと捉えて問題ない

Page 44: Reconsidering Multithreading Design Patterns

説明しきれんけど• Future• IAsyncResult とか Task ( Awaiter )みたいなも

ん。 JavaScript で言うと promiss

• Two-Phase Termination• CancellationToken での Cancel 的な。 Cancel

要求は無理やり殺さずフラグ立てるだけ。スレッド側がフラグチェックして安全に終了する

• Thread-Specific Storage• .NET 4.0 から BCL に ThreadLocal<T> クラスが

• Active Object• いろんなパターンの組み合わせらしい

Page 45: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern

Page 46: Reconsidering Multithreading Design Patterns

3種類のオブジェクト• Producer• Consumer• Channel

Page 47: Reconsidering Multithreading Design Patterns

Producer• Consumer への処理の Request を出す•例えるならわんこそば大会の調理係• 3 人で調理する• 競技者( Consumer )の目の前( Channel )が

一杯だったらちょっと待つ• 競技者の目の前が空いたら、そば( Request )を置いて調理場に戻り、次のそばを作り始める

Page 48: Reconsidering Multithreading Design Patterns

Consumer• Producer からの Request を処理する•例えるならわんこそば大会の競技者• 5 人で争う• 目の前( Channel )に置かれたそば

( Request )があればすぐに食べ始める• 目の前にそばが置かれていなければ、そばが置か

れるまで待つ(大問題 !! )

Page 49: Reconsidering Multithreading Design Patterns

Channel• Producer が Request を置く場所• Consumer が Request を取る場所•プログラム的には、 Producer やConsumer を待たせるのが最重要な役割

Page 50: Reconsidering Multithreading Design Patterns

Channel のサンプル• https://github.com/atsukanrock/MultithreadDesignPattern/blob/master/MultithreadDesignPattern/ProducerConsumer/Channel.cs•ポイント :• Add メソッドで容量( _capacity )いっぱいの場

合は Producer スレッドを待たせている( Monitor.Wait )• Take メソッドで空の場合は Consumer スレッド

を待たせている( Monitor.Wait )• Request 数の変化時に待っているスレッドを起こ

している( Monitor.PulseAll )

Page 51: Reconsidering Multithreading Design Patterns

Producer のサンプル• https://github.com/atsukanrock/MultithreadDesignPattern/blob/master/ProducerConsumer.ConsoleApp/Producer.cs• とその基底クラスたち

• 抽象化したのでちょっと分かりづらいです。。。

•ポイント :• スレッドを意識したコードがない

Page 53: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern の美しい所•スレッドの協調を意識しているのがChannel だけ• Producer と Consumer はフルパワーで働いている(つもりになっている)だけ• 作っていた / 食べていたと思っていたら待ってい

た !!

•複雑さの局所化•シンプルさを保てる• KISS ( Keep It Simple, Stupid )の原則、大事

です。

Page 54: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern の注意点• Producer の生産力と Consumer の消費力に差があり過ぎて、かつ Channel のCapacity が小さい場合、待ち状態のスレッドがたくさんになる

•NotifyAll でロックを取るスレッドが、Capacity 一杯 or 空状態を解消できないスレッドになる可能性が高くなる( NotifyAll のアルゴリズムがラウンドロビンなら良いんだけど、、、未調査)•NotifyAll の空打ちが頻発する

Page 55: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern の注意点

•NotifyAll の空打ちが頻発する

• Producer と Consumer のスレッド数決定を慎重にすること

前スライドからの矢印

Page 56: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern の発展形•典型的な(シンプルな) Channel はQueue ( FIFO )で実装される• Stack ( LIFO )にしても良いし•最後の Request だけ取って他のRequest は捨てるという形(“ LIFO-and-Clear” と名付けよう)もあり得る• 例えば Kinect プログラミングでは、センサーから

の入力情報が無慈悲に飛んでくる• ちょっと重たい画像処理をかける場合などには、

全部は処理しきれない基礎を理解していれば応用が効きます。

Page 57: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern の発展形•時間があったら LIFO-and-Clear をライブコーディング(無理やな)• LIFO-and-Clear程度なら Rx を上手く使えばデフォで書けるけんども• Rx は Producer-Consumer pattern の Channel

役を立派にこなしてくれる• ソースがマルチスレッドでもシリアライズしてく

れる(らしい)し• Sample メソッドで n ミリ秒に 1 回だけデータを拾ったり、 Buffer メソッドである程度溜めてから拾ったり

• Channel自体はシンプルなクラスで済むから、自分で書いてかゆい所に手を届かせるのもヨシ• かゆうま

Page 58: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern のデモ

Page 59: Reconsidering Multithreading Design Patterns

画像処理の高速化•手元のスマホで↓にアクセス !!• http://imgproc.cloudapp.net/

• 1日限定です。課金怖い

Page 60: Reconsidering Multithreading Design Patterns

画像処理の高速化•ソースは https://github.com/atsukanrock/MultithreadDesignPattern• ゴミ多いので注意されたし

• 当初、 Azure の Worker Role で処理させる目論見だった

• Emulator ではバッチリ動いた。ヒャッハー• Azure にデプローイ !!→動かぬ。ずっと Busy• 急遽方針変更。 WPF 上で全部やることに( 8/2 1:30

頃)• WPF にぎりぎり移しきった(イマココ)

Page 61: Reconsidering Multithreading Design Patterns

画像処理の高速化•時間があれば、ソースの説明• SignalR

• Publish from ASP.NET Web API• WPF client

• BlockingCollection<T>• BCL に含まれている Channel クラス• コンストラクター引数で Queue / Stack の切り替えが

可能• CompleteAdding メソッドで Producer の完了を

Channel に通知できる• Consumer は IsCompleted プロパティを見て終了判定

• WorkerBase<T>• 典型的な Consumer の基底クラス• 処理結果はイベントで返す→ Rx で受けるのが ( ・∀・ )イイ !!

Atsushi Kambara
Page 62: Reconsidering Multithreading Design Patterns

そして Cloud へ

Page 63: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern って…• Azure の Queue と Worker に似てる•その通り• Channel はインプロセスの Queue そのもの

Page 64: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern って…•Queue の場合は永続化されている

•対象外性に優れている• Producer や Consumer が急にお亡くなりになっても Queue は生きている。そう、 Queue は生きている

Page 65: Reconsidering Multithreading Design Patterns

Producer-Consumer pattern って…•Queue の場合は永続化されている

• Channel自体のパフォーマンスはもちろんインプロセスの方が良いが、 Request の追加 / 取得自体より処理の方が重たいのが普通だから、問題にならない•インプロセスではスケールアウトできないからすぐ上限が来るし

Page 66: Reconsidering Multithreading Design Patterns

Cloud 最高… ?!•データを Cloud に持っていくのでセキュリティがー•Worker をいっぱい立てたら課金がー

•エンタープライズでは意外と気軽に使えなかったりする•手軽にマルチスレッドデザインパターンを実装できると役に立つ

Page 67: Reconsidering Multithreading Design Patterns

Cloud 最高… ?!• BIG DATA (笑)の処理などプロセス境界を容易に超えられるものは Cloud へ• 節子、それ発明やない。 MapReduce や

•センサーデータ処理などプロセス境界超えられないものはマルチスレッドデザインパターンで

Page 68: Reconsidering Multithreading Design Patterns

まとめ

Atsushi Kambara
Page 69: Reconsidering Multithreading Design Patterns

大事なこと• async/await だけではマルチスレッドの協調が必要な並列処理は書けない•Wait / Pulse / PulseAll でおk• Immutable厨にならないように気をつけよう• 僕は昔なりました

• Producer-Consumer は超応用が効く

•Worker Role 死すべし• 嘘です勉強します。。。

Page 70: Reconsidering Multithreading Design Patterns

質疑応答

Atsushi Kambara