programmation multithread

En fait, après la création d'un thread, le thread ne conserve pas toujours un état. Son état est à peu près le suivant :

Nouveau créé

Runnable ready. En attente de planification

En cours d'exécution

Bloqué. Un blocage peut survenir en mode Wait Locked Sleeping

Dead

Les threads ont différents états et différents types. Il peut être grossièrement divisé en :

Thème principal

Sous-thread

Thème démon (thread d'arrière-plan)

Thème de premier plan

Après une brève compréhension de ceux-ci, nous commençons à examiner l'utilisation spécifique du code.

1. Création de threads

Python fournit deux modules pour les opérations multithread, à savoir le thread et le threading

Le premier est un module de niveau relativement bas, utilisé pour les opérations de niveau inférieur, et n'est généralement pas adapté. pour le développement au niveau des applications. Couramment utilisé.

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import time
import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('thread {}, @number: {}'.format(self.name, i))
            time.sleep(1)
def main():
    print("Start main threading")
    # 创建三个线程
    threads = [MyThread() for i in range(3)]
    # 启动三个线程
    for t in threads:
        t.start()
    print("End Main threading")
if __name__ == '__main__':
    main()

Résultats d'exécution :

Start main threading
thread Thread-1, @number: 0
thread Thread-2, @number: 0
thread Thread-3, @number: 0
End Main threading
thread Thread-2, @number: 1
thread Thread-1, @number: 1
thread Thread-3, @number: 1
thread Thread-1, @number: 2
thread Thread-3, @number: 2
thread Thread-2, @number: 2
thread Thread-2, @number: 3
thread Thread-3, @number: 3
thread Thread-1, @number: 3
thread Thread-3, @number: 4
thread Thread-2, @number: 4
thread Thread-1, @number: 4

Notez que les résultats de sortie des différents environnements ici sont définitivement différents.

2. Fusion de threads (méthode de jointure)

D'après les résultats imprimés dans l'exemple ci-dessus, une fois le thread principal terminé, le thread enfant est toujours en cours d'exécution. Nous avons donc besoin que le thread principal attende que le thread enfant ait fini de s'exécuter avant de quitter. Que devons-nous faire ?

À ce stade, vous devez utiliser la méthode de jointure.

Dans l'exemple ci-dessus, ajoutez un morceau de code comme suit :

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import time
import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('thread {}, @number: {}'.format(self.name, i))
            time.sleep(1)
def main():
    print("Start main threading")
    # 创建三个线程
    threads = [MyThread() for i in range(3)]
    # 启动三个线程
    for t in threads:
        t.start()
    # 一次让新创建的线程执行 join
    for t in threads:
        t.join()
    print("End Main threading")
if __name__ == '__main__':
    main()

D'après les résultats imprimés, il est clairement visible que par rapport aux résultats imprimés dans l'exemple ci-dessus, le thread principal se termine après avoir attendu que le thread enfant finir de courir.

Start main threading
thread Thread-1, @number: 0
thread Thread-2, @number: 0
thread Thread-3, @number: 0
thread Thread-1, @number: 1
thread Thread-3, @number: 1
thread Thread-2, @number: 1
thread Thread-2, @number: 2
thread Thread-1, @number: 2
thread Thread-3, @number: 2
thread Thread-2, @number: 3
thread Thread-1, @number: 3
thread Thread-3, @number: 3
thread Thread-3, @number: 4
thread Thread-2, @number: 4
thread Thread-1, @number: 4
End Main threading

3. Synchronisation des threads et verrouillage mutex

L'utilisation du chargement de threads pour obtenir des données entraîne généralement une désynchronisation des données. Bien sûr, nous pouvons verrouiller la ressource à ce moment-là, c'est-à-dire que le thread accédant à la ressource doit obtenir le verrou avant d'y accéder.

Le module de threading nous offre une fonction Lock.

lock = threading.Lock()

Acquérir le verrou dans le thread

lock.acquire()

Après utilisation, nous devons absolument libérer le verrou

lock.release()

Bien sûr, afin de prendre en charge plusieurs requêtes pour la même ressource dans le même thread, Python fournit un verrou réentrant (RLock ). RLock maintient en interne un verrou et une variable de compteur. Le compteur enregistre le nombre d'acquisitions, de sorte que la ressource puisse être requise plusieurs fois. Jusqu'à ce que toutes les acquisitions d'un thread soient libérées, d'autres threads peuvent obtenir la ressource.

Alors comment créer un verrou de réentrée ? C'est aussi une question de code :

r_lock = threading.RLock()

4. Variable de condition de condition

Les verrous pratiques peuvent réaliser la synchronisation des threads, mais dans des environnements plus complexes, certains jugements conditionnels doivent être portés sur les verrous. Python fournit des objets Condition. L'objet Condition peut être utilisé pour traiter les données une fois que certains événements sont déclenchés ou que des conditions spécifiques sont remplies. En plus de la méthode d'acquisition et de la méthode de libération de l'objet Lock, Condition fournit également des méthodes d'attente et de notification. Le thread acquiert d’abord un verrou de variable de condition. Si les conditions sont insuffisantes, le thread attend. Si les conditions sont remplies, le thread est exécuté et il peut même notifier les autres threads. Les autres threads en état d'attente réévalueront les conditions après avoir reçu la notification.

La variable de condition peut être vue comme différents threads acquérant le verrou les uns après les autres. Si la condition n'est pas remplie, elle peut être comprise comme étant jetée dans un pool d'attente (Lock ou RLock). Informez directement les autres threads, puis réévaluez la condition. Ce processus est répété en continu pour résoudre des problèmes de synchronisation complexes.

35192f0e58595b25a0c422efd13ef05.png

Ce modèle est souvent utilisé dans le modèle producteur-consommateur. Jetez un œil aux exemples suivants d'acheteurs et de vendeurs d'achats en ligne :

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import threading, time
class Consumer(threading.Thread):
    def __init__(self, cond, name):
        # 初始化
        super(Consumer, self).__init__()
        self.cond = cond
        self.name = name
    def run(self):
        # 确保先运行Seeker中的方法
        time.sleep(1)
        self.cond.acquire()
        print(self.name + ': 我这两件商品一起买,可以便宜点吗')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 我已经提交订单了,你修改下价格')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 收到,我支付成功了')
        self.cond.notify()
        self.cond.release()
        print(self.name + ': 等待收货')
