9-3 线程,线程池,守护线程,锁,协程
一 线程
# 什么是进程 :是计算机资源分配的最小单位
# 什么是线程
# 线程和进程的关系 :
# 每一个进程中都至少有一个线程
#开启100个线程
1 import os 2 import time 3 from threading import Thread 4 n=100 5 def func(i): 6 global n 7 time.sleep(1) 8 n-=1 9 print(os.getpid(),'thread%s'%i) 10 l=[] 11 for i in range(100): 12 t=Thread(target=func,args=(i,)) 13 t.start() 14 l.append(t) 15 for i in l: 16 i.join() 17 print(n)
n的结果是0
总结:
#每个进程里至少有一个主线程负责执行代码
# 在主线程中可以再开启一个新的线程
# 在同一个进程中就有两个线程同时在工作了
# 线程才是CPU调度的最小单位
# 多个线程之间的数据时共享的
二 守护线程
# 主线程结束了之后守护线程也同时结束
# 守护线程会等待主线程完全结束之后才结束
from threading import Thread import time def foo(): while True: print(123) time.sleep(1) def bar(): print(456) time.sleep(3) print('end456') t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True #foo是守护线程 t1.start() t2.start() print("main----") #主线程结束的地方 打印结果: 123 456 main---- 123 123 end456
三 锁(Lock)
1普通锁 lock
# 当你的程序中出现了取值计算再赋值的操作 数据不安全 —— 加锁
1 from threading import Thread,Lock 2 import time 3 def work(): 4 global n 5 6 lock.acquire() 7 temp=n 8 time.sleep(0.1) 9 n=temp-1 10 lock.release() 11 if __name__ == '__main__': 12 lock = Lock() 13 n=100 14 l=[] 15 for i in range(10): 16 p=Thread(target=work) 17 l.append(p) 18 p.start() 19 for i in l: 20 i.join() 21 print(n) 22 23 最后n的值为90
2 递归锁(Rlock)(很少用)
1 from threading import RLock 2 3 lock = RLock() 4 lock.acquire() 5 lock.acquire() 6 print(123) 7 lock.release() 8 print(456) 9 lock.release()
总结:
# 普通的锁 在同一个线程中 只能acquire一次
# 所以当acquire两次的时候就容易出现死锁现象
# 出现了死锁现象可以使用递归锁去解决问题
# 但是本质上死锁的出现是因为逻辑的错误
# 因此我们更应该把注意力集中在解决逻辑错误
# 而不要在出现错误的时候直接用递归锁规避
递归锁并没有本质上解决死锁的问题
四 线程池(ThreadPoolExecutor)
1
1 import time 2 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor 3 def func(num): 4 print(num) 5 time.sleep(1) 6 print(num) 7 if __name__ == '__main__': 8 t=ThreadPoolExecutor(20) #20个线程,每次处理20个 9 for i in range(50): 10 t.submit(func,i) #异步提交命令 11 t.shutdown()#同join整个线程池 12 print('done')
2 回调函数(callback)
1 # import time 2 # import random 3 # from concurrent.futures import ThreadPoolExecutor 4 # from threading import current_thread 5 # urls=[ 6 # 'https://www.baidu.com', 7 # 'https://www.python.org', 8 # 'https://www.openstack.org', 9 # 'https://help.github.com/', 10 # 'http://www.sina.com.cn/' 11 # 'http://www.cnblogs.com/' 12 # 'http://www.sogou.com/' 13 # 'http://www.sohu.com/' 14 # ] 15 # 16 # def analies(content): 17 # print('分析网页',current_thread()) 18 # print(content.result()) 19 # 20 # 21 # def get_url(url): 22 # print('爬取网页',current_thread()) 23 # time.sleep(random.uniform(1,3)) 24 # # analies(url*10) 25 # return url*10 26 # 27 # t = ThreadPoolExecutor(3) 28 # print('主线程',current_thread()) 29 # for url in urls: 30 # t.submit(get_url,url).add_done_callback(analies) #
#回调函数当执行了get_url之后,得到了一个url*10 ,然后在立即执行analies函数,并传给content参数
# concurrent.futures里面的 callback是由子线程做的
五协程(gevent)
需要手动安装,第三方模块
安装 :pip3 install gevent
协程 把一个线程拆分成几个
# 协程 是程序级别调度
# 减轻了操作系统的负担、增强了用户对程序的可控性
特点是:只要遇到阻塞就会执行别的任务。例如:
from gevent import monkey;monkey.patch_all() import gevent import time def eat(name): print('%s eat 1' %name) time.sleep(2) print('%s eat 2' %name) def play(name): print('%s play 1' %name) time.sleep(1) print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,'alex') g1.join() g2.join() # gevent.joinall([g1,g2])#第二种写法 print('主')
执行结果是:
egon eat 1
alex play 1
alex play 2
egon eat 2
主
例2:利用协程批量访问url
1 from gevent import monkey;monkey.patch_all() #这个是必须加的否则gevent不识别time 2 import gevent 3 import requests 4 import time 5 6 def get_page(url): 7 print('GET: %s' %url) 8 response=requests.get(url) 9 if response.status_code == 200: 10 print('%d bytes received from %s' %(len(response.text),url)) 11 12 13 start_time=time.time() 14 gevent.joinall([ 15 gevent.spawn(get_page,'https://www.python.org/'), 16 gevent.spawn(get_page,'https://www.yahoo.com/'), 17 gevent.spawn(get_page,'https://github.com/'), 18 ]) 19 stop_time=time.time() 20 print('run time is %s' %(stop_time-start_time))