Apa itu "penyimpanan lokal utas" di Python, dan mengapa saya membutuhkannya?

100

Di Python secara khusus, bagaimana variabel dibagikan di antara utas?

Meskipun saya telah menggunakan threading.Threadsebelumnya, saya tidak pernah benar-benar memahami atau melihat contoh bagaimana variabel dibagikan. Apakah mereka terbagi antara utas utama dan anak-anak atau hanya di antara anak-anak? Kapan saya perlu menggunakan penyimpanan lokal utas untuk menghindari pembagian ini?

Saya telah melihat banyak peringatan tentang sinkronisasi akses ke data bersama di antara utas dengan menggunakan kunci tetapi saya belum melihat contoh masalah yang benar-benar bagus.

Terima kasih sebelumnya!

Mike
sumber
2
Judul tidak sesuai dengan pertanyaannya. Pertanyaannya adalah tentang berbagi variabel antar utas, judulnya menyiratkan bahwa ini secara khusus tentang penyimpanan lokal utas
Casebash
2
@Casebash: dari suara pertanyaan ini, Mike membaca bahwa TLS diperlukan untuk menghindari masalah yang disebabkan oleh data yang dibagikan, tetapi tidak jelas data mana yang dibagikan secara default, dengan apa dibagikan, dan bagaimana data itu dibagikan. Saya telah menyesuaikan judul agar lebih cocok dengan pertanyaannya.
Shog9

Jawaban:

83

Di Python, semuanya dibagikan, kecuali untuk variabel lokal-fungsi (karena setiap pemanggilan fungsi mendapatkan kumpulan lokal sendiri, dan utas selalu merupakan pemanggilan fungsi yang terpisah.) Dan bahkan kemudian, hanya variabel itu sendiri (nama yang merujuk ke objek) bersifat lokal untuk fungsi tersebut; objek itu sendiri selalu global, dan apa pun bisa merujuknya. The Threadobjek untuk thread tertentu bukan objek khusus dalam hal ini. Jika Anda menyimpan Threadobjek di suatu tempat yang dapat diakses semua thread (seperti variabel global), semua thread dapat mengakses satu Threadobjek tersebut. Jika Anda ingin mengubah apa pun yang dapat diakses oleh utas lain secara menyeluruh, Anda harus melindunginya dengan kunci. Dan tentu saja semua utas harus berbagi kunci yang sama ini, atau itu tidak akan efektif.

Jika Anda menginginkan penyimpanan lokal-utas yang sebenarnya, di situlah threading.localmasuk. Atribut threading.localtidak dibagi di antara utas; setiap utas hanya melihat atribut yang ditempatkan di sana. Jika Anda ingin tahu tentang implementasinya, sumbernya ada di _threading_local.py di pustaka standar.

Thomas Wouters
sumber
1
Bisakah Anda memberikan detail lebih lanjut tentang kalimat berikut? "Jika Anda ingin memodifikasi secara menyeluruh apa pun yang tidak hanya Anda buat di utas yang sama ini, dan tidak menyimpan di mana pun utas lain bisa mendapatkannya, Anda harus melindunginya dengan kunci."
changyuheng
@changyuheng: Berikut adalah penjelasan tentang apa itu aksi atom: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
Tom Busby
1
@TomBusby: Jika tidak ada thread lain yang bisa mendapatkannya, mengapa kita perlu melindunginya dengan kunci, yaitu mengapa kita perlu membuat proses atom?
changyuheng
2
Tolong bisakah Anda memberikan contoh singkat dari: "objek itu sendiri selalu global, dan apa pun bisa merujuknya". Dengan merujuk, anggap Anda maksud membaca dan bukan menetapkan / menambahkan?
variabel
@ Variabel: Saya pikir maksudnya nilai tidak memiliki ruang lingkup
pengguna1071847
75

Perhatikan kode berikut:

#/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 (). Mulai ()
Saya dipanggil dari Thread-2
Saya dipanggil dari Thread-1 

Di sini threading.local () digunakan sebagai cara cepat dan kotor untuk meneruskan beberapa data dari run () ke bar () tanpa mengubah antarmuka foo ().

Perhatikan bahwa menggunakan variabel global tidak akan berhasil:

#/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 (). Mulai ()
Saya dipanggil dari Thread-2
Saya dipanggil dari Thread-2 

Sementara itu, jika Anda mampu meneruskan data ini sebagai argumen foo () - itu akan menjadi cara yang lebih elegan dan dirancang dengan baik:

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

Tetapi ini tidak selalu memungkinkan saat menggunakan kode pihak ketiga atau yang dirancang dengan buruk.

ahatchkins
sumber
18

Anda dapat membuat penyimpanan lokal utas menggunakan threading.local().

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

Data yang disimpan ke tls akan menjadi unik untuk setiap utas yang akan membantu memastikan bahwa pembagian yang tidak disengaja tidak terjadi.

Aaron Maenpaa
sumber
2

Sama seperti di setiap bahasa lain, setiap utas Python memiliki akses ke variabel yang sama. Tidak ada perbedaan antara 'utas utama' dan utas turunan.

Satu perbedaan dengan Python adalah Global Interpreter Lock berarti hanya satu utas yang dapat menjalankan kode Python pada satu waktu. Namun, ini tidak banyak membantu dalam hal sinkronisasi akses, karena semua masalah pre-emption biasa masih berlaku, dan Anda harus menggunakan primitif threading seperti dalam bahasa lain. Ini berarti Anda perlu mempertimbangkan kembali jika Anda menggunakan utas untuk kinerja, namun.

Nick Johnson
sumber
0

Saya mungkin salah di sini. Jika Anda tahu sebaliknya, harap jelaskan karena ini akan membantu menjelaskan mengapa seseorang perlu menggunakan thread local ().

Pernyataan ini sepertinya tidak tepat, tidak salah: "Jika Anda ingin memodifikasi secara menyeluruh apa pun yang dapat diakses oleh utas lain, Anda harus melindunginya dengan kunci." Menurut saya pernyataan ini -> efektif <- benar tetapi tidak sepenuhnya akurat. Saya pikir istilah "atom" berarti bahwa penerjemah Python membuat potongan kode byte yang tidak menyisakan ruang untuk sinyal interupsi ke CPU.

Saya pikir operasi atom adalah potongan kode byte Python yang tidak memberikan akses ke interupsi. Pernyataan Python seperti "running = True" adalah atom. Anda tidak perlu mengunci CPU dari interupsi dalam kasus ini (saya yakin). Rincian kode byte Python aman dari gangguan utas.

Kode Python seperti "threads_running [5] = True" tidak bersifat atomik. Ada dua potongan kode byte Python di sini; satu untuk de-referensi list () untuk objek dan potongan kode byte lainnya untuk menetapkan nilai ke objek, dalam hal ini "tempat" dalam daftar. Sebuah interupsi dapat dimunculkan -> antara <- dua byte-code -> potongan <-. Itu adalah hal buruk yang terjadi.

Bagaimana thread local () berhubungan dengan "atomic"? Inilah sebabnya mengapa pernyataan itu tampaknya salah arah bagi saya. Jika tidak, bisakah Anda menjelaskan?

DevPlayer
sumber
1
Sepertinya ini sebuah jawaban, tetapi dilaporkan bermasalah, saya asumsikan karena pertanyaan yang diajukan. Saya akan menghindari meminta klarifikasi dalam jawaban. Untuk inilah komentar.
Dharman