一、并发和并行
1、多任务的概念
简单地说,就是操作系统可以同时运行多个任务2、CPU与多任务的关系
a.单核CPU可不可以执行多任务?
单核CPU是可以执行多任务的。由于CPU执行代码都是顺序执行的,所以单核CPU是可以执行多任务的:操作系统会轮流让各个任务交替执行,任务1执行一段时间后,切换给任务2再执行一段时间...这样反复执行下去,每个任务都交替执行。但是,由于CPU的执行速度实在太快了,我们感觉就好像所有任务都在同时执行一样
注意.CPU的时间周期是根据它的主频来计算的3、真正的并行执行多任务是如何进行的?
真正的并行去执行多个任务,只能在多个CPU上实现。但是,由于任务数量往往会远大于
CPU的核心。所以,操作系统也会自动把多个任务轮流调度到每个CPU核心上去执行4、并发和并行的区别
并行:指的是任务数小于或等于CPU核数,这时候任务是可以同时并行运行的
并发:指的是任务数多于CPU核数,通过操作系统的各种任务调度算法,从而实现多个任务“一起”执行的情况(实际上这些任务都在轮流执行的,只是因为时间太短,给人们造成一种“同时”的错觉)5、多并发有什么用?
可以让代码同时执行多个任务,更节省时间
二、同步和异步
- 同步(同步协调)
指线程在访问某一资源时,获得了资源的返回结果之后,才可以去执行其他的操作 - 异步
指线程在访问某一资源时,无论是否得到这个资源的返回结果,都可以去进行下一步操作,当这个资源有了返回结果之后,系统再自动去通知这个线程(异步回调) - 例子:比如说吃饭的场景
同步:起床 -> 去市场 -> 买菜 -> 煮饭 -> 炒菜 -> 吃饭 -> 学习
异步:起床 -> 点外卖 -> 学习 -> 收外卖 -> 吃饭 -> 学习
异步跳过了去买菜到炒菜的过程,在这个时间段可以去学习,外卖到了外卖小哥打电话(异步回调),拿到外卖后就吃饭
三、多线程
threading
模块介绍
Python的Thread模块是比较底层的模块,threading模块是对Thread做了一些封装,可以更加方便地使用:threading.Thread
Thread类常用方法如下:
1.创建线程对象
threading.Thread(target = 函数名, name=自定义线程名, args=(要执行的函数的参数值), kwargs={函数参数值键值对}, ...)
target
:必参,指定线程执行的任务(函数名)
2.run()
:用于表示线程活动的方法
3、start()
:启动线程活动
4、join(time)
:设置主线程等待time秒后,再往下执行;time如果不填写,则默认是当前子线程结束(多个子线程,则进行叠加),常用于对主线程进行阻塞,等待子线程完成后,主线程再进行下面的语句
5、is_alive()
:返回线程是否还在活动,开启后是True,开启前和结束后是False(同种作用的过期方法是isAlive())
6、getName()
:返回线程名
7、setName()
:设置线程名
8、threading.currentThread()
:返回当前执行的线程
9、threading.enumerate()
:返回正在执行的所有线程(list),不包括启动前和终止后的线程主线程默认不会等待子线程,不管子线程是否执行完毕,主线程结束完毕,子线程如果还没执行完毕,那么子线程继续执行下去。
threading
官方文档:https://docs.python.org/zh-cn/3/library/threading.html
四、Thread
常用方法的使用
- 1、对函数使用多线程以及
join()
的一个使用场景(看主线程的运行时间)
from threading import Thread
import time
def count_time(func):
"""
计算函数运行时间的装饰器方法
"""
def warpper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print(F'该函数运行了:{end_time - start_time}秒')
return warpper
def work1():
print('喝水5秒')
for i in range(5):
time.sleep(1)
print('喝水中...')
def work2():
print('浇花4秒')
for i in range(4):
time.sleep(1)
print('浇花中...')
@count_time # 该装饰器主要是为了看main1这个主线程函数的执行时间
def main1():
"""
最简单的创建线程方式
"""
t1 = Thread(target=work1)
t2 = Thread(target=work2)
t1.start()
t2.start()
# 设置主线程等待子线程都结束之后,再往下执行主线程
t1.join()
t2.join()
print('---主线程把main1函数执行完毕---')
main1()
print('---主线程所有函数执行完毕后,函数外面的代码')
'''
不添加join()方法,运行的结果:
喝水5秒
浇花4秒
---主线程把main1函数执行完毕---
该函数运行了:0.00092秒 # 主线程的执行时间,一下子就结束了
---主线程所有函数执行完毕后,函数外面的代码
喝水中...
浇花中...
浇花中...
喝水中...
喝水中...
浇花中...
浇花中...
喝水中...
喝水中...
'''
'''
添加join()方法(time不填写),运行的结果:
喝水5秒
浇花4秒
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
---主线程把main1函数执行完毕---
该函数运行了:5.009000062942505秒 # 两个子线程+主线程的执行时间
---主线程所有函数执行完毕后,函数外面的代码
'''
- 2、通过类来定义线程
1.方法:
通过看threading的源码可以知道,start()方法调用了run()方法,那么
可以通过继承Thread类,并重写Thread.run()方法,直接把要进行多线程
的函数写到这个方法中。接下来线程生成和启动的方法都不变
2.例子以及join()
方法的注意点
from threading import Thread
import requests
import time
def count_time(func):
"""
计算函数运行时间的装饰器方法
"""
def warpper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print(F'该函数运行了:{end_time - start_time}秒')
return warpper
class MyThread(Thread):
"""
定义线程类
"""
def run(self):
"""
重写Thread类的run方法
:return:
"""
# 百度进行了反爬处理,headers需要带上User-Agent
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0; "
"Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 "
"Mobile Safari/537.36 SE 2.X MetaSr 1.0"}
for i in range(1, 21): # 发送20次请求
requests.get(url="https://www.baidu.com", headers=headers)
print(F'第{i}次发送请求')
@count_time
def main1():
t = MyThread() # 一个线程,测试是否成功
t.start()
t.join()
# main1() # 该函数运行了:5.770000219345093秒(20个请求消耗的时间)
# 创建5个线程来执行
@count_time
def main2():
# 把所创建的线程添加到list中
thread_list = []
for i in range(5):
t = MyThread() # 创建线程对象
t.start() # 开始运行线程
thread_list.append(t)
'''
不能直接在这里t.join(),这样会导致
一个子线程执行完毕后,再去生成并执行第二个子线程
这样子得到的时间会是5个单线程发送20个请求的时间的乘积
'''
# t.join()
'''
# 也不能在这里进行t.join()。因为这里进行等待的是最后一个子线程
# 如果出现前面的子线程运行比最后的子线程慢的情况,那么得到的结果
# 也不准确
'''
# t.join()
'''
最准确的做法
'''
# 遍历线程list
for t in thread_list:
# 主线程等待子线程执行完毕再执行
t.join()
main2() # 该函数运行了:7.66700005531311秒(5*20=100个请求所耗费的时间)
- 3、给执行多线程的函数传参
1.如果要执行多线程的任务需要传参数,该如何处理?
通过看源码,可以发现Thread类的__init__方法的参数中,有args和kwargs参数。并且tun方法中也接收了这两个参数
2.对函数使用多线程的情况下,给函数传参的例子
import time
from threading import Thread
def work1(name):
print('喝水5秒')
for i in range(5):
time.sleep(1)
print(F'{name}在喝水中...')
def work2():
print('浇花4秒')
for i in range(4):
time.sleep(1)
print('浇花中...')
def main():
# 给work1函数传参数,两种方法
# 注意:一个参数的元组,别忘了加,号,否则报错
# t1 = Thread(target=work1, args=('lzl',))
t1 = Thread(target=work1, kwargs={'name': 'lzl'})
t1.start()
main()
'''
结果:
喝水5秒
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
'''
- 自定义Thread类,如果给执行多线程的函数传参?
1.方法1:重写__init__方法(注意:要调用父类的__init__方法)
2.方法2:后面可能说
from threading import Thread
import requests
class MyThread(Thread):
"""
定义线程类
"""
# 给执行多线程的函数传参,方法1:
def __init__(self, url):
super().__init__()
self.url = url
def run(self):
"""
重写Thread类的run方法
"""
# 百度进行了反爬处理,headers需要带上User-Agent
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0; "
"Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 "
"Mobile Safari/537.36 SE 2.X MetaSr 1.0"}
for i in range(1, 21): # 发送20次请求
requests.get(url=self.url, headers=headers) # 接收传递来的url
print(F'第{i}次发送请求给{self.url}')
def main1():
t = MyThread(url="https://www.baidu.com") # 一个线程,测试是否成功
t.start()
main1()
'''
结果:
第1次发送请求给https://www.baidu.com
第2次发送请求给https://www.baidu.com
...
'''