⭐ 简介:大家好,我是zy阿二,我是一名对知识充满渴望的自由职业者。
☘️ 最近我沉溺于Python的学习中。你所看到的是我的学习笔记。
❤️ 如果对你有帮助,请关注我,让我们共同进步。有不足之处请留言指正!
认识多线程
A:那我们以前写的程序难道都是单线程的嘛?
Q:是的。把程序比作一个作坊。 单线程就是老板自己接单,自己安排任务,自己生产产品,自己销售。生产效率低,产值低,但是管理方便自己管自己,做完一个做下一个。
A:那多线程是什么样子?
Q:老板接了个大单子,一个人来不及干了,怎么办? 只能招工、请外援。老板从第一生产线退居到了第二生产线的管理者,管理工人生产。生产效率高了,产值也高了,但是带来问题就是如何把大量的工作合理的安排给工人呢? 请往下阅读。
A: 现有多线程教程很多,你为什么还要写他?
Q:正如标题所述,自动分配线程对应多任务。在上看到太多的文章都是直接3行代码开多线程。第一句for循环,第二句创建线程任务,第三句t.strat。殊不知这样是在用每一个线程做一个任务。过度耗费CPU资源,过高的线程并发,这样的爬虫对目标网站也是不道德的。
多线程的简单用法。
- 安装theading包。
pip install theading - 导入包
import threading - 本文涉及
threading模块的方法:
| 代码 | 作用 |
|---|---|
t=threading.Thead(target=func,args=(a,)) |
创建一个线程对象,并给一个func任务 |
t.start |
激活多线程对象(激活 ≠ 开启) |
t.join |
等待线程结束 |
threading.active_count() |
返回当前激活线程数 |
m=threading.BoundedSemaphore(3) |
设定线程的最大数量 |
m.acquire(timeout=5) |
超线程上锁,超时时间5秒 |
m.release() |
解锁一个线程,写在任务结束的地方 |
一、初试多线程:
先来看看示例代码,单线程的。等于只有老板自己一个人在干活。生产一个商品需要耗费0.3秒,所以生产10个需要3秒。这是单线程的效果
较真的朋友要说:不对啊,明明是3.091秒啊。。。 Python编译代码,开启进程执行代码都需要时间。老板安排工作是有耗时的

import threading
import time
def func(i, ):
print(f'子线程打印{
i}')
time.sleep(1)
if __name__ == '__main__':
# args接受的必须是一个可迭代对象。所以及时只有一个参数也要写args=(i,)
st = time.time()
for i in range(10):
t = threading.Thread(target=func, args=(i,))
t.start()
print('主线程结束,耗时:', time.time() - st)

老板招了10个工人,每个工人生产1个产品需要1秒。10个线程同时开始工作,任务。只0.0019秒? 聪明的小伙伴就开始提问了:
A1:就算10个人一起开始,那也应该需要1秒才能完成任务啊。为什么只用0.19秒就打印了主线程结束呢?
Q:老板给10个工人安排任务用了0.0019秒,所以老板是主线程,他没有其他任务所以结束的很快,但是子线程(工人)任然需要继续工作,1秒后,子线程全部完工,同时进程结束。所以此时的工厂有11个人,10个工人和1个老板。
A2:为什么打印的结果不是那么整齐?
Q:t.start() 是激活线程,具体开始时间取决于CPU,先激活的线程不一定就是先完成的。同时每个线程在实际情况中遇到的情况不同,所以具体完成的时间不同,打印结果也就会乱。
A3:那如果我有9999个产品难道要开9999个线程才可以吗?
Q:厉害!能想到这个问题。很多刚接触多线程去做爬虫的伙伴,经常会这样: 有100页面要爬,然后写多线程的时候代码如下
for i in range(100):
t = threading.Thread(target=func, args=(i,))
t.start()
细品这个代码是什么意思? 开了100个线程?做100个任务?
如果你是老板,你会雇佣100个工人每个工人只生成一个产品就下班了?
所以如何使用theading模块合理安排多线程多任务,请往下看。
A4:示例func中为什么没有return呢?那如何接受返回值?
Q:threading库并没有返回值的功能。所以我们要用其他的方法,1. 写入硬盘。 2. 全局变量。 3. 队列。 这也是本文要讲的内容之一。
二、多线程 threading.Thread 参数和方法:
# 先来看下 threading.Thread 中接受的参数
t = threading.Thread(group=None, target=(), name=None,
args=(), kwargs={
}, *, daemon=True)
| 参数名 | 作用 |
|---|---|
| target | 必填,函数名或方法名。 |
| args | 元组类型数据传参。(单个参数也需要写成元组,如:(1,)) |
| kwargs | 字典类型数据传参。 |
| name | 线程名,可以忽略,一般不用设置。有默认名。 |
| group | 线程组,直接忽略,因为目前只能使用None。 |
| daemon | 布尔值,默认False。主线程守护。True = 子线程会随主线程一起结束 |
| t.setDaemon(True) | 也可以在后续设置线程守护 |
| 方法 | 作用 |
|---|---|
| t.start() | 激活线程 |
| t.jion() | 等待对象线程结束。 |
| threading.current_thread() | 获取当前的线程名字 |
| threading.active_count() | 获得当前激活的线程数 |
| lock = threading.BoundedSemaphore() | 限制最大线程数量锁 |
| lock.acquire() | 上锁 |
| lock.release() | 解锁 |
| lock2 = threading.Lock() | 线程锁,互斥锁 |
| lock2.acquire() | 上锁 |
| lock2.release() | 解锁 |
三、多任务 分配(任务多 线程少)
不废话,直接行代码
import threading
import time
def func():
time.sleep(0.3)
print('当前线程数量:', threading.active_count())
# 在完成工作后,解锁
lock.release()
if __name__ == '__main__':
# 创建一个允许最大激活线程数量为 5 的锁
# 可以理解为:做多允许出现 5 把锁
lock = threading.BoundedSemaphore(5)
for i in range(100):
# 每次开启线程前,加一次锁,循环5次后,这里就会等待解锁一把后才会放行。
lock.acquire()
t = threading.Thread(target=func)
t.start()

妙不妙?
其实这个问题因为有更好的解决方案:线程池,所以导致了threading模块的这个控制最大线程的方法被雪藏。我上培训机构的老师都没教。都是直接一个for循环到底每个任务一个线程。
我也是钻了牛角看了很多文章,突然豁然开朗。如下是我的解题经历:
- 最初的时候,我也直接选择用线程池,又简单有能解决问题。
- 后来我选择效率更高的异步并发。
- 都爽完后,我静下心来思考了一个问题,难道Thead库真的这么鸡肋?
- 于是开始静下心来查阅theading相关文章,很快发现了threading.active_count()方法
- 随即我写了如下代码,用while循环堵塞主线程。
- 不满足的我觉得肯定有更好,更合理的方法。
- 于是又查阅了十几篇文章后发现了threading.BoundedSemaphore()方法
# 这个代码是我的解题经历,不是最终答案。最优解在上面 👆
# 这个代码是我的解题经历,不是最终答案。最优解在上面 👆
# 这个代码是我的解题经历,不是最终答案。最优解在上面 👆
import threading
import time
def func<


288

被折叠的 条评论
为什么被折叠?



