Python'da "iş parçacığı yerel depolama" nedir ve neden buna ihtiyacım var?


101

Özellikle Python'da, değişkenler iş parçacığı arasında nasıl paylaşılır?

Daha threading.Threadönce kullanmış olmama rağmen , değişkenlerin nasıl paylaşıldığına dair örnekleri hiç anlamadım veya görmedim. Ana konu ile çocuklar arasında mı yoksa sadece çocuklar arasında mı paylaşılıyor? Bu paylaşımı önlemek için iş parçacığı yerel depolamasını ne zaman kullanmam gerekir?

Kilitleri kullanarak iş parçacıkları arasında paylaşılan verilere erişimi senkronize etme konusunda birçok uyarı gördüm, ancak sorunun gerçekten iyi bir örneğini henüz görmedim.

Şimdiden teşekkürler!


2
Başlık soruyla uyuşmuyor. Soru, iş parçacıkları arasında değişkenlerin paylaşılmasıyla ilgilidir, başlık özellikle iş parçacığı yerel depolamayla ilgili olduğunu ima eder
Casebash

2
@Casebash: Bu sorunun sesinden Mike, paylaşılan verilerin neden olduğu sorunlardan kaçınmak için TLS'nin gerekli olduğunu okudu, ancak varsayılan olarak hangi verilerin paylaşıldığı, neyle paylaşıldığı ve nasıl paylaşıldığı konusunda net değildi. Başlığı soruyla daha iyi eşleşecek şekilde ayarladım.
Shog9

Yanıtlar:


85

Python'da, işlev yerel değişkenler dışında her şey paylaşılır (çünkü her işlev çağrısı kendi yerel kümesini alır ve evreler her zaman ayrı işlev çağrılarıdır.) Ve o zaman bile, yalnızca değişkenlerin kendileri (nesnelere atıfta bulunan isimler) işlev için yereldir; nesnelerin kendileri her zaman küreseldir ve herhangi bir şey onlara atıfta bulunabilir. ThreadBelirli bir iş parçacığı nesnesi bu konuda özel bir nesne değildir. ThreadNesneyi tüm iş parçacıklarının erişebileceği bir yerde (genel bir değişken gibi) depolarsanız, tüm iş parçacıkları o Threadnesneye erişebilir . Başka bir iş parçacığının erişebildiği herhangi bir şeyi atomik olarak değiştirmek istiyorsanız, onu bir kilitle korumanız gerekir. Ve tüm iş parçacıkları elbette bu aynı kilidi paylaşmalı, yoksa çok etkili olmaz.

Gerçek iş parçacığı yerel depolama istiyorsanız, burada threading.localdevreye girer. Öğelerinin öznitelikleri threading.localiş parçacıkları arasında paylaşılmaz; her iş parçacığı yalnızca oraya yerleştirdiği öznitelikleri görür. Uygulamasını merak ediyorsanız, kaynak standart kitaplıkta _threading_local.py içindedir .


1
Aşağıdaki cümle hakkında daha fazla ayrıntı verebilir misiniz lütfen? "Sadece bu aynı iş parçacığında yaratmadığınız herhangi bir şeyi atomik olarak değiştirmek istiyorsanız ve başka bir iş parçacığının ulaşabileceği herhangi bir yere kaydetmediyseniz, onu bir kilitle korumanız gerekir."
changyuheng

@changyuheng: İşte atomik eylemlerin ne olduğuna dair bir açıklama: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
Tom Busby

1
@TomBusby: Eğer ona ulaşabilecek başka bir iş parçacığı yoksa, neden onu bir kilitle korumamız gerekiyor, yani süreci neden atomik hale getirmemiz gerekiyor?
changyuheng

2
Lütfen hızlı bir örnek verebilir misiniz: "nesnelerin kendileri her zaman küreseldir ve bunlara her şey başvurabilir". Referans derken okuduğunuzu varsayalım ve atama / ekleme değil mi?
Değişken

