Question: En raison de l'utilisation de "GIL" par python, Python est-il capable d'exécuter ses threads simultanément?
Info:
Après avoir lu ceci , je suis plutôt incertaine de savoir si Python est capable de tirer parti d’un processeur multicœur. Aussi bien que soit le python, il est vraiment bizarre de penser qu’il manque une capacité aussi puissante. Donc, me sentant incertain, j'ai décidé de demander ici Si j'écris un programme multi-thread, sera-t-il capable de s'exécuter simultanément sur plusieurs cœurs?
La réponse est "oui, mais ..."
Mais cPython ne le peut pas lorsque vous utilisez des threads normaux pour la simultanéité.
Vous pouvez utiliser quelque chose comme multiprocessing
, celery
ou mpi4py
pour scinder le travail parallèle en un autre processus;
Ou vous pouvez utiliser quelque chose comme Jython ou IronPython pour utiliser un autre interpréteur qui n’a pas de GIL.
Une solution plus souple consiste à utiliser des bibliothèques qui ne gênent pas le GIL pour les tâches de processeur lourdes, par exemple numpy
peut faire le gros travail sans conserver le GIL, afin que les autres threads python puissent continuer. Vous pouvez également utiliser la bibliothèque ctypes
de cette manière.
Si vous n'effectuez pas de travail lié au processeur, vous pouvez ignorer complètement le problème GIL, car Python n'acquerra pas le fichier GIL tant qu'il attend l'IO.
Python threads ne peut tirer parti de nombreux cœurs. Cela est dû à un détail d'implémentation interne appelé le verrou d'interprète global (GIL) dans l'implémentation C de python (cPython) qui est presque certainement ce que vous utilisez.
La solution de contournement est le module multiprocessing
http://www.python.org/dev/peps/pep-0371/ qui a été développé à cette fin.
Documentation: http://docs.python.org/library/multiprocessing.html
(Ou utilisez un langage parallèle.)
CPython (l'implémentation classique et répandue de Python) ne peut pas avoir plusieurs threads exécutant le bytecode Python en même temps. Cela signifie que les programmes liés à l'informatique n'utiliseront qu'un seul noyau. Les opérations d'E/S et l'informatique se déroulant à l'intérieur des extensions C (telles que numpy) peuvent fonctionner simultanément.
D'autres implémentations de Python (telles que Jython ou PyPy) peuvent se comporter différemment, je suis moins clair sur leurs détails.
La recommandation habituelle est d'utiliser plusieurs processus plutôt que plusieurs threads.
exemple de code prenant les 4 cœurs de mon Ubuntu 14.04, Python 2.7 64 bits.
import time
import threading
def t():
with open('/dev/urandom') as f:
for x in xrange(100):
f.read(4 * 65535)
if __== '__main__':
start_time = time.time()
t()
t()
t()
t()
print "Sequential run time: %.2f seconds" % (time.time() - start_time)
start_time = time.time()
t1 = threading.Thread(target=t)
t2 = threading.Thread(target=t)
t3 = threading.Thread(target=t)
t4 = threading.Thread(target=t)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print "Parallel run time: %.2f seconds" % (time.time() - start_time)
résultat:
$ python 1.py
Sequential run time: 3.69 seconds
Parallel run time: 4.82 seconds
Les threads partagent un processus et un processus s'exécute sur un cœur, mais vous pouvez utiliser le module de multitraitement de python pour appeler vos fonctions dans des processus distincts et utiliser d'autres cœurs, ou vous pouvez utiliser le module de sous-processus, qui peut également exécuter votre code et le code non python. .
Sur Raspberry Pi 3 Modèle B Rev 1.2
pi@raspberrypi:~/smart/cpu $ python2 core.py
Sequential run time: 2.34 seconds
Parallel run time: 1.74 seconds
pi@raspberrypi:~/smart/cpu $ python2.7 core.py
Sequential run time: 2.34 seconds
Parallel run time: 1.74 seconds
pi@raspberrypi:~/smart/cpu $ python3 core.py
Sequential run time: 2.32 seconds
Parallel run time: 1.74 seconds
pi@raspberrypi:~/smart/cpu $ python3.4 core.py
Sequential run time: 2.32 seconds
Parallel run time: 1.74 seconds
J'ai converti le script en Python3 et l'ai exécuté sur mon Raspberry Pi 3B +:
import time
import threading
def t():
with open('/dev/urandom', 'rb') as f:
for x in range(100):
f.read(4 * 65535)
if __== '__main__':
start_time = time.time()
t()
t()
t()
t()
print("Sequential run time: %.2f seconds" % (time.time() - start_time))
start_time = time.time()
t1 = threading.Thread(target=t)
t2 = threading.Thread(target=t)
t3 = threading.Thread(target=t)
t4 = threading.Thread(target=t)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print("Parallel run time: %.2f seconds" % (time.time() - start_time))
python3 t.py
Sequential run time: 2.10 seconds
Parallel run time: 1.41 seconds
Pour moi, courir en parallèle était plus rapide.