web-dev-qa-db-ja.com

Pythonでキューをスレッドセーフにする方法

これはずっと前にインタビューで私に尋ねられたことを認めますが、私はそれをチェックすることを決して気にしませんでした。

質問は単純でした、Python= Queueをスレッドセーフにする方法は?

私の答えは、インタープリターロック(GIL)が原因で、他のスレッドがスリープ/待機しているときに、1つのスレッドだけがキューから要素を取得するための呼び出しを行っているということでした。それが有効な答えであるかどうかはまだわかりませんでした。

面接官は不満を感じて尋ねたQueuesがJavaまたは.NETのPythonのGILを持たない.Net実装)でスレッドセーフかどうか上記のデータ構造にスレッドセーフ機能を実装します。

探してみたのですが、いつもつまづいているようですhow to use thread-safe queues

では、Queueまたは単純なlistをスレッドセーフにして競合状態を回避するにはどうすればよいでしょうか。

または、スレッドセーフGEventsのQueue実装で使用されているアルゴリズムまたは手法は何ですか?

6
Harsh Gupta

GILがPythonプログラムをスレッドセーフにすることは間違いです。インタープリタ自体がスレッドセーフになるだけです。

たとえば、非常に単純なLIFOキュー(別名:スタック)を見てみましょう。listは既にスタックとして使用できることを無視します。

_class Stack(object):
  def __init__(self, capacity):
    self.size = 0
    self.storage = [None] * capacity

  def Push(self, value):
    self.storage[self.size] = value
    self.size += 1

  def pop(self):
    self.size -= 1
    result = self.storage[self.size]
    self.storage[self.size] = None
    return result
_

これはスレッドセーフですか? GILの下で実行されているにもかかわらず、絶対にそうではありません。

次の一連のイベントを考えてみます。

  • スレッド1はいくつかの値を追加します

    _stack = Stack(5)
    stack.Push(1)
    stack.Push(2)
    stack.Push(3)
    _

    状態は_storage=[1, 2, 3, None, None]_、_size=3_になりました。

  • スレッド1は値stack.Push(4)を追加し、サイズを増やす前に中断されます

    _self.storage[self.size] = value
    # interrupted here
    self.size += 1
    _

    状態は_storage=[1, 2, 3, 4, None]_、_size=3_になりました。

  • スレッド2は、_3_である値stack.pop()を削除します。

    状態は_storage=[1, 2, None, 4, None]_、_size=2_になりました。

  • スレッド1が再開されます

    _self.storage[self.size] = value
    # resume here
    self.size += 1
    _

    状態は_storage=[1, 2, None, 4, None]_、_size=3_になりました。

その結果、スタックが破損します。プッシュされた値を取得できず、最上位の要素が空です。

GILはデータアクセスを線形化するだけですが、操作の順序はまだ予測できないため、これは通常のPython=開発者にはほとんど役に立ちません。つまり、GILはPythonレベルのロックとして使用できません。すべての変数の値が最新であることを保証するだけです(CまたはJavaではvolatile)Python GILなしの実装でも、互換性のためにこのプロパティを提供する必要があります。たとえば、 volatileメモリアクセスまたは独自のロックの使用 JythonはGILなしの実装ですdictlistなどのスレッドセーフ実装を具体的に使用しますオン。

Pythonは、スレッド間の操作の順序を保証しないため、スレッドセーフなデータ構造がロックを使用する必要があることは当然のことです。たとえば、標準ライブラリ _queue.Queue_ class @ v3.6.4 にはmutexメンバーと、そのミューテックスを使用するいくつかのcondvarsがあります。すべてのデータアクセスは適切に保護されています。ただし、このクラスは主にキューデータ構造を意図したものではないことに注意してください。ただし、複数のスレッド間のジョブキューとして、純粋なデータ構造は通常、ロックに関係しません。

もちろん、ロックやミューテックスはさまざまな理由で悪臭を放ちます。デッドロックの可能性があるため、およびロックの取得が遅いためです。結果として、 lock-free data structure には多くの関心があります。ハードウェアが特定のアトミック命令を提供する場合、そのようなアトミック操作でデータ構造を更新することが可能です。ポインタを置き換える。しかし、これはかなり難しくなる傾向があります。

6
amon