2016-07-08 17 views
21

Jeśli mam całkowitą i, to nie jest to bezpieczne i += 1 na wielu wątków:Czy rozszerzenie listy pythonów (np. L + = [1]) jest bezpieczne dla wątków?

>>> i = 0 
>>> def increment_i(): 
...  global i 
...  for j in range(1000): i += 1 
... 
>>> threads = [threading.Thread(target=increment_i) for j in range(10)] 
>>> for thread in threads: thread.start() 
... 
>>> for thread in threads: thread.join() 
... 
>>> i 
4858 # Not 10000 

Jednak jeśli mam listę l, to wydaje się bezpieczne l += [1] na wielu wątków:

>>> l = [] 
>>> def extend_l(): 
...  global l 
...  for j in range(1000): l += [1] 
... 
>>> threads = [threading.Thread(target=extend_l) for j in range(10)] 
>>> for thread in threads: thread.start() 
... 
>>> for thread in threads: thread.join() 
... 
>>> len(l) 
10000 

Czy l += [1] jest gwarantowany jako bezpieczny dla wątków? Jeśli tak, czy dotyczy to wszystkich implementacji Pythona lub tylko CPython?

Edit: Wydaje się, że l += [1] jest bezpieczny wątku, ale nie jest l = l + [1] ...

>>> l = [] 
>>> def extend_l(): 
...  global l 
...  for j in range(1000): l = l + [1] 
... 
>>> threads = [threading.Thread(target=extend_l) for j in range(10)] 
>>> for thread in threads: thread.start() 
... 
>>> for thread in threads: thread.join() 
... 
>>> len(l) 
3305 # Not 10000 
+1

To dla mnie naprawdę zaskakujące - nie spodziewałbym się, że tak się stanie. Mam nadzieję, że ktoś wyraźnie to wyjaśni. –

+2

Chociaż przegłosowałem to, myślę, że stwierdzenie "Które operacje w Pythonie są gwarantowane jako bezpieczne, a które nie?" potępia pytanie o szerokie zamknięcie. Czy mógłbyś to przeformułować? – Bathsheba

+1

Czekając na pewne ograniczenia dodane do pytania, znalazłem effBot ponownie: [Jaka mutacja globalnej wartości jest bezpieczna dla wątków?] (Http://effbot.org/pyfaq/what-kinds-of-global-value-mutation -are-thread-safe.htm) ciekawa lektura.Proponuję przeformułowanie na: "Jakie rodzaje globalnej mutacji wartości są bezpieczne dla wątków", aby być miłym zagrożeniem ;-) W odniesieniu do próbki listy: Lista jest bezpieczna dla wątków w swoich operacjach, ale same dane nie są "zabezpieczone" przez pojemnik. Tak więc każdy dostęp do zawartości zmieniającej element listy będzie cierpieć jako liczba całkowita "+ = 1". – Dilettant

Odpowiedz

14

Nie jest szczęśliwy ;-) Odpowiedź na to pytanie. Nie ma w tym nic gwarantowanego, co możesz potwierdzić, zauważając, że instrukcja obsługi Pythona nie daje żadnych gwarancji co do atomowości.

W CPython jest to kwestia pragmatyki. Jak mówi krótka część artykułu w effbot,

W teorii oznacza to, że dokładna księgowość wymaga dokładnego zrozumienia implementacji kodu bajtowego PVM [Python Virtual Machine].

I taka jest prawda. CPython ekspert wie L += [x] jest atomowy, bo wiedzą, wszystkie z poniższych:

  • += kompiluje się do kodu bajtowego INPLACE_ADD.
  • Implementacja INPLACE_ADD dla obiektów listy jest napisana w całości w C (kod Pythona nie znajduje się na ścieżce wykonania, więc GIL nie może zostać zwolniony między bajtów).
  • W listobject.c implementacja INPLACE_ADD jest funkcją list_inplace_concat() i nic podczas jej wykonywania nie musi wykonywać żadnego kodu Pythona użytkownika (jeśli tak, GIL może ponownie zostać zwolniony).

To wszystko może wydawać się niesamowicie trudne do zachowania prosto, ale dla kogoś, kto ma wiedzę na temat wewnętrznych elementów CPythona (w momencie pisania tego artykułu), tak naprawdę nie jest. W rzeczywistości, biorąc pod uwagę, że głębokość wiedzy, to wszystko jest raczej oczywiste ;-)

Więc w gruncie pragmatyki eksperci CPython zawsze swobodnie powoływać się na, że ​​„operacje, które«wyglądają atomowy»powinno być naprawdę atomowy” , a także kierowało niektórymi decyzjami językowymi. Na przykład, operacja brakuje listy effbot użytkownika (dodane do języka po napisaniu tego artykułu):

x = D.pop(y) # or ... 
x = D.pop(y, default) 

jeden argument (w tym czasie) za dodanie dict.pop() właśnie, że oczywiste implementacja C byłoby atomowy , podczas gdy w trakcie używania (w tym czasie) Alternatywnie:

