รายการมีความปลอดภัยต่อเธรดหรือไม่


155

.pop()ผมสังเกตเห็นว่ามันก็มักจะแนะนำให้ใช้คิวที่มีหลายกระทู้แทนของรายการและ เป็นเพราะรายการไม่ปลอดภัยสำหรับเธรดหรือด้วยเหตุผลอื่น


1
ยากที่จะบอกเสมอสิ่งที่รับประกันความถูกต้องของเธรดใน Python และเป็นเรื่องยากที่จะให้เหตุผลเกี่ยวกับความปลอดภัยของเธรดในนั้น แม้แต่ Bitcoin wallet Electrum ที่ได้รับความนิยมสูงก็ยังมีบั๊กที่เกิดขึ้นพร้อมกันที่อาจเกิดจากสิ่งนี้
sudo

คำตอบ:


182

รายการตัวเองปลอดภัยต่อเธรด ใน CPython GIL จะป้องกันการเข้าถึงพร้อมกันและการใช้งานอื่น ๆ ระมัดระวังที่จะใช้การล็อคแบบละเอียดหรือประเภทข้อมูลที่ซิงโครไนซ์สำหรับการใช้งานรายการ อย่างไรก็ตามในขณะที่รายการตัวเองไม่สามารถเสียหายได้โดยความพยายามในการเข้าถึงพร้อมกันข้อมูลของรายการจะไม่ได้รับการป้องกัน ตัวอย่างเช่น:

L[0] += 1

ไม่รับประกันว่าจะเพิ่ม L [0] ทีละคนถ้าเธรดอื่นทำสิ่งเดียวกันเพราะ+=ไม่ใช่การดำเนินการปรมาณู (การดำเนินการที่น้อยมากใน Python เป็นอะตอมจริงเนื่องจากส่วนใหญ่สามารถทำให้รหัส Python โดยพลการที่จะเรียก) คุณควรใช้คิวเพราะถ้าคุณเพียงแค่ใช้รายการที่ไม่มีการป้องกันคุณอาจได้รับหรือลบรายการที่ไม่ถูกต้องเพราะการแข่งขัน เงื่อนไข


1
Deque ยังปลอดภัยสำหรับเธรดหรือไม่? ดูเหมือนจะเหมาะสมกว่าสำหรับการใช้งานของฉัน
lemiant

20
วัตถุ Python ทั้งหมดมีเธรดความปลอดภัยชนิดเดียวกัน - ตัวเองไม่เสียหาย แต่ข้อมูลของพวกเขาอาจ collection.deque คือสิ่งที่อยู่เบื้องหลังวัตถุ Queue.Queue หากคุณเข้าถึงสิ่งต่าง ๆ จากสองเธรดคุณควรใช้วัตถุ Queue.Queue จริงๆ.
โธมัสวูสเตอร์

10
lemiant deque เป็นแบบปลอดภัยต่อเธรด จากบทที่ 2 ของ Fluent Python: "คลาส collection.deque เป็นคิวดับเบิลสิ้นสุดที่ปลอดภัยสำหรับเธรดที่ออกแบบมาสำหรับการแทรกและลบอย่างรวดเร็วจากปลายทั้งสองด้าน [... ] การดำเนินการต่อท้ายและ popleft เป็นแบบอะตอมดังนั้น deque จึงปลอดภัยที่จะ ใช้เป็น LIFO-queue ในแอพพลิเคชันแบบมัลติเธรดโดยไม่ต้องใช้การล็อค "
Al Sweigart

3
คำตอบนี้เกี่ยวกับ CPython หรือเกี่ยวกับ Python หรือไม่ อะไรคือคำตอบสำหรับ Python
user541686

@Nils: เอ่อ, หน้าแรกที่คุณเชื่อมโยงกับกล่าวว่างูใหญ่แทน CPython เพราะมันจะอธิบายภาษา Python และลิงค์ที่สองนั้นบอกว่ามีการใช้งานภาษา Python หลายครั้งเพียงอันเดียวที่ได้รับความนิยมมากขึ้น เมื่อพิจารณาคำถามเกี่ยวกับ Python คำตอบควรอธิบายสิ่งที่สามารถรับประกันได้ว่าจะเกิดขึ้นในการติดตั้ง Python ที่สอดคล้องกันไม่ใช่เฉพาะสิ่งที่เกิดขึ้นใน CPython โดยเฉพาะ
user541686

89

เพื่อชี้แจงจุดในคำตอบที่ดีเยี่ยมของโทมัสมันควรจะกล่าวว่าappend() เป็นที่ปลอดภัยด้าย

นี้เป็นเพราะมีความกังวลว่าข้อมูลถูกไม่อ่านจะอยู่ในสถานที่เดียวกันเมื่อเราไปเขียนไป การappend()ดำเนินการไม่ได้อ่านข้อมูล แต่จะเขียนข้อมูลไปยังรายการเท่านั้น


