Bezpieczne współdzielenie danych
Używaj kolejek i blokad
Bezpieczne współdzielenie danych to bezpłatna lekcja Python Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Python Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Python Academy zawiera 4 lekcji w sumie.
Problem warunku wyścigu
Gdy wiele wątków modyfikuje te same dane, ich operacje mogą się przeplatać i prowadzić do zniekształcenia wyniku. Jest to warunek wyścigu. Potrzebujemy narzędzi synchronizacji, aby bezpiecznie udostępniać dane.
import threading
counter = 0
def bump():
global counter
for _ in range(100000):
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter (may be < 200000):', counter)Lock na ratunek
Obiekt threading.Lock gwarantuje, że w danym momencie tylko jeden wątek wejdzie do sekcji krytycznej. Należy go przejąć za pomocą bloku with obejmującego modyfikację współdzielonych danych.
import threading
counter = 0
lock = threading.Lock()
def bump():
global counter
for _ in range(100000):
with lock:
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter:', counter)acquire i release
Blokady można obsługiwać ręcznie za pomocą acquire() i release(), ale forma z with jest bezpieczniejsza, ponieważ zwalnia blokadę nawet wtedy, gdy wystąpi błąd.
import threading
lock = threading.Lock()
lock.acquire()
try:
print('inside critical section')
finally:
lock.release()
print('released')Kolejki bezpieczne dla wątków
Klasa queue.Queue jest już bezpieczna dla wątków. Wątki mogą wywoływać put i get bez jawnego używania blokad, dlatego jest to preferowany sposób przekazywania danych.
import queue, threading
q = queue.Queue()
def producer():
for i in range(3):
q.put(i)
t = threading.Thread(target=producer)
t.start()
t.join()
while not q.empty():
print('got', q.get())Producent i konsument
Klasyczny wzorzec wygląda następująco: producenci umieszczają zadania w kolejce za pomocą put, a konsumenci pobierają je za pomocą get i przetwarzają. Kolejka obsługuje wszystkie blokady.
import queue, threading
q = queue.Queue()
results = []
def consumer():
while True:
item = q.get()
if item is None:
break
results.append(item * 10)
q.task_done()
t = threading.Thread(target=consumer)
t.start()
for i in range(3):
q.put(i)
q.put(None)
t.join()
print(results)join i task_done
q.join() blokuje działanie do chwili, gdy dla każdego elementu dodanego za pomocą put zostanie wywołane odpowiadające mu task_done(). Dzięki temu wątek główny może zaczekać na zakończenie całej pracy.
import queue, threading
q = queue.Queue()
def worker():
while True:
item = q.get()
q.task_done()
threading.Thread(target=worker, daemon=True).start()
for i in range(5):
q.put(i)
q.join()
print('all items processed')RLock do ponownego wejścia
Zwykła blokada Lock powoduje zakleszczenie, jeśli ten sam wątek spróbuje przejąć ją po raz drugi. Obiekt threading.RLock (blokada reentrantna) na to pozwala, zliczając przejęcia blokady.
import threading
rlock = threading.RLock()
def outer():
with rlock:
inner()
def inner():
with rlock:
print('re-entered safely')
outer()Event do sygnalizacji
Obiekt threading.Event pozwala jednemu wątkowi sygnalizować zdarzenia innym wątkom. Wątki wywołują wait(), dopóki inny wątek nie wywoła set().
import threading
start = threading.Event()
def worker():
start.wait()
print('go!')
t = threading.Thread(target=worker)
t.start()
print('setting event')
start.set()
t.join()Udostępnianie danych między procesami
Między procesami należy zamiast tego używać multiprocessing.Queue. Kolejka ta serializuje obiekty między oddzielnymi przestrzeniami pamięci.
import multiprocessing as mp
def producer(q):
for i in range(3):
q.put(i * i)
if __name__ == '__main__':
q = mp.Queue()
p = mp.Process(target=producer, args=(q,))
p.start()
p.join()
while not q.empty():
print('received', q.get())Unikanie zakleszczeń
Zakleszczenie występuje, gdy wątki czekają na blokady posiadane przez siebie nawzajem i nigdy nie mogą kontynuować działania. Można go uniknąć, zawsze przejmując wiele blokad w tej samej kolejności we wszystkich miejscach programu.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def safe():
with lock_a:
with lock_b:
print('acquired in consistent order')
safe()Preferowanie kolejek zamiast blokad
Jeśli to możliwe, należy preferować przekazywanie komunikatów za pomocą kolejek zamiast współdzielonego, modyfikowalnego stanu chronionego blokadami. Taki kod łatwiej analizować i jest znacznie mniej podatny na błędy.
import queue
q = queue.Queue()
for word in ['safe', 'simple', 'clear']:
q.put(word)
while not q.empty():
print(q.get())Szybki test
Sprawdź, czy rozumie Pan / rozumie Pani zasady bezpiecznego udostępniania danych.
Podsumowanie
Nauczył się Pan / Nauczyła się Pani bezpiecznie udostępniać dane:
- Warunki wyścigu prowadzą do uszkodzenia niezabezpieczonego współdzielonego stanu.
LockiRLockchronią sekcje krytyczne.queue.Queuejest bezpieczna dla wątków; między procesami należy używaćmultiprocessing.Queue.Eventsłuży do sygnalizowania między wątkami, a zachowanie stałej kolejności przejmowania blokad zapobiega zakleszczeniom.
Następny kurs: uruchamianie zewnętrznych programów za pomocą subprocess.
Często zadawane pytania
Czy lekcja „Bezpieczne współdzielenie danych” jest bezpłatna?
Tak — pełny tekst „Bezpieczne współdzielenie danych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Python Academy, przejdź na CoddyKit PRO. Kurs Python Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Bezpieczne współdzielenie danych”?
Używaj kolejek i blokad Ćwiczysz Python Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Python Academy?
Nie wymagamy żadnego doświadczenia. Python Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Bezpieczne współdzielenie danych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Python Academy?
Tak. Każda lekcja Python Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Wątki i GIL
- ThreadPoolExecutor
- Podstawy multiprocessing
- Bezpieczne współdzielenie danych