Python技术迷

如何使用多线程/多进程进行并发编程?

我们组小李突然问我:多线程和多进程到底咋用?我当时一口豆浆差点喷出来,这不是刚学Python那会天天折腾的事嘛。结果一聊,发现其实新手还真容易搞混,尤其是你要做并发的活的时候。

先说点场景,顺便聊聊多线程

你看,就拿我前段时间写个爬虫举例哈,本来想着几十个网页采集一下,顺序来得累死。后来一想,直接上多线程啊,不就“秒”出数据了嘛。其实Python自带的threading模块用起来挺顺手,比如下面这样:

import threading

defcrawl_page(url):
    print(f"爬取 {url} 中...")

urls = ['http://a.com', 'http://b.com', 'http://c.com']
threads = []

for url in urls:
    t = threading.Thread(target=crawl_page, args=(url,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

就是这种写法最基础对吧,你开多少个线程随你爽。反正我们爬虫那种IO型的,多线程效果是明显的。对了,cpu密集型别用这个哈,Python的GIL(全局解释器锁)你肯定会想骂人——同一时刻只能一个线程在跑Python字节码,等于你那些线程其实就是个假把式。我们组那谁还说过,之前用多线程批量加密文件,结果一点不比串行快,直接被GIL玩死。

多进程?有点重但真有用

你别说,有时候真得多进程,比如CPU密集型的活儿,你想爆机器性能只能靠multiprocessing,不然GIL直接给你卡死。我自己写图片处理脚本,有一次用多线程以为能快,结果根本没提升,后来改成多进程就飞起来了,代码像下面这样:

from multiprocessing import Process

defprocess_image(image_path):
    print(f"处理 {image_path} 中...")

images = ['img1.png', 'img2.png', 'img3.png']
processes = []

for img in images:
    p = Process(target=process_image, args=(img,))
    processes.append(p)
    p.start()

for p in processes:
    p.join()

多进程好处是每个进程就是一个独立的Python解释器,GIL啥的全都不见了,能把多核CPU榨干。坏处呢,进程间通信麻烦点,用什么Queue、Pipe之类的,写多了容易脑壳疼。还有个坑就是多进程创建太多你机器内存撑不住,我上次一不小心卡死一台服务器,小王还以为我在挖矿,真的是……

异步IO顺便提一句

对了,那天楼下老张顺嘴一提,说现在都啥年代了还手撸线程,直接asyncio不香嘛?说实话,Python的asyncio处理IO场景还真牛逼,尤其是你一堆网络请求、文件操作那种,啥都不用开线程和进程,性能也能挺猛,代码稍微变一下就行。不过话说回来,不少老代码用惯了threading和multiprocessing,一下子要全换成async/await,适应个把礼拜肯定得有。

线程池、进程池别装不知道

还有那种任务多得一批的时候,不用池你可能连自己几百线程几百进程都管理不过来,直接ThreadPoolExecutor或者ProcessPoolExecutor搞定一切:

from concurrent.futures import ThreadPoolExecutor

deffetch_url(url):
    print(f"fetching {url}")

urls = ['http://a.com', 'http://b.com']
with ThreadPoolExecutor(max_workers=4) as executor:
    executor.map(fetch_url, urls)

这个写法真是让人省心,扔一堆活给池子,自动调度不用你管。进程池用法也类似,把ThreadPoolExecutor换成ProcessPoolExecutor就行,参数名字都一样。

要注意的坑

唉,写多了你就知道,线程用多了有锁的地方肯定有坑。比如那种共享变量,没加锁你指不定哪天崩溃。多进程通信更别说了,想同步一下状态,你得折腾Manager、Queue啥的,搞不好还死锁。上次遇到线程死锁,三个人晚上查bug查到想哭,最后发现加了个Lock,结果俩线程都在等对方,最后谁都等不到谁。

反正就是,IO型的任务你就多线程,cpu型的你就多进程,线程池和进程池用起来别手写for循环,别用global变量互通信息,搞清楚每个场景下哪种并发最适合你,写代码别贪多,调试也方便点。你要想高端点,asyncio那一套也学着用用。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领