Python的多线程、多进程及协程

Python 代码执行由python虚拟机控制,每个CPU在任意时刻只有一个线程在解释器运行,对python虚拟机的访问由全局解释锁GIL控制,如在单核CPU时多线程是并发不是并行。

并发:两个或多个事件在同一时间间隔发生,或交替发生。

并行:两个或多个事件同一事件发生,或同时做不同事情。

Python的多线程的每个线程执行方式:

1. 获取 GIL 锁

2. 切换到这个线程执行

3. 运行代码的两种机制:指定数量的字节码指令或固定时间(15ms)主动让出控制

4. 线程睡眠

5. 释放 GIL

在 Python2 中,解释器执行代码前要先获取 GIL,遇到 I/O 操作时会释放锁,对纯计算而没有 I/O 操作,解释器会每100词操作就是释放锁,执行其他程序。

在 Python3 中,不使用计数器而使用计时器,执行15ms后释放GIL,减少了释放次数。

CPU 密集任务:循环、计数等操作

IO 密集任务:文件处理、爬虫等操作

对 IO 密集任务使用多线程可提高执行效率;CPU 密集型使用多进程提高执行效率。

实际中 IO 密集和CPU密集任务混杂,需要实际测试效率决定用多线程或多进程;

可使用如下包中的线程池和进程池测试:

multiprocessing.dummy 调用多线程模块,是对多线程模块的封装,与多进程有相同的API;

  1. from multiprocessing import Pool
  2. from multiprocessing.dummy import Pool as ThreadPool

注:多核多线程会比单核多线程更差,如 CPU1 释放 GIL后,其他CPU 的线程都会竞争,而此线程可能又会被CPU1获得,CPU2释放GIL也是同样的过程,这样会造成线程颠簸,降低效率。

协程:也称微线程,执行效率高,子程序切换不是线程切换,没有线程切换的消耗,线程数量越多协程的性能优势越明显;协程中共享资源不需要锁,协程是一个线程,可通过多进程 + 协程充分利用多核 CPU。

协程的切换只是单纯的操作CPU的上下文,切换速度特别快,耗能小。在一个线程中执行,执行函数时可以随时中断,由程序(用户)自身控制,执行效率极高。

Join的使用

没有设置join的情况下:
        主线程会先出发,此时跑道上只有主线程。

        主线程依次激活其他线程(strat函数),此时主线程和其他线程都在跑道上,

        主线程不一定先到达终点,取决于线程的任务量。

        等待所有的线程都结束了[各自执行完自己的任务];

        但此时如果子线程任务需要返回结果,例如将结果添加到列表,子线程可能无法完成。

在设置join的情况下:
        主线程会先出发[此时跑道上只有主线程],

        主线程依次激活其他线程,主线对带有.join()的线程特殊照顾,

        在join()的位置一直等到该线程结束,才向前跑,只有主线程受影响,其他线程不受影响

        等待所有的线程都结束了[各自执行完自己的任务]。

判断线程池中的任务是否都执行完毕

import threading
import time
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(max_workers=100)
var = 0
lock = threading.Lock()
def foo():
    global var
    global lock
    with lock:
        if var < 10:
            time.sleep(1)  # 模拟网咯 IO 操作,比如操作数据库,或者请求接口
            var += 1
for _ in range(100):
    pool.submit(foo)
pool.shutdown(wait=True)  # 等待所有任务执行完毕
print(f'最后的 {var}')

使用多线程时,执行的函数需要获取返回值

可定义一个全局列表或使用queue接收,

import time
from threading import Thread

def demo_run(sleep_time, rd):
    time.sleep(sleep_time)
    # print(f"Wake up after {sleep_time}s sleep.")
    rs.append(rd)

if __name__ == "__main__":
    rs = []
    t1 = Thread(target=demo_run, args=(0.5, 1))
    t2 = Thread(target=demo_run, args=(2, 2))
    # t2.setDaemon(True)  # 守护线程不起作用,rs=[]
    t1.start()
    t2.start()
    # 如果主线程无任务执行如sleep(1),子线程业务join,则直接结束,rs=[]。
    # time.sleep(1)  # 主线程sleep 1s,t1执行完,t2未执行完,主线程结束,rs=[1]。
    # t1.join()  # 主线程等待 t1 执行完才结束,此时t2未执行完,rs=[1]
    # t2.join()  # 主线程等待 t2 执行完才结束,rs=[1, 2]
    print(rs)

使用多线程遇到的问题

报错:“pymysql多线程读写数据库报错:Packet sequence number wrong”。

原因:多线程共享了同一个数据库连接,但每个execute前没有加上互斥锁。

解决:

        方法一:每个execute前加上互斥锁,lock.acquire(),cursor.execute(command,data),lock.release()

        方法二:每个线程拥分别使用数据库连接,即在线程调用函数中加上数据库连接代码。

        方法三:所有线程共用一个连接池,需要考虑线程总数和连接池连接数上限的问题。

参考:

python线程join方法_两只蜡笔的小新的博客-CSDN博客_python 线程join

python判断任务是CPU密集型还是IO密集型 (shuzhiduo.com)

Python的线程池如何判断任务是否全部执行完毕? - python后端实战经验分享 - SegmentFault 思否

Python 使用threading+Queue实现线程池示例_python_脚本之家 (jb51.net)

pymysql多线程读写数据库报错:Packet sequence number wrong-CSDN博客

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值