Python - 并发模型

佚名 / 2023-05-08 / 原文

import itertools
import time
from threading import Thread, Event


def spin(msg: str, done: Event) -> None:
    for char in itertools.cycle(r'\|/-'): # 1
        status = f'\r{char}{msg}' # 2
        print(status, end='', flush=True)
        if done.wait(.1): # 3
            break # 4
    blanks = ' ' * len(status)
    print(f'\r{blanks}\r', end='') #5
    
def slow() -> int:
    time.sleep(3) # 7
    return 42

'''
1. 这是一个无限循环,因为itertools.cycle 一次产出一个字符,一直反复迭代字符串
2. 用文本实现动画的技巧: 使用ASCCI 回车符('\r') 把光标移动到行头
3. 如果其他线程设置了set(),则Event.wait(timeout=None) 方法返回True
   如果其他线程未设置set(), 则经过timeout 时间后返回False,这里把暂停时间设置为0.1秒,作用是把动画的
   帧率设为10fps。如果想指针旋转的快些,可以把值设置的小一些
4. 退出无限循环
5. 显示空格,并把光标移到开头,清空状态行
6.slow() 由主线程调用。假设有一个API通过网络发送,速度很慢。调用sleep 阻塞主线程,但是GIL已被释放,因此指针还能继续转动
'''

def supervisor() -> int: # 1
    done = Event() # 2 
    spinner = Thread(target=spin, args=('thinking!', done)) # 3
    print(f'spinner object:{spinner}') # 4
    spinner.start() # 5
    result = slow() # 6
    done.set() # 7
    spinner.join() # 8
    return result


def main() -> None:
    result = supervisor()
    print(f'AnswerL: {result}')

'''
1.supervisor 反回slow结果
2.threading.Event 实例是协调 main线程和spinnner 线程活动的关键
3.创建一个Thread 实例,target 的值是一个函数,args 参数的值是一个元组,即传给target函数的参数
4.显示spinner对象。输出是spinner object:<Thread(Thread-1 (spin), initial)>,其中initial是线程的动态,表示尚未启动
5.启动线程
6.调用slow,阻塞main线程。同时,次线程运行旋转动画指针
7.把Event标志设置为True,终止spin函数中的for循环
8.等待,直到spinner 线程结束
'''

if __name__ == '__main__':
    main()


运行中:

运行结束:

通过这个实例,要了解最重要的一点:

调用 time.sleep()阻塞所在的线程,但是释放GIL,其他Pyhton线程可以继续运行
在Python中,协调线程的信号机制,使用threading.Event 类最简单。Event 实例有一个内部布尔标志,开始时为False,
调用Event.set() 可把这个标志设为True。这个标志为False时,在一个线程中调用Event.wait(),该线程将被阻塞,直到另一个线程
调用Event.set(),致使Event.wait()返回True。使用Event.wait(s) 设置一个暂停时间(单位秒),经过这段时间后,Event.wait(s)
调用返回False,如果一个线程调用Event.set(),则立即返回True