x = D[y] 
del D[y] 

nie atomowe (wyszukiwania i usunięcie dokonuje się za pomocą różnych bytecodes tak gwinty można przełączać między nimi).

Ale doktorzy nigdy nie mówili , że.pop() był atomowy i nigdy nie będzie. Jest to rzecz "przyzwolenie dorosłych": jeśli jesteś ekspertem na tyle, aby świadomie wykorzystać to, nie potrzebujesz trzymania ręki. Jeśli nie masz wystarczającej wiedzy eksperckiej, obowiązuje ostatnie zdanie artykułu o effbot:

W razie wątpliwości użyj muteksu!

W gruncie pragmatycznego konieczności rdzeniowe deweloperzy nigdy nie złamie niepodzielność przykładów effbot (lub z D.pop() lub D.setdefault()) w CPython. Inne implementacje nie są wcale zobowiązane do naśladowania tych pragmatycznych wyborów. Rzeczywiście, ponieważ atomowość w tych przypadkach opiera się na specyficznej formie kodu bajtowego CPythona w połączeniu z korzystaniem przez CPythona z globalnej blokady interpretera, która może być zwolniona tylko między bajtodami, może to być prawdziwy ból dla innych implementacji, aby naśladować te kody.

I nigdy nie wiadomo: niektóre przyszłe wersje CPython mogą również usunąć GIL! Wątpię w to, ale teoretycznie jest to możliwe. Ale jeśli tak się stanie, założę się, że utrzymana zostanie również wersja równoległa zachowująca GIL, ponieważ cała masa kodu (zwłaszcza modułów rozszerzeń napisanych w C) opiera się również na GIL dla bezpieczeństwa wątków.

warto powtarzać:

W przypadku wątpliwości, należy mutex!

+0

Dziękuję za tę wyczerpującą odpowiedź. Podsumowanie wydaje się być "albo użyć muteksa, albo polegać na nieudokumentowanych szczegółach implementacji CPython". Przypuszczalnie istnieje wiele kodu Pythona, który opiera się na "operacjach, które" wyglądają na atomowe "powinny być naprawdę atomowe". Jak mówisz, oznacza to, że CPython nigdy nie może złamać atomowości niektórych operacji. Inne implementacje mogą (i mogą okazać się łatwiejsze) zrobić to kosztem złamania tego kodu. – user200783

+0

Jednak wydaje się, że alternatywne implementacje Pythona próbują dopasować wskazówki atomowości CPython: Powtórzyłem wyniki w pytaniu PyPy, IronPython i Jython. Mam wiele szacunku dla twórców tych środowisk wykonawczych: bycie wysoce kompatybilnym z CPythonem w celu uruchomienia jak największej ilości kodu Pythona, nawet kod, który opiera się na nieudokumentowanych szczegółach implementacji, musi być ogromną ilością pracy. – user200783

+0

Niestety, nie ma sposobu, abyś wiedział, nie stając się ekspertem od każdej implementacji: samo testowanie nie może udowodnić braku złych zachowań związanych z rasą. Na przykład w implementacji CPythona pojawiły się subtelne wątki błędów, które przez lata były nieodkryte, aż do momentu, w którym nietypowa kombinacja HW, OS i obciążenia spowodowała, że ​​wyścigi były zawsze możliwe, ale nigdy wcześniej nie były widziane. Niezbyt często, tak, ale "_know_" jest silnym słowem ;-) –

9

Od http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm:

Operacje, które zastępują inne obiekty mogą powoływać się na te inne obiekty __del__ metoda, gdy ich licznik odniesienia osiągnie zero, a to może mieć wpływ na rzeczy. Jest to szczególnie ważne w przypadku masowych aktualizacji słowników i list.

następujące operacje są atomowej (L, L1, L2 są listy, D, D1, D2 dicts, x, y są przedmioty, i, j są typu int):

L.append(x) 
L1.extend(L2) 
x = L[i] 
x = L.pop() 
L1[i:j] = L2 
L.sort() 
x = y 
x.field = y 
D[x] = y 
D1.update(D2) 
D.keys() 

Te aren” T:

i = i+1 
L.append(L[-1]) 
L[i] = L[j] 
D[x] = D[x] + 1 

Powyżej jest czysto CPython specyficzne i mogą się różnić w różnych Python implementacja takich jak pypy.

Nawiasem mówiąc jest kwestią otwartą do dokumentowania operacji atomowych Python - https://bugs.python.org/issue15339

+1

OP zapytał także o inne implementacje Pythona i chciałbym wiedzieć, że to też ... Czy GIL działa identycznie dla wszystkich tych operacji we wszystkich implementacjach (które faktycznie mają GIL)? –

+0

Dziękuję za te linki. Wydanie 15339 ma 4 lata - nie wydaje się, żeby ktokolwiek spieszył się z dokumentowaniem czegokolwiek na temat bezpieczeństwa wątków w Pythonie. Ta podstawowa właściwość języka pozostaje szczegółem implementacji jego referencyjnej implementacji. – user200783

Powiązane problemy