@variable: Sanırım değerlerin kapsamı yok
user1071847

76

Aşağıdaki kodu göz önünde bulundurun:

#/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 (). Başla (); T (]. Başlangıç ​​()
Thread-2'den aradım
Konu-1'den aradım 

Burada threading.local (), bazı verileri foo () arayüzünü değiştirmeden run () 'dan bar ()' a aktarmanın hızlı ve kirli bir yolu olarak kullanılır.

Global değişkenleri kullanmanın işe yaramayacağını unutmayın:

#/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 (). Başla (); T (]. Başlangıç ​​()
Thread-2'den aradım
Thread-2'den aradım 

Bu arada, bu veriyi foo () argümanı olarak aktarmayı göze alabilseydiniz - bu daha zarif ve iyi tasarlanmış bir yol olurdu:

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())

Ancak, üçüncü taraf veya kötü tasarlanmış kod kullanılırken bu her zaman mümkün değildir.


18

Kullanarak iş parçacığı yerel depolaması oluşturabilirsiniz threading.local().

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

Tls'de depolanan veriler, her iş parçacığı için benzersiz olacak ve bu, istenmeyen paylaşımların olmamasını sağlamaya yardımcı olacaktır.


2

Tıpkı diğer tüm dillerde olduğu gibi, Python'daki her iş parçacığı aynı değişkenlere erişebilir. Ana iş parçacığı ile alt iş parçacığı arasında hiçbir ayrım yoktur.

Python ile bir fark, Global Yorumlayıcı Kilidinin aynı anda yalnızca bir iş parçacığının Python kodunu çalıştırabileceği anlamına gelmesidir. Erişimi senkronize etmek söz konusu olduğunda bu pek yardımcı olmuyor, ancak tüm olağan ön alım sorunları hala geçerli olduğundan ve diğer dillerde olduğu gibi iş parçacığı ilkelleri kullanmak zorundasınız. Bununla birlikte, performans için iş parçacığı kullanıp kullanmadığınızı yeniden gözden geçirmeniz gerektiği anlamına gelir.


0

Burada yanılıyor olabilirim. Eğer aksini biliyorsanız, lütfen açıklayınız, çünkü bu, bir kişinin neden local () thread kullanması gerektiğini açıklamaya yardımcı olacaktır.

Bu ifade yanlış değil, yanlış görünüyor: "Başka bir iş parçacığının erişebildiği herhangi bir şeyi atomik olarak değiştirmek istiyorsanız, onu bir kilitle korumalısınız." Sanırım bu ifade -> etkili bir şekilde <- doğru ama tamamen doğru değil. "Atomik" teriminin, Python yorumlayıcısının CPU'ya bir kesinti sinyali için yer bırakmayan bir bayt kodu parçası oluşturduğu anlamına geldiğini düşündüm.

Atomik işlemlerin kesintilere erişim sağlamayan Python bayt kodu parçaları olduğunu düşündüm. "Running = True" gibi Python ifadeleri atomiktir. Bu durumda CPU'yu kesintilerden kilitlemenize gerek yoktur (inanıyorum). Python bayt kodu dökümü, iş parçacığı kesintisine karşı güvenlidir.

"Thread_running [5] = True" gibi Python kodu atomik değildir. Burada iki parça Python bayt kodu vardır; biri bir nesne için list () 'e ve bir nesneye bir değer atamak için başka bir bayt kodu parçasına, bu durumda listedeki bir "yer" e referanstan kurtulmak için. <- iki bayt kodu -> öbek <- arasında bir kesme yükseltilebilir. Bu kötü şeyler oldu.

Thread local () ile "atomik" arasında nasıl bir ilişki vardır? Bu yüzden ifade bana yanlış yönlendiriyor. Eğer değilse açıklayabilir misin?


1
Bu bir cevap gibi görünüyor, ancak sorulan sorulardan dolayı sorunlu olarak rapor edildi. Cevapta açıklama istemekten kaçınırdım. Yorumlar bunun için.
Dharman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.