web-dev-qa-db-ja.com

リストはスレッドセーフですか?

リストと.pop()の代わりに、複数のスレッドを持つキューを使用することがしばしば推奨されることに気づきます。これは、リストがスレッドセーフではないため、または他の何らかの理由でですか?

131
lemiant

リスト自体はスレッドセーフです。 CPythonでは、GILはそれらへの同時アクセスから保護します。他の実装では、リスト実装にきめの細かいロックまたは同期データ型を使用するように注意します。ただし、リストthemselvesは同時アクセスを試みても破損することはありませんが、リストのdataは保護されません。例えば:

L[0] += 1

+=はアトミック操作ではないため、別のスレッドが同じことを行う場合、実際にL [0]を1増やすことは保証されません。 (ほとんどの場合、Pythonの操作はほとんどアトミックではありません。ほとんどの場合、任意のPythonコードが呼び出される可能性があるためです。)保護されていないリストを使用する場合、競合状態のために間違ったアイテムを取得または削除できます。

158
Thomas Wouters

Thomasの優れた答えのポイントを明確にするために、append()isスレッドセーフであることに言及する必要があります。

これは、readのデータがwriteに移動すると、同じ場所にあるという懸念がないためです。 append()操作はデータを読み取りません。リストにデータを書き込むだけです。

78
dotancohen

これは包括的な例ですが、網羅的ではない例のリストです of list操作と、スレッドセーフかどうか。 obj in a_list言語構成要素 ここ に関する回答を得ることを望んでいます。

34
Jonathan

私は最近、1つのスレッドでリストに連続して追加し、アイテムをループしてアイテムの準備ができているかどうかを確認する必要があるこのケースがありましたが、それは私の場合はAsyncResultであり、準備ができている場合にのみリストから削除します。私の問題を明確に示した例は見つかりませんでしたここに、あるスレッドのリストへの追加と、別のスレッドの同じリストからの削除を連続して示す例を示します。数回、エラーが表示されます

FLAWEDバージョン

import threading
import time

# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []

def add():
    for i in range(count):
        l.append(i)
        time.sleep(0.0001)

def remove():
    for i in range(count):
        l.remove(i)
        time.sleep(0.0001)


t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()

print(l)

エラー時に出力

Exception in thread Thread-63:
Traceback (most recent call last):
  File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
    l.remove(i)
ValueError: list.remove(x): x not in list

ロックを使用するバージョン

import threading
import time
count = 1000
l = []
r = threading.RLock()
def add():
    r.acquire()
    for i in range(count):
        l.append(i)
        time.sleep(0.0001)
    r.release()

def remove():
    r.acquire()
    for i in range(count):
        l.remove(i)
        time.sleep(0.0001)
    r.release()


t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()

print(l)

出力

[] # Empty list

結論

前の回答で述べたように、リスト自体から要素を追加またはポップする行為はスレッドセーフですが、スレッドセーフではないのは、あるスレッドに追加して別のスレッドにポップするときです

2
PirateApp