MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

各言語におけるマルチスレッドプログラミング入門


増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

目次

はじめに

最近は、マルチプロセスで複数のプロセスを協調させる

マイクロサービスが利用されることが多いですが、

GUIやハードウェアアクセスがあるソフトウェアでは、

未だにマルチスレッドプログラミングをしないといけない時も多いです。

 

今回は、冒頭の本を元に

様々な言語でマルチスレッドプログラミングを

実施する際の、メモを残しておきたいと思います。

 

各言語でマルチスレッドプログラミング

各言語でマルチスレッドを実施する時の概要についてまとめておきます。

Java

Javaは様々な言語の中でも、

かなりマルチスレッドプログラミングがしやすい言語だと思います。

複数のスレッドで共有されるフィールドは

synchronizedやvolatileで守ることで、マルチスレッドプログラミングが可能です。

また、java.util.concurrentという標準ライブラリを使うことで、

よく使用するマルチスレッドプログラミングをしやすくなっています。

docs.oracle.com

Javaにおけるマルチスレッドプログラミングの基本に関しては、

冒頭の書籍を参考してください。

 

Python

Pythonには、マルチスレッド用の標準ライブラリとして、

threadingがあります。

docs.python.org

こちらを使うことで、JavaのThreadクラスのようにスレッドを生成したり、

synchronizedのような排他処理が可能になります。

(そもそも、こちらのライブラリは、

Javaのスレッドライブラリを元に、設計されたようです。)

 

特に、Conditionsオブジェクトを使うことで、Javaのような

synchronized, wait, notify, notify_all などが実現できるため、

Javaのマルチスレッドプログラミングに慣れている人にはおすすめです。

docs.python.org

hidemon.hatenadiary.jp

molpako.hatenablog.com

 

Pythonのスレッドの注意点としては、

PythonはGlobal Interpreter Lock:GILというメモリ管理機能により、

マルチスレッドのプログラミングをしても、

複数のコアを使いきるような処理は出来ません。

あくまでもI/O待ちなどの時に、

スループットを向上させる用途がメインです。

このような場合は、

並行処理ではなく、マルチプロセスによる並列処理をすることで、

マルチコアを使い切る処理ができます。

myenigma.hatenablog.com

 

マルチスレッドパターン

下記は、記事冒頭の書籍を元に、

各マルチスレッドプログラミングの代表的なパターンを、

各言語で実装したものです。

1. Single-Threaded Executionパターン

このパターンは、複数のスレッドがあるときに、

ある特定の処理は、複数のスレッドから

同時に実施されないようにするパターンです。

Javaコード

github.com

Pythonコード

github.com

2. Immutableパターン

このパターンは、

複数のスレッドであるリソースにアクセスしても大丈夫なように、

あるクラスのフィールドを不変(Immutable)にするパターンです。

 

Pythonコード

Pythonは、JavaやC++のように、finalやconstで

完全にクラスのフィールドをImmutableにするのが難しいため、

propertyマクロで、変更出来ないようにしています。

github.com

Javaコード

github.com

3. guarded_suspensionパターン

このパターンは、

ある条件が満たされるまで、

スレッドを待たせるパターンです。

Javaコード

github.com

Pythonコード

Pythonでは、threading.Conditionが、

waitやnotify, notify_allの関数を持つため、

Javaと同じような形でスレッドプログラミングを実現できます。

github.com

 

4. Balkingパターン

このパターンは、ある条件に達してない場合は、

スレッドの処理をやめさせるパターンです。

Java コード

github.com

Python

Pythonでも、get_thread()して、現在のthreadのオブジェクトを取得し、

nameフィールドで、threadの名前を取得できます。

github.com

 

5. Producer-Consumer

データを作成するスレッドと、

そのデータを利用するスレッドが別れているときに、

間に橋渡しのクラスを作って、

安全にデータをやり取りするパターンです。

それぞれの処理速度のずれを吸収することができます。

Javaコード

java_public_sandbox/malti_thread_lessons/fifth_chapter_producer_comsumer at master · AtsushiSakai/java_public_sandbox · GitHub

Pythonコード

Pythonでは、Producer-Consumerパターン用のキュークラスとして、

下記の標準ライブラリが準備されています。

docs.python.org

  上記のライブラリを使った、例が下記のサンプルコードです。

github.com

 

ちなみにマルチスレッドではなく、マルチプロセス用のqueueも準備されており、

docs.python.org

これを利用した並列処理の例は下記の記事で紹介しています。

myenigma.hatenablog.com

 

6. Read Write Lock

あるリソースに対して、