class Producer(threading.Thread):
    def __init__(self, cond, name):
        super(Producer, self).__init__()
        self.cond = cond
        self.name = name
    def run(self):
        self.cond.acquire()
        # 释放对琐的占用,同时线程挂起在这里,直到被 notify 并重新占有琐。
        self.cond.wait()
        print(self.name + ': 可以的,你提交订单吧')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 好了,已经修改了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 嗯,收款成功,马上给你发货')
        self.cond.release()
        print(self.name + ': 发货商品')
cond = threading.Condition()
consumer = Consumer(cond, '买家(两点水)')
producer = Producer(cond, '卖家(三点水)')
consumer.start()
producer.start()

Les résultats de sortie sont les suivants :

买家(两点水): 我这两件商品一起买,可以便宜点吗
卖家(三点水): 可以的,你提交订单吧
买家(两点水): 我已经提交订单了,你修改下价格
卖家(三点水): 好了,已经修改了
买家(两点水): 收到,我支付成功了
买家(两点水): 等待收货
卖家(三点水): 嗯,收款成功,马上给你发货
卖家(三点水): 发货商品

5. communication

Si le programme contient plusieurs threads, et ces threads doivent inévitablement communiquer entre eux. Alors, comment pouvons-nous échanger des informations ou des données en toute sécurité entre ces threads ?

Le moyen le plus sûr d'envoyer des données d'un thread à un autre est probablement d'utiliser une file d'attente de la bibliothèque de files d'attente. Crée un objet Queue partagé par plusieurs threads, qui ajoutent ou suppriment des éléments de la file d'attente à l'aide des opérations put() et get().

# -*- coding: UTF-8 -*-
from queue import Queue
from threading import Thread
isRead = True
def write(q):
    # 写数据进程
    for value in ['两点水', '三点水', '四点水']:
        print('写进 Queue 的值为:{0}'.format(value))
        q.put(value)
def read(q):
    # 读取数据进程
    while isRead:
        value = q.get(True)
        print('从 Queue 读取的值为:{0}'.format(value))
if __name__ == '__main__':
    q = Queue()
    t1 = Thread(target=write, args=(q,))
    t2 = Thread(target=read, args=(q,))
    t1.start()
    t2.start()

Les résultats de sortie sont les suivants :

写进 Queue 的值为:两点水
写进 Queue 的值为:三点水
从 Queue 读取的值为:两点水
写进 Queue 的值为:四点水
从 Queue 读取的值为:三点水
从 Queue 读取的值为:四点水

Python fournit également l'objet Event pour la communication entre les threads. Il s'agit d'un indicateur de signal défini par le thread. Si l'indicateur de signal est vrai, les autres threads attendent que le signal soit contacté. .

L'objet Event implémente un mécanisme de communication de thread simple. Il fournit des signaux de configuration, d'effacement, d'attente, etc. pour réaliser la communication entre les threads.

Définir le signal

Utilisez la méthode set() de Event pour définir l'indicateur de signal à l'intérieur de l'objet Event sur true. L'objet Event fournit la méthode isSe() pour déterminer l'état de son indicateur de signal interne. Lorsque vous utilisez la méthode set() de l'objet événement, la méthode isSet() renvoie true

Effacer le signal

Utilisez la méthode clear() de l'objet Event pour effacer l'indicateur de signal à l'intérieur de l'objet Event, c'est-à-dire définissez-le sur false. Lors de l'utilisation de la méthode Event After clear, la méthode isSet() renvoie false

Waiting

La méthode wait de l'objet Event ne s'exécutera rapidement et terminera le retour que lorsque le signal interne est vrai. Lorsque l'indicateur de signal interne de l'objet Event est faux, la méthode wait attend qu'il soit vrai avant de revenir.

Exemple :

# -*- coding: UTF-8 -*-
import threading
class mThread(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)
    def run(self):
        # 使用全局Event对象
        global event
        # 判断Event对象内部信号标志
        if event.isSet():
            event.clear()
            event.wait()
            print(self.getName())
        else:
            print(self.getName())
            # 设置Event对象内部信号标志
            event.set()
# 生成Event对象
event = threading.Event()
# 设置Event对象内部信号标志
event.set()
t1 = []
for i in range(10):
    t = mThread(str(i))
    # 生成线程列表
    t1.append(t)
for i in t1:
    # 运行线程
    i.start()

Le résultat de sortie est le suivant :

1
0
3
2
5
4
7
6
9
8

6. Fil d'arrière-plan

Par défaut, après la sortie du thread principal, même si le thread enfant ne se joint pas. Ensuite, une fois le thread principal terminé, le thread enfant continuera à s'exécuter. Si vous souhaitez que le thread principal se ferme, ses sous-threads se fermeront également et ne s'exécuteront plus, vous devez définir les sous-threads comme threads d'arrière-plan. Python fournit la méthode setDeamon.

Formation continue
  • Recommandations de cours
  • Téléchargement du didacticiel