1.GIL(Global Interpreter Lock)
在python中,由于GIL(Global Interpreter Lock)全局解释器锁的存在,所以多线程并不是真正意义上的多线程。在 Python 语言的主流实现 CPython 中,GIL 是一个货真价实的全局线程锁,在解释器解释执行任何 Python 代码时,都需要先获得这把锁才行。CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处
于等待状态等着 GIL 的释放。
通俗来讲就是:你现在面前放了三台电脑,你在第一台电脑上敲了一句代码,电脑开始执行任务,紧接着你又在第二台电脑上开了一个音乐播放器,播放音乐,再然后,你又切换到了第三台电脑,开始打开 python 的学习视频观看,在那些不懂电脑人的眼里,你就像是同时在使用三台电脑。线程锁好比就是你这个人,而至始至终只有你这一个人在操控电脑,真正意义上的多线程应该是三个人同时用三个电脑进行工作。
2.线程
但是虽然如此,使用多线程仍然能够起到加速的作用。
那什么是线程呢?
以电脑中的软件举例,我们每运行一个软件,就是开启了一个进程。比如我们想听一首歌,所以打开了网易云音乐,此时就是开启了一个进程。偶尔碰到一首歌曲,非常好听,想下载下来,在我们下载歌曲时,歌曲的播放并不会停止,这就是使用多线程来解决的。一个程序中的多任务执行就是多线程。且一个进程中至少包含一个线程。
线程的概念
由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程即线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。
线程的创建
分两种,实例化对象开启线程与函数开启线程
函数开启线程:
引入模块:from threading import Thread
创建对象:t = Thread(target=func,args=(arg1,))
实例化对象开启:
引入模块:from threading import Thread
创建对象:t = Classname(arg1,arg2)
参数的含义:
- target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码,值为函数名
- args:给 target指定的函数传递的参数,以元组的形式进行传递
- kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
- name:给线程设定一个名字,可以省略
常用方法:
t.activeCount():获取当前总共线程的个数,包含主线程
t.enumerate():返回当前运行中的 Thread 对象列表
t.setdaemon(True):设置守护线程,默认为子线程
t.start():开启线程
t.is_alive():判断某个线程是否存活,返回布尔值
t.join():进行阻塞,等当前线程执行完毕之后再执行下面的代码
线程间通信
由于线程是在同一个进程中存在的,所以难免会遇到共享变量的情况,在这个时候,可以使用队列来达到目的,而且非常好用,可以避免重复。
导入:from threading import Queue
对象:q = Queue(n)
其中n是指定的队列长度,可以不进行指定
q.put()
将变量放入队列中
q.get()
将变量从队列中取出
线程锁(互斥锁)
导入:from threading import Lock
对象:lock = Lock()
作用:一般放在多线程共享的变量更改时的上下使用。在某个线程进行变量更改时可以防止其他线程对此变量进行更改,常见举例,购物车
上锁:lock.acquire() #此时别的线程无法对变量进行更改
解锁:lock.release()
案例:
from queue import Queue
from threading import Thread
import requests,json,time,threading
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1562211235205&countryId=&' \
'cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=python&pageIndex={}' \
'&pageSize=10&language=zh-cn&area=cn'
headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0'}
"""使用函数开启多线程"""
def thread1(q1,q2,i):
# 如果不确定具体次数可以进行无限循环,并设置退出条件
while True:
try:
url = q1.get(timeout=2)#timeout的作用是设置等待时间,如果超过等待时间便继续下次循环,不会报错
response = requests.get(url=url, headers=headers).content.decode('utf-8')
contents = json.loads(response)
# 将变量放入队列
q2.put(contents)
print('url线程'+str(i)+'放了一个内容进去')
except:
# 设置退出条件,当队列中没有数据时退出,但这种方式不严谨,不建议使用
if q1.empty():
break
def thread4(q2,i):
while True:
try:
# 无限从队列中取出变量,并设置等待时间
contents = q2.get(timeout=1)
print('解析线程' + str(i) + '解析了一个内容'+'----->'+contents['Data']['Posts'][0]['RecruitPostName'])
print('*******'*10)
except:
if con:
print('再等会')
continue
else:
print('搞定,收工')
# 查看当前活动的线程
print(threading.activeCount())
break
if __name__ == '__main__':
# 创建队列对象
q1 = Queue()
q2 = Queue()
con = False
for i in range(1,75):
url = base_url.format(i)
q1.put(url)
t_list = []
for i in range(1,4):
# 创建线程
t1 = Thread(target=thread1,args=(q1,q2,i))
t4 = Thread(target=thread4,args=(q2,i))#注意是元组,要加逗号
# 开启线程
t1.start()
t4.start()
t_list.append(t1)
# t1.join()
# 对每一个放数据的线程都进行阻塞
for t in t_list:
t.join()
con = True
"""使用类的方法开启多线程
与函数开启没有太大区别,只是创建方式不同,不过类较为正式
"""
class Produce(Thread):#继承Thread类
def __init__(self,name,url_q,data_q):
super(Produce, self).__init__()
self.name = name
self.url_q = url_q
self.data_q = data_q
def run(self):
while True:
try:
url = self.url_q.get(timeout=1)
contents = requests.get(url=url,headers=headers).json()
self.data_q.put(contents)
print(self.name+'*************************发布了一个招聘公告')
except:
break
class Consumer(Thread):
def __init__(self,name,data_q):
super(Consumer, self).__init__()
self.name = name
self.data_q = data_q
def run(self):
while True:
try:
contents = self.data_q.get(timeout=1)
print(self.name+'来应聘----->>'+contents['Data']['Posts'][0]['RecruitPostName'])
except:
# 由于设置了阻塞,所以在con的值为True时说明整个程序运行结束,可以退出
if con:
break
if __name__ == '__main__':
url_q = Queue()
data_q = Queue()
hr = ['张三','李四','王五']
em = ['赵六','陈七','周八']
con = False
p_list = []
for i in range(1,75):
url = base_url.format(i)
url_q.put(url)
for h in hr:
p = Produce(h,url_q,data_q)
p.start()
p_list.append(p)
for e in em:
p2 = Consumer(e,data_q)
p2.start()
for p in p_list:
p.join()
con = True
3.多进程
进程的创建
与线程的创建没有太大区别
引入模块:from multiprocessing import Process
创建对象:p= Process(target=func,args=(arg1,))
参数的含义:
- target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码
- args:给 target 指定的函数传递的参数,以元组的形式进行传递
- kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
- name:给进程设定一个名字,可以省略
- group:指定进程组,大多数情况下用不到
常用方法:
p.start():启动子进程实例(创建子进程)
p.is_alive():判断进程子进程是否还在活着
p.terminate():不管任务是否完成,立即终止子进程Process 创建的实例对象的常用属性:
p.name:当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数
p.pid:当前进程的 pid(进程号)
进程间通信
线程之间可以进行通信,同样的,进程之前也可以进行通信,同样也是使用队列,但是队列对象的来源不同。
导入:from multiprocessing import Queue
对象:q = Queue()
q.put()
将变量放入队列中
q.get()
将变量从队列中取出
q.empty()
如果调用此方法时 q 为空,返回 True
q.full()
如果 q 已满,返回为 True
小案例:
def getData(q):
for i in range(1000):
q.put(i)
print('put')
print('爬取完毕')
def process_data(q):
for i in range(1000):
r = q.get()
print('get')
print('处理完毕')
if __name__ == '__main__':
q = Queue()
p1 = Process(target=getData, args=(q,))
p2 = Process(target=process_data, args=(q,))
p1.start()
p2.start()
进程池的使用
在利用Python进行系统管理,并行操作可以节约大量的时间。 当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程, 十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐, 此时可以发挥进程池的功效。 Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时, 如果池还没有满,那么就会创建一个新的进程用来执行该请求; 但如果池中的进程数已经达到规定最大值,那么该请求就会等待, 直到池中有进程结束,才会创建新的进程来它
导入:
from multiprocessing import Pool
创建:
pool = Pool(n)
可以指定进程池的大小
同步操作:
pool.apply(func[, args[, kwds]])
它会阻塞,直到结果就绪
异步操作:
pool.apply_async(func[, args[, kwds[, callback[, error_callback]]]])
不会阻塞,可以指定回调函数
关闭进程池:
pool.colse()
阻塞:
pool.join()
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了
小案例:
from multiprocessing import Pool
import time
def down_load(movie_name):
for i in range(5):
print('电影:{},下载进度{}%'.format(movie_name, (i / 4 * 100)))
time.sleep(1)
return movie_name
def alert(movie_name):
print('恭喜{}下载完成了...'.format(movie_name))
if __name__ == '__main__':
movie_lst = ['魔兽世界', '权利的游戏', '西部世界','碟中谍','战狼','红海行动','唐伯虎点秋香']
pool = Pool(3)
for movie_name in movie_lst:
pool.apply_async(down_load, (movie_name,),
callback=alert)
pool.close()
pool.join()