Read同士は同時にできるが、

Writeは排他制御されるパターンです。

 

リソースをWriteする頻度は少ないが、

Readする頻度が高い時に、Readの排他制御を無くすことで、

スループットを改善することができます。

 

Java

github.com

 

Python

github.com

7. Thread per messageパターン

一つの要求ごとに、新しいthreadをつくるパターンです。

リクエストが終わる前に、

メインのスレッドが動き始められるので、応答性が高くすることができます。

 

しかし、このパターンは、返り値を受け取らない場合のみ利用できます。

返り値が必要な場合は後述のFutureパターンを使います。

 

Java

github.com

 

Python

github.com

 

8. Worker Thread

Thread per messageは一つの要求毎に、

スレッドを作っていましたが、

スレッドを作るのは結構、

Worker Threadは最初に生成した複数のTheradを使い回す。

Java

github.com

Python

github.com

 

9. Future (promise)

ある処理を別のスレッドで実施し、その返り値も必要な場合、

まず要求を出して、future(先物)をもらい、

別のプロセスで返り値を計算しつつ、

そのfutureを元に、後ほど返り値をもらうパターンです。

このパターンはpromise(約束)と呼ばれることがあります。

Java

Javaでこのfutureを実装するときには、

  • java.util.concurrent.Callable

  • java.util.concurrent.Future

  • java.util.concurrent.FutureTask

  • java.util.concurrent.CompletableFuture;

などを使って、実装されます。

github.com

 

CompletableFutureとFutureの違いに関しては、こちらも参照ください。

myenigma.hatenablog.com

 

Python

Pythonでは、マルチスレッドでも、マルチプロセスでも、

下記のconcurrent.futuresモジュールを使います。

docs.python.org

下記がマルチスレッドにおけるfutureパターンのサンプルコードです。

github.com

上記のコードのように、

javaのinterfaceのようなコードは、

abc (抽象基底クラス)で実現できます。

docs.python.org

 

また、マルチプロセスにおけるFutureパターンは

下記の記事で紹介しています。

myenigma.hatenablog.com  

10. Two Phase Termination

このパターンは、スレッドで仕事をして、

スレッドを止めた後に、

終了処理を実施するパターンです。

 

Java

github.com

Python

下記がPythonのサンプルコードです。

github.com

 

前述の通り、PythonはGILがあるため、2つのスレッドが同時に処理をすることはありません。

ですので、Javaのvolatileのような処理は不要です。

また、Pythonのthreadingモジュールは、

自分自身のthreadを止めるinterruptのような関数も実装されていません。

 

11. Thread Specific Storage

このパターンは、javaのThreadLocalを使って、

Thread毎のリソースを管理するパターンです。

Thread毎にリソースを管理するので、同期処理を考えなくて良くなるのが特徴です。

Java

github.com

Python

github.com

Pythonでは、threading.localを使うことで、

スレッド毎の変数を作ることができます。

docs.python.org

  

12. Active Objectパターン

Threadを使って、RPCを実現するパターンです。

Java

github.com

 

Python

github.com

 

マルチスレッドプログラミングで注意すべきこと

いくつか、マルチスレッドプログラミング特有の

注意すべきことがあるので、まとめておきます。

reentrant lock (再入可能ロック)

eentrant lock (再入可能ロック)は同じオーナーであれば、

何度もロックをとれるロック機構のことです。

stackoverflow.com

www.baeldung.com

yuukiyg.hatenablog.jp

yosuke-furukawa.hatenablog.com

 

Javaでは、java.util.concurrent.locks.ReentrantLockとして標準提供されています。

docs.oracle.com

Pythonでも、threadingモジュール内で、reentrant lockは提供されています。

docs.python.org

 

reentrant lockのことを、再帰ミューテックスということがあります。

また、JavaのSynchronizedブロックも再入可能です。

 

このreentrant lockは、これはすでにロックを取得したかどうかを

管理するのが難しいときに便利です。

reentrant lockでないロックを使って、複数回ロックを取ると、

デットロックが発生する可能性があります。

 

但し、reentrant lockを使わなくて良い場合は使わずに、

普通のlockを使うほうが良いようです。

  

JavaのsynchronizedブロックとLockの実装クラス

synchronizedとLockの実装クラスでは同じようなことを実現できますが、

Lockの実装クラスの方が一般的に激しい競合下での性能が優れているようです。

また Lockの実装クラスでは、ロック取得時にタイムアウトを設定したり割り込みをできるようにしたりなど、

より一層細かい制御を行うことができます。

 

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com


増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

 

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com