リストと.pop()
の代わりに、複数のスレッドを持つキューを使用することがしばしば推奨されることに気づきます。これは、リストがスレッドセーフではないため、または他の何らかの理由でですか?
リスト自体はスレッドセーフです。 CPythonでは、GILはそれらへの同時アクセスから保護します。他の実装では、リスト実装にきめの細かいロックまたは同期データ型を使用するように注意します。ただし、リストthemselvesは同時アクセスを試みても破損することはありませんが、リストのdataは保護されません。例えば:
L[0] += 1
+=
はアトミック操作ではないため、別のスレッドが同じことを行う場合、実際にL [0]を1増やすことは保証されません。 (ほとんどの場合、Pythonの操作はほとんどアトミックではありません。ほとんどの場合、任意のPythonコードが呼び出される可能性があるためです。)保護されていないリストを使用する場合、競合状態のために間違ったアイテムを取得または削除できます。
Thomasの優れた答えのポイントを明確にするために、append()
isスレッドセーフであることに言及する必要があります。
これは、readのデータがwriteに移動すると、同じ場所にあるという懸念がないためです。 append()
操作はデータを読み取りません。リストにデータを書き込むだけです。
これは包括的な例ですが、網羅的ではない例のリストです of list
操作と、スレッドセーフかどうか。 obj in a_list
言語構成要素 ここ に関する回答を得ることを望んでいます。
私は最近、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
結論
前の回答で述べたように、リスト自体から要素を追加またはポップする行為はスレッドセーフですが、スレッドセーフではないのは、あるスレッドに追加して別のスレッドにポップするときです