1
PyList_Append กำลังอ่านจากหน่วยความจำ คุณหมายถึงว่าการอ่านและเขียนของมันเกิดขึ้นในล็อก GIL เดียวกันหรือไม่ github.com/python/cpython/blob/…
amwinter

1
@amwinter ใช่การโทรทั้งหมดPyList_Appendนั้นทำได้ในการล็อค GIL เดียว มันได้รับการอ้างอิงไปยังวัตถุที่จะผนวก เนื้อหาของวัตถุที่อาจจะมีการเปลี่ยนแปลงหลังจากที่มีการประเมินและก่อนที่จะเรียกร้องให้PyList_Appendมีการกระทำ แต่มันจะยังคงเป็นวัตถุเดียวกันและท้ายได้อย่างปลอดภัย (ถ้าคุณทำlst.append(x); ok = lst[-1] is xแล้วokอาจจะเป็นเท็จของหลักสูตร) รหัสที่คุณอ้างอิงไม่ได้อ่านจากวัตถุที่ต่อท้ายยกเว้น INCREF มันอ่านและอาจจัดสรรใหม่ซึ่งเป็นรายการที่ผนวกเข้า
greggo

3
dotancohen 's ประเด็นก็คือว่าL[0] += xจะดำเนินการ__getitem__บนLแล้ว__setitem__บนL- ถ้าLสนับสนุน__iadd__ก็จะทำในสิ่งที่แตกต่างกันเล็กน้อยที่อินเตอร์เฟซวัตถุ แต่ยังคงมีการดำเนินการทั้งสองแยกจากกันบนLที่ระดับหลามล่าม (คุณจะเห็นพวกเขาใน รวบรวม bytecode) appendจะทำใน AA เรียกวิธีการเดียวในโค้ดไบต์
greggo

6
แล้วไงremoveล่ะ
acrazing

2
upvoted! ดังนั้นฉันสามารถผนวกในหนึ่งกระทู้อย่างต่อเนื่องและ pop ในหัวข้ออื่นได้หรือไม่
PirateApp

38

นี่คือรายการตัวอย่างของlistการดำเนินการที่ครอบคลุม แต่ไม่ครบถ้วนสมบูรณ์และไม่ว่าจะปลอดภัยหรือไม่ หวังว่าจะได้คำตอบเกี่ยวกับการobj in a_listสร้างภาษาที่นี่


ลิงค์แรกดูเหมือนจะตาย
displayname

2

ฉันเพิ่งมีกรณีนี้ที่ฉันต้องการผนวกเข้ากับรายการอย่างต่อเนื่องในหนึ่งเธรดวนรอบรายการและตรวจสอบว่ารายการนั้นพร้อมหรือไม่มันเป็น AsyncResult ในกรณีของฉันและลบออกจากรายการเฉพาะเมื่อมันพร้อม ฉันไม่พบตัวอย่างใด ๆ ที่แสดงให้เห็นถึงปัญหาของฉันอย่างชัดเจนนี่เป็นตัวอย่างที่แสดงให้เห็นถึงการเพิ่มรายการในหนึ่งเธรดอย่างต่อเนื่องและลบออกจากรายการเดียวกันในเธรดอื่นอย่างต่อเนื่องเวอร์ชั่นที่มีข้อบกพร่องทำงานได้อย่างง่ายดาย ไม่กี่ครั้งและคุณจะเห็นข้อผิดพลาด

รุ่นที่มีข้อบกพร่อง

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 = []
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)

เอาท์พุต

[] # Empty list

ข้อสรุป

ดังที่ได้กล่าวไว้ในคำตอบก่อนหน้านี้ในขณะที่การกระทำของการผนวกหรือ popping องค์ประกอบจากรายการตัวเองเป็นด้ายปลอดภัยสิ่งที่ไม่ปลอดภัยเธรดคือเมื่อคุณผนวกในหนึ่งกระทู้และป๊อปในอีก


6
รุ่นที่มีการล็อคมีพฤติกรรมเช่นเดียวกับที่ไม่มีการล็อค โดยทั่วไปแล้วข้อผิดพลาดเกิดขึ้นเนื่องจากพยายามลบบางสิ่งที่ไม่อยู่ในรายการ แต่ไม่เกี่ยวข้องกับความปลอดภัยของเธรด ลองรันเวอร์ชันด้วยการล็อกหลังจากเปลี่ยนลำดับการเริ่มต้นเช่นเริ่มต้น t2 ก่อนหน้า t1 และคุณจะเห็นข้อผิดพลาดเดียวกัน เมื่อใดก็ตามที่ t2 อยู่ก่อนหน้า t1 ข้อผิดพลาดจะเกิดขึ้นไม่ว่าคุณจะใช้การล็อกหรือไม่ก็ตาม
Dev

1
นอกจากนี้คุณควรใช้ตัวจัดการบริบท ( with r:) แทนการโทรอย่างชัดเจนr.acquire()และr.release()
GordonAitchJay

1
@GordonAitchJay 👍
Timothy C. Quinn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.