day39
一、死锁与递归锁
所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
关于死锁的经典问题:哲学家吃面
死锁问题:
import time def eat1(lock1, lock2, name): lock1.acquire() print("%s:抢到了筷子" % name) time.sleep(1) lock2.acquire() print("%s:抢到了面条" % name) time.sleep(1) print("开始吃了") time.sleep(2) lock2.release() print("面条放下了") lock1.release() def eat2(lock1, lock2, name): lock2.acquire() print("%s:抢到了面条" % name) time.sleep(1) lock1.acquire() print("%s:抢到了筷子" % name) time.sleep(1) print("开始吃了") time.sleep(2) lock1.release() print("筷子放下了") lock2.release() from threading import Thread, Lock if __name__ == '__main__': lock1 = Lock() lock2 = Lock() for i in ['张三','李四', '小吴']: t = Thread(target=eat1, args=(lock1, lock2, i)) t.start() for i in ['tom','egon', 'jason']: t = Thread(target=eat2, args=(lock1, lock2, i)) t.start()
递归锁解决死锁问题:
import time def eat1(lock1, lock2, name): lock1.acquire() print("%s:抢到了筷子" % name) time.sleep(1) lock2.acquire() print("%s:抢到了面条" % name) time.sleep(1) print("开始吃了") time.sleep(2) lock2.release() print("面条放下了") lock1.release() def eat2(lock1, lock2, name): lock2.acquire() print("%s:抢到了面条" % name) time.sleep(1) lock1.acquire() print("%s:抢到了筷子" % name) time.sleep(1) print("开始吃了") time.sleep(2) lock1.release() print("筷子放下了") lock2.release() # RLock => 递归锁, 可重入锁 from threading import Thread, Lock, RLock if __name__ == '__main__': lock1 = RLock() lock2 = lock1 for i in ['张三','李四', '小吴']: t = Thread(target=eat1, args=(lock1, lock2, i)) t.start() for i in ['tom','egon', 'jason']: t = Thread(target=eat2, args=(lock1, lock2, i)) t.start()
二、线程Queue
queue队列:使用import queue, 用法与进程Queue一样
""" 同一个进程下多个线程数据是共享的 为什么先同一个进程下还会去使用队列呢 因为队列是 管道 + 锁 所以用队列还是为了保证数据的安全 """
from queue import Queue, LifoQueue, PriorityQueue if __name__ == '__main__': # 1、先进先出 q = Queue() q.put('ly is handsome1') q.put('ly is handsome2') print(q.get()) # 2、 后进先出 堆, 栈, 队列, 链表, q = LifoQueue() q.put('ly is handsome1') q.put('ly is handsome2') print(q.get()) # 3、#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q = PriorityQueue() q.put((20, 'a')) q.put((10, 'b')) q.put((30, 'c')) print(q.get())
三、进程池
1、介绍
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor:进程池,提供异步调用
2、基本方法
submit(fn, *args, **kwargs):异步提交任务
map(func, *iterables, timeout=None, chunksize=1):取代for循环submit的操作
shutdown(wait=True):相当于进程池的pool.close()+pool.join()操作
- wait=True,等待池内所有任务执行完毕回收完资源后才继续
- wait=False,立即返回,并不会等待池内的任务执行完毕
- 但不管wait参数为何值,整个程序都会等到所有任务执行完毕
- submit和map必须在shutdown之前
result(timeout=None):取得结果
add_done_callback(fn):回调函数
done():判断某一个线程是否完成
cancle():取消某个任务
ProcessPoolExecutor
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import os,time,random def task(n): print('%s is runing' %os.getpid()) time.sleep(random.randint(1,3)) return n**2 if __name__ == '__main__': executor=ProcessPoolExecutor(max_workers=3) futures=[] for i in range(11): future=executor.submit(task,i) futures.append(future) executor.shutdown(True) print('+++>') for future in futures: print(future.result())
ThreadPoolExecutor用法与ProcessPoolExecutor相同
map的用法
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import os,time,random def task(n): print('%s is runing' %os.getpid()) time.sleep(random.randint(1,3)) return n**2 if __name__ == '__main__': executor=ThreadPoolExecutor(max_workers=3) # for i in range(11): # future=executor.submit(task,i) executor.map(task,range(1,12)) #map取代了for+submit
回调函数
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor from multiprocessing import Pool import requests import json import os def get_page(url): print('<进程%s> get %s' %(os.getpid(),url)) respone=requests.get(url) if respone.status_code == 200: return {'url':url,'text':respone.text} def parse_page(res): res=res.result() print('<进程%s> parse %s' %(os.getpid(),res['url'])) parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text'])) with open('db.txt','a') as f: f.write(parse_res) if __name__ == '__main__': urls=[ 'https://www.baidu.com', 'https://www.python.org', 'https://www.openstack.org', 'https://help.github.com/', 'http://www.sina.com.cn/' ] # p=Pool(3) # for url in urls: # p.apply_async(get_page,args=(url,),callback=pasrse_page) # p.close() # p.join() p=ProcessPoolExecutor(3) for url in urls: p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
多线程爬取网页
import requests def get_page(url): res=requests.get(url) name=url.rsplit('/')[-1]+'.html' return {'name':name,'text':res.content} def call_back(fut): print(fut.result()['name']) with open(fut.result()['name'],'wb') as f: f.write(fut.result()['text']) if __name__ == '__main__': pool=ThreadPoolExecutor(2) urls=['http://www.baidu.com','http://www.cnblogs.com','http://www.taobao.com'] for url in urls: pool.submit(get_page,url).add_done_callback(call_back)
定时器
# 定时器,指定ns后执行某个任务 from threading import Timer def test(name): print('%s sb'%name) t=Timer(1,test,args=('铁蛋',)) t.start()
四、协程
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
需要强调的是:
- python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
- 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换。
优点如下:
- 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
- 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点如下:
- 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
- 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结协程特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
五、greenlet模块
def eat(name): print("%s:吃了一口" % name) g2.switch(name) print("%s:又吃了一口" % name) g2.switch() def play(name): print("%s:玩了一下" % name) g1.switch() print("%s:又玩了一口" % name) from greenlet import greenlet g1 = greenlet(eat) g2 = greenlet(play) g1.switch('egon')
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作), 反而会降低程序的执行速度。
六、gevent模块
import time import gevent # 猴子补丁 from gevent import monkey monkey.patch_all() # 其底层原理就是将time.sleep()替换为gevent.sleep()
def eat(name): print("%s:吃了一口" % name) time.sleep(2) print("%s:又吃了一口" % name) def play(name): print("%s:玩了一下" % name) time.sleep(3) print("%s:又玩了一口" % name) ctime = time.time() g1 = gevent.spawn(eat, 'egon') g2 = gevent.spawn(play, 'egon') g1.join() g2.join() print(time.time() - ctime)
上述例子中使用io切换的方法:time.sleep()与gevent.time(),打印结果分别是五秒多与三秒多,其原因是因为GIL锁,GIL锁在遇到io时直接释放锁

浙公网安备 33010602011771号