Saya perhatikan bahwa sering disarankan untuk menggunakan antrian dengan banyak utas, bukan daftar dan .pop()
. Apakah ini karena daftar tidak aman untuk alasan lain, atau karena alasan lain?
Saya perhatikan bahwa sering disarankan untuk menggunakan antrian dengan banyak utas, bukan daftar dan .pop()
. Apakah ini karena daftar tidak aman untuk alasan lain, atau karena alasan lain?
Jawaban:
Daftar itu sendiri aman untuk thread. Dalam CPython, GIL melindungi terhadap akses bersamaan untuk mereka, dan implementasi lainnya berhati-hati untuk menggunakan kunci berbutir halus atau tipe data yang disinkronkan untuk implementasi daftar mereka. Namun, sementara daftar sendiri tidak bisa pergi korup dengan upaya untuk akses bersamaan, daftar ini data yang tidak dilindungi. Sebagai contoh:
L[0] += 1
tidak dijamin untuk benar-benar meningkatkan L [0] oleh satu jika thread lain melakukan hal yang sama, karena +=
bukan merupakan operasi atom. (Sangat, sangat sedikit operasi di Python sebenarnya atom, karena sebagian besar dari mereka dapat menyebabkan kode Python sewenang-wenang disebut.) Anda harus menggunakan Antrian karena jika Anda hanya menggunakan daftar yang tidak dilindungi, Anda mungkin mendapatkan atau menghapus item yang salah karena ras kondisi.
Untuk memperjelas titik dalam jawaban Thomas' baik, harus disebutkan bahwa append()
adalah thread aman.
Ini karena tidak ada kekhawatiran bahwa data yang sedang dibaca akan berada di tempat yang sama setelah kita menulisnya . The append()
operasi tidak membaca data, hanya menulis data ke dalam daftar.
PyList_Append
dilakukan dalam satu kunci GIL. Itu diberikan referensi ke objek untuk ditambahkan. Konten objek itu dapat diubah setelah dievaluasi dan sebelum panggilan ke PyList_Append
dilakukan. Tapi itu akan tetap menjadi objek yang sama, dan ditambahkan dengan aman (jika Anda melakukannya lst.append(x); ok = lst[-1] is x
, maka ok
mungkin False, tentu saja). Kode yang Anda referensi tidak membaca dari objek yang ditambahkan, kecuali untuk INCREF. Bunyinya, dan mungkin merealokasi, daftar yang ditambahkan.
L[0] += x
akan melakukan __getitem__
on L
dan kemudian __setitem__
on L
- jika L
mendukungnya __iadd__
akan melakukan sesuatu yang sedikit berbeda pada antarmuka objek, tetapi masih ada dua operasi terpisah pada L
level interpreter python (Anda akan melihatnya di dikompilasi dengan bytecode). Hal append
ini dilakukan dalam pemanggilan metode tunggal dalam bytecode.
remove
?
Berikut adalah daftar lengkap belum lengkap non-contoh dari list
operasi dan apakah atau tidak mereka thread aman. Berharap mendapatkan jawaban mengenai obj in a_list
konstruk bahasa di sini .
Saya baru-baru ini memiliki kasus ini di mana saya perlu menambahkan daftar terus-menerus dalam satu utas, loop melalui item dan memeriksa apakah item sudah siap, itu adalah AsyncResult dalam kasus saya dan menghapusnya dari daftar hanya jika sudah siap. Saya tidak dapat menemukan contoh yang menunjukkan masalah saya dengan jelas. Berikut adalah contoh yang menunjukkan penambahan ke daftar di satu utas secara terus-menerus dan menghapus dari daftar yang sama di utas lain terus-menerus beberapa kali dan Anda akan melihat kesalahan
Versi cacat
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)
Output saat KESALAHAN
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
Versi yang menggunakan kunci
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
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)
Keluaran
[] # Empty list
Kesimpulan
Seperti disebutkan dalam jawaban sebelumnya sementara tindakan menambahkan atau muncul elemen dari daftar itu sendiri adalah thread aman, yang tidak aman thread adalah ketika Anda menambahkan satu thread dan pop di yang lain
with r:
) daripada menelepon secara eksplisit r.acquire()
danr.release()