멀티스레드 프로그래밍

실제로 스레드가 생성된 후 스레드가 항상 상태를 유지하지는 않습니다. 상태는 대략 다음과 같습니다.

새로 생성됨

실행 가능 준비. 일정을 기다리는 중

실행 중

차단되었습니다. Wait Locked Sleeping

Dead

스레드에서 차단이 발생할 수 있습니다. 스레드에는 다양한 상태와 유형이 있습니다. 대략적으로 나눌 수 있습니다:

메인 스레드

하위 스레드

데몬 스레드(백그라운드 스레드)

포그라운드 스레드

이들을 간략하게 이해한 후 구체적인 코드 사용법을 살펴보기 시작합니다.

1. 스레드 생성

Python은 다중 스레드 작업을 위한 두 가지 모듈, 즉 스레드와 스레딩을 제공합니다.

전자는 상대적으로 낮은 수준의 모듈로 하위 수준 작업에 사용되며 일반적으로 적합하지 않습니다. 애플리케이션 수준 개발에 일반적으로 사용됩니다.

#!/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()

실행 결과:

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

여기서 서로 다른 환경의 출력 결과는 확실히 다릅니다.

2. 스레드 병합(조인 방법)

위의 예에서 출력된 결과에서 메인 스레드가 끝난 후에도 하위 스레드는 계속 실행 중입니다. 따라서 종료하기 전에 하위 스레드의 실행이 완료될 때까지 기다리는 메인 스레드가 필요합니다.

이때 Join 방식을 이용하셔야 합니다.

위 예제에서 다음과 같은 코드를 추가하세요.

#!/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()

인쇄된 결과를 보면 위 예제의 인쇄된 결과와 비교하면 메인 스레드는 자식 스레드가 종료될 때까지 기다린 후 종료된다는 것을 확실히 알 수 있습니다. 달리기를 마친다.

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. 스레드 동기화 및 뮤텍스 잠금

스레드 로딩을 ​​사용하여 데이터를 얻으면 일반적으로 데이터가 동기화되지 않습니다. 물론 이때 리소스를 잠글 수 있습니다. 즉, 리소스에 액세스하는 스레드는 리소스에 액세스하기 전에 잠금을 획득해야 합니다.

스레딩 모듈은 잠금 기능을 제공합니다.

lock = threading.Lock()

스레드에서 잠금 획득

lock.acquire()

사용 후에는 반드시 잠금을 해제해야 합니다

lock.release()

물론, 동일한 스레드에서 동일한 리소스에 대한 여러 요청을 지원하기 위해 Python은 재진입 잠금(RLock)을 제공합니다. ). RLock은 내부적으로 Lock과 카운터 변수를 유지하며, 카운터는 획득 횟수를 기록하므로 리소스가 여러 번 필요할 수 있습니다. 스레드의 모든 획득이 해제될 때까지 다른 스레드가 리소스를 얻을 수 있습니다.

그럼 재진입 잠금을 만드는 방법은 무엇일까요? 코드의 문제이기도 합니다.

r_lock = threading.RLock()

4. 조건 조건 변수

실용적인 잠금은 스레드 동기화를 달성할 수 있지만 더 복잡한 환경에서는 잠금에 대해 일부 조건부 판단을 내려야 합니다. Python은 Condition 객체를 제공합니다. Condition 객체는 특정 이벤트가 트리거되거나 특정 조건이 충족된 후 데이터를 처리하는 데 사용할 수 있으며 Lock 객체의 획득 방법 및 해제 방법 외에도 대기 및 알림 방법도 제공합니다. 스레드는 먼저 조건 변수 잠금을 획득합니다. 조건이 불충분하면 스레드는 대기하며 조건이 충족되면 스레드가 실행되고 다른 스레드에도 알릴 수 있습니다. 대기 상태의 다른 스레드는 알림을 받은 후 조건을 다시 판단합니다.

조건 변수는 서로 다른 스레드가 차례로 잠금을 획득하는 것으로 볼 수 있습니다. 조건이 충족되지 않으면 대기 풀(Lock 또는 RLock)에 던져지는 것으로 이해할 수 있습니다. 다른 스레드에 직접 통보한 후 상태를 다시 판단합니다. 이 프로세스는 복잡한 동기화 문제를 해결하기 위해 지속적으로 반복됩니다.

35192f0e58595b25a0c422efd13ef05.png

이 모델은 생산자-소비자 모델에서 자주 사용됩니다. 다음 온라인 쇼핑 구매자 및 판매자의 예를 살펴보세요.

#!/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()

출력 결과는 다음과 같습니다.

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

5. communications

프로그램에 여러 개의 스레드가 있고, 이 스레드들은 필연적으로 서로 통신해야 합니다. 그렇다면 이러한 스레드 간에 정보나 데이터를 안전하게 교환하려면 어떻게 해야 할까요?

아마도 한 스레드에서 다른 스레드로 데이터를 보내는 가장 안전한 방법은 대기열 라이브러리의 대기열을 사용하는 것입니다. put() 및 get() 작업을 사용하여 대기열에서 요소를 추가하거나 제거하는 여러 스레드가 공유하는 Queue 개체를 만듭니다.

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

출력 결과는 다음과 같습니다.

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

Python은 스레드 간 통신을 위한 이벤트 개체도 제공합니다. 이는 스레드에서 설정한 신호 플래그입니다. 신호 플래그가 true인 경우 다른 스레드는 신호에 도달할 때까지 기다립니다. .

이벤트 객체는 스레드 간의 통신을 실현하기 위한 신호 설정, 신호 지우기, 대기 등을 제공하는 간단한 스레드 통신 메커니즘을 구현합니다.

신호 설정

이벤트의 set() 메서드를 사용하여 이벤트 개체 내부의 신호 플래그를 true로 설정합니다. Event 객체는 내부 신호 플래그의 상태를 확인하기 위해 isSe() 메서드를 제공합니다. 이벤트 객체의 set() 메소드를 사용할 때 isSet() 메소드는 true를 반환합니다

Clear the signal

Event 객체의clear() 메소드를 사용하면 Event 객체 내부의 신호 플래그를 지울 수 있습니다. 즉, Event를 사용하는 경우 Clear 메소드 이후 isSet() 메소드는 false를 반환합니다.

Waiting

Event 객체의 wait 메소드는 내부 신호가 true인 경우에만 빠르게 실행되고 반환을 완료합니다. Event 객체의 내부 신호 플래그가 false인 경우 wait 메서드는 반환하기 전에 true가 될 때까지 기다립니다.

예:

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

출력 결과는 다음과 같습니다.

1
0
3
2
5
4
7
6
9
8

6. 백그라운드 스레드

기본적으로 하위 스레드가 조인하지 않더라도 기본 스레드가 종료된 후입니다. 그런 다음 메인 스레드가 종료된 후에도 하위 스레드는 계속 실행됩니다. 메인 스레드를 종료하고 해당 하위 스레드도 종료되어 더 이상 실행되지 않도록 하려면 하위 스레드를 백그라운드 스레드로 설정해야 합니다. Python은 setDeamon 메소드를 제공합니다.

지속적인 학습
  • 코스 추천
  • 코스웨어 다운로드
현재 코스웨어를 다운로드할 수 없습니다. 현재 직원들이 정리하고 있습니다. 앞으로도 본 강좌에 많은 관심 부탁드립니다~