“ ที่จัดเก็บเธรดในเครื่อง” ใน Python คืออะไรและเหตุใดฉันจึงต้องใช้


101

ใน Python โดยเฉพาะตัวแปรจะแชร์ระหว่างเธรดได้อย่างไร

แม้ว่าฉันจะเคยใช้มาthreading.Threadก่อน แต่ฉันไม่เคยเข้าใจหรือเห็นตัวอย่างของการแบ่งปันตัวแปรเลย มีการแบ่งปันระหว่างกระทู้หลักกับเด็ก ๆ หรือเฉพาะในกลุ่มเด็ก ๆ เท่านั้น? ฉันจะต้องใช้ที่จัดเก็บเธรดในเครื่องเมื่อใดเพื่อหลีกเลี่ยงการแชร์นี้

ฉันเห็นคำเตือนมากมายเกี่ยวกับการซิงโครไนซ์การเข้าถึงข้อมูลที่แชร์ระหว่างเธรดโดยใช้การล็อก แต่ฉันยังไม่เห็นตัวอย่างที่ดีของปัญหา

ขอบคุณล่วงหน้า!


2
ชื่อไม่ตรงกับคำถาม คำถามคือจะทำอย่างไรกับการแบ่งปันตัวแปรระหว่างเธรดหัวเรื่องบอกเป็นนัยว่าเฉพาะเกี่ยวกับการจัดเก็บเธรดในเครื่อง
Casebash

2
@Casebash: จากเสียงของคำถามนี้ Mike อ่านว่า TLS มีความจำเป็นเพื่อหลีกเลี่ยงปัญหาที่เกิดจากข้อมูลที่แชร์ แต่ก็ไม่ชัดเจนว่าข้อมูลใดถูกแชร์โดยค่าเริ่มต้นแชร์กับอะไรและแบ่งปันอย่างไร ฉันได้ปรับชื่อให้เข้ากับคำถามมากขึ้นแล้ว
Shog9

คำตอบ:


85

ใน Python ทุกอย่างจะถูกใช้ร่วมกันยกเว้นตัวแปร function-local (เนื่องจากการเรียกใช้ฟังก์ชันแต่ละครั้งได้รับชุด local ของตัวเองและ thread จะแยกการเรียกใช้ฟังก์ชันออกจากกันเสมอ) และถึงแม้จะมีเพียงตัวแปรเท่านั้น (ชื่อที่อ้างถึงวัตถุ) อยู่ในท้องถิ่นของฟังก์ชัน วัตถุนั้นเป็นของโลกเสมอและสิ่งใด ๆ ก็สามารถอ้างถึงได้ Threadวัตถุสำหรับหัวข้อโดยเฉพาะอย่างยิ่งไม่ได้เป็นวัตถุพิเศษในเรื่องนี้ หากคุณเก็บThreadวัตถุไว้ที่ใดที่หนึ่งเธรดทั้งหมดสามารถเข้าถึงได้ (เช่นตัวแปรส่วนกลาง) เธรดทั้งหมดจะสามารถเข้าถึงThreadอ็อบเจ็กต์นั้นได้ หากคุณต้องการแก้ไขสิ่งใดก็ตามที่เธรดอื่นเข้าถึงได้ในอะตอมคุณต้องป้องกันด้วยการล็อก และแน่นอนว่าเธรดทั้งหมดจะต้องแชร์ล็อกเดียวกันนี้ไม่เช่นนั้นจะไม่มีประสิทธิภาพมากนัก

หากคุณต้องการพื้นที่จัดเก็บเธรด - โลคัลจริงนั่นคือสิ่งที่threading.localมาในแอตทริบิวต์ของthreading.localจะไม่ใช้ร่วมกันระหว่างเธรด แต่ละเธรดจะเห็นเฉพาะแอตทริบิวต์ที่วางไว้ในนั้น หากคุณสงสัยเกี่ยวกับการใช้งานแหล่งที่มาจะอยู่ใน_threading_local.pyในไลบรารีมาตรฐาน


1
คุณสามารถให้รายละเอียดเพิ่มเติมเกี่ยวกับประโยคต่อไปนี้ได้หรือไม่? "ถ้าคุณต้องการแก้ไขสิ่งที่คุณไม่ได้เพิ่งสร้างขึ้นในเธรดเดียวกันนี้และไม่ได้เก็บเธรดอื่นไว้ที่ใดคุณต้องป้องกันด้วยการล็อก"
changyuheng

@changyuheng: นี่คือคำอธิบายว่าการกระทำของอะตอมคืออะไร: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
Tom Busby

1
@ TomBusby: หากไม่มีเธรดอื่น ๆ ที่สามารถเข้าถึงได้ทำไมเราต้องป้องกันด้วยการล็อคกล่าวคือทำไมเราต้องทำให้กระบวนการปรมาณู?
changyuheng

2
โปรดยกตัวอย่างสั้น ๆ เกี่ยวกับ: "ออบเจ็กต์ต่าง ๆ มักจะเป็นของโลกและทุกสิ่งสามารถอ้างถึงได้" โดยการอ้างอิงถือว่าคุณหมายถึงอ่านและไม่กำหนด / ผนวก?
ตัวแปร

@variable: ฉันคิดว่าเขาหมายถึงคุณค่าที่ไม่มีขอบเขต
user1071847

76

พิจารณารหัสต่อไปนี้:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread, local

data = local()

def bar():
    print("I'm called from", data.v)

def foo():
    bar()

class T(Thread):
    def run(self):
        sleep(random())
        data.v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
