多线程
1、线程是程序里面最小的执行单元。
2、进程是资源的集合
多线程就是多个线程一起干活,及并行运行
1、传统方式:串行,循环5次需要15s
import threading def run(): time.sleep(3)#干活需要3秒 print('哈哈哈哈哈') for i in range(5):#串行,就是一个一个输出 run() #运行的时候可以看到每个三秒输出:哈哈哈哈哈
2、并行:利用多线程并发,节省时间
import threading,time def run(): time.sleep(3)#干活需要3秒 print('哈哈哈哈哈') for i in range(5):#并行 t=threading.Thread(target=run)#实例化一个线程,其中target=执行的函数名
t.start() # #多线程,就是N个线程一起在干活 #结果,运行后三秒输出5个结果:哈哈哈哈哈哈
例子:多线程爬虫,比较串行和并发的时间:
(1)串行方式:
import threading,time,requests urls = { 'besttest':'http://www.besttest.cn', 'niuniu':'http://www.nnzhp.cn', 'dsx':'http://www.imdsx.cn', 'cc':'http://www.cc-na.cn'} data={}#在线程中是获取不到返回值的,只能通过定义一个列表或者字典,将数据保存在列表或者字典中返回才能获取 def down_html(filename,url): sart_time = time.time()#开始时间 res=requests.get(url).content open(filename+'.html','wb').write(res) end_time = time.time()#结束时间 run_time = end_time - sart_time data[url]=run_time#运行时间 print(run_time,url)#打印每个下载文件的运行时间 #1、用串行 sart_time=time.time()#开始时间 for k,v in urls.items(): down_html(k,v) end_time=time.time()#结束时间 run_time=end_time-sart_time#运行时间 print('串行下载总共花了XXX时间',run_time) #结果 0.33101892471313477 http://www.besttest.cn 1.4620835781097412 http://www.nnzhp.cn 0.9180524349212646 http://www.imdsx.cn 3.6742103099823 http://www.cc-na.cn 串行下载总共花了XXX时间 6.38536524772644
2、并行方式:
(1)一开始以为像串行一样直接在循环线程之前加开始时间,循环线程结束后加结束时间就能够计算到并行的运行时间,其实这样是错误的,究竟是为什么呢?看下面分析:
因为在运行多线程时候,默认先会运行主线程,其他线程只是开始运行(下载网页)并不一定结束(而一个进程中的多个线程都是相互独立的。)主线程负责定义函数,启动其
他线程等。因此,最终并行运行的结果时间,仅仅是主线程的运行时间。
import threading,time,requests urls = { 'besttest':'http://www.besttest.cn', 'niuniu':'http://www.nnzhp.cn', 'dsx':'http://www.imdsx.cn', 'cc':'http://www.cc-na.cn'} def down_html(file_name,url): res = requests.get(url).content #返回二进制内容 open(file_name+'.html','wb').write(res) #因此打开的时候,用wb二进制打开 #用并行方式 start_time = time.time() for k,v in urls.items():#因为urls里有3个Key,因此会启动3个线程 t = threading.Thread(target=down_html,args=(k,v)) #使用多线程调用参数时,必须用args=xx才能传参 t.start() end_time = time.time() run_time = end_time - start_time print('下载共花了%s时间'%run_time) #结果: 下载共花了0.004000425338745117时间
(2)那如何计算多线程的运行时间呢?接下来先讲一下线程等待:
1、计算主线程时间,跟上述代码一样
import threading,time #主线程:默认有个主线程,主线程主要是启动子线程,启动之后跟子线程没有关系 #上面计算的是主线程运行的时间,不是子线程的时间 def run(): time.sleep(3) print('哈哈哈哈') #1、主线程时间 sart_time = time.time() for i in range(5): # 并行 t=threading.Thread(target=run)#实例化一个线程 t.start() end_time=time.time() run_time=end_time-sart_time print('run_time',run_time)
2、可以使用join()函数设置线程等待:
t.join()放在线程循环体里面,主线程等待第一个线程结束才开始循环第二个线程,所以并没有进行并行,还是跟串行一样
import threading,time def run(): time.sleep(3) print('哈哈哈哈') sart_time = time.time() for i in range(5): # 并行 t=threading.Thread(target=run)#实例化一个线程 t.start() t.join()#来一个线程等待一个线程执行结束,等执行结束了才循环继续下一个线程执行结束,所以总共会等待15s,变成串行 end_time=time.time() run_time=end_time-sart_time print('run_time',run_time) #结果 哈哈哈哈 哈哈哈哈 哈哈哈哈 哈哈哈哈 哈哈哈哈 run_time 15.000857830047607
3、主线程等待5个线程完成:这就是多线程并行时间
主线程负责启动5个子线程,把每个线程放在threads list里,然后等待所有线程等待完毕后,再执行end_time = time.time()语句,实现最后计算所有线程都结束的并发时间。
import threading,time def run(): time.sleep(3) print('哈哈哈哈') sart_time = time.time() threads=[] for i in range(5): # 并行 t=threading.Thread(target=run)#实例化一个线程 t.start() threads.append(t) for t in threads:#为什么循环,是因为循坏等待才能计算子线程的运行时间 t.join()#循环等待,只主线程循坏等待5个子线程执行结束,及等5个线程执行完就行了 end_time=time.time() run_time=end_time-sart_time print('run_time',run_time) #结果 哈哈哈哈 哈哈哈哈 哈哈哈哈 哈哈哈哈 哈哈哈哈 run_time 3.001171827316284
4、计算上述爬虫的多线程执行时间,代码如下:
import requests data={}#在线程中是获取不到返回值的,只能通过定义一个列表或者字典,将数据保存在列表或者字典中返回才能获取 def down_html(filename,url):#计算每个线程的执行时间 sart_time = time.time() res=requests.get(url).content open(filename+'.html','wb').write(res) end_time = time.time() run_time = end_time - sart_time data[url]=run_time print(run_time,url) #用并行 sart_time=time.time() threads=[] for k,v in urls.items():#5次 t=threading.Thread(target=down_html,args=(k,v))#多线程的函数如果传参数的话,必须用args t.start() threads.append(t) for t in threads:#为什么循环,是因为循坏等待才能计算子线程的运行时间 t.join() end_time=time.time() run_time=end_time-sart_time print('并行下载总共花了XXX时间',run_time) #6个线程,进程里面默认有一个线程,这个线程叫做主线程 #结果: 0.37302136421203613 http://www.besttest.cn 1.2510716915130615 http://www.imdsx.cn 1.5230872631072998 http://www.nnzhp.cn 3.583204746246338 http://www.cc-na.cn 并行下载总共花了XXX时间 3.588205337524414
线程锁
import threading,time num=1 lock=threading.Lock()#申请一把锁 def run(): time.sleep(1) global num lock.acquire()#加锁 num+=1 lock.release()#解锁 ts=[] for i in range(50): t=threading.Thread(target=run) t.start() ts.append(t) [t.join() for t in ts] print(num) #锁的就是,在多个线程同时修改一个数据的时候,可能会把数据覆盖,在Python2里面需要加锁 #Python3中里面不加锁也无所谓,默认会自动帮你加锁