>> T (). start (); T (). start ()
ฉันถูกเรียกจากเธรด -2
ฉันถูกเรียกจากเธรด -1 

ที่นี่ threading.local () ใช้เป็นวิธีที่รวดเร็วและสกปรกในการส่งผ่านข้อมูลบางส่วนจาก run () ไปยัง bar () โดยไม่ต้องเปลี่ยนอินเทอร์เฟซของ foo ()

โปรดทราบว่าการใช้ตัวแปรส่วนกลางจะไม่ทำเคล็ดลับ:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread

def bar():
    global v
    print("I'm called from", v)

def foo():
    bar()

class T(Thread):
    def run(self):
        global v
        sleep(random())
        v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
>> T (). start (); T (). start ()
ฉันถูกเรียกจากเธรด -2
ฉันถูกเรียกจากเธรด -2 

ในขณะเดียวกันหากคุณสามารถส่งผ่านข้อมูลนี้เป็นอาร์กิวเมนต์ของ foo () - มันจะเป็นวิธีที่หรูหราและออกแบบมาอย่างดี:

from threading import Thread

def bar(v):
    print("I'm called from", v)

def foo(v):
    bar(v)

class T(Thread):
    def run(self):
        foo(self.getName())

แต่ไม่สามารถทำได้เสมอไปเมื่อใช้รหัสของบุคคลที่สามหรือรหัสที่ออกแบบมาไม่ดี


18

คุณสามารถสร้างที่จัดเก็บเธรดโลคัลโดยใช้threading.local().

>>> tls = threading.local()
>>> tls.x = 4 
>>> tls.x
4

ข้อมูลที่จัดเก็บไว้ใน tls จะไม่ซ้ำกันสำหรับแต่ละเธรดซึ่งจะช่วยให้มั่นใจได้ว่าจะไม่เกิดการแบ่งปันโดยไม่ได้ตั้งใจ


2

เช่นเดียวกับในภาษาอื่น ๆ ทุกเธรดใน Python สามารถเข้าถึงตัวแปรเดียวกันได้ ไม่มีความแตกต่างระหว่าง 'เธรดหลัก' และเธรดย่อย

ความแตกต่างอย่างหนึ่งของ Python คือ Global Interpreter Lock หมายความว่าเธรดเดียวเท่านั้นที่สามารถรันโค้ด Python ได้ในแต่ละครั้ง สิ่งนี้ไม่ได้ช่วยอะไรมากนักในเรื่องการซิงโครไนซ์การเข้าถึง แต่เนื่องจากปัญหาการปล่อยล่วงหน้าตามปกติทั้งหมดยังคงมีผลอยู่และคุณต้องใช้เธรดแบบดั้งเดิมเช่นเดียวกับในภาษาอื่น ๆ หมายความว่าคุณต้องพิจารณาใหม่ว่าคุณใช้เธรดเพื่อประสิทธิภาพหรือไม่


0

ฉันอาจจะผิดที่นี่ หากคุณทราบเป็นอย่างอื่นโปรดอธิบายเนื่องจากจะช่วยอธิบายได้ว่าเหตุใดจึงต้องใช้เธรด local ()

คำสั่งนี้ดูเหมือนจะไม่ผิด: "หากคุณต้องการแก้ไขสิ่งใดก็ตามที่เธรดอื่นเข้าถึงได้ในอะตอมคุณต้องป้องกันด้วยการล็อก" ฉันคิดว่าข้อความนี้ -> ได้ผล <- ถูกต้อง แต่ไม่ถูกต้องทั้งหมด ฉันคิดว่าคำว่า "อะตอม" หมายความว่าล่าม Python สร้างชิ้นส่วนไบต์โค้ดที่ไม่เหลือที่ว่างสำหรับสัญญาณขัดจังหวะไปยัง CPU

ฉันคิดว่าการทำงานของอะตอมเป็นส่วนหนึ่งของโค้ด Python byte ที่ไม่ให้การเข้าถึงการขัดจังหวะ คำสั่ง Python เช่น "running = True" คือ atomic คุณไม่จำเป็นต้องล็อค CPU จากการขัดจังหวะในกรณีนี้ (ฉันเชื่อว่า) การแบ่งรหัสไบต์ของ Python ปลอดภัยจากการหยุดชะงักของเธรด

โค้ด Python เช่น "threads_running [5] = True" ไม่ใช่ atomic มีโค้ดไบต์ของ Python สองส่วนที่นี่ หนึ่งเพื่อยกเลิกการอ้างอิงรายการ () สำหรับอ็อบเจ็กต์และโค้ดไบต์อื่นเพื่อกำหนดค่าให้กับอ็อบเจ็กต์ในกรณีนี้คือ "สถานที่" ในรายการ การขัดจังหวะสามารถเพิ่มขึ้น -> ระหว่าง <- สองไบต์ - โค้ด -> ส่วน <- นั่นคือสิ่งที่ไม่ดีเกิดขึ้น

thread local () เกี่ยวข้องกับ "ปรมาณู" อย่างไร? นี่คือสาเหตุที่ดูเหมือนว่าข้อความดังกล่าวส่งผลให้ฉันเข้าใจผิด ถ้าไม่สามารถอธิบายได้?


1
ดูเหมือนจะเป็นคำตอบ แต่ได้รับรายงานว่าเป็นปัญหาฉันคิดว่าเนื่องจากคำถามที่ถาม ฉันจะหลีกเลี่ยงการขอคำชี้แจงในคำตอบ นี่คือความคิดเห็นสำหรับ
ธรรมทาน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.