死锁
死锁就是指两个或两个以上的进程和线程在执行过程中,因为争夺资源造成的一种互相等待的现象,此时称系统处于死锁状态。
解决办法:
递归锁,,Rlock
看一个很经典的问题----哲学家吃面问题
"""
需求:
哲学家吃面这是一个典型的死锁现象, 一个哲学家吃面是需要一个叉子和一把刀
,一个桌子上坐着六个人,每个人的左右手都只有一把刀和一把叉子,而要吃饭要先抢到叉子和刀
接下来我们就模拟这个问题,用两个函数代表两个哲学家,然后开启两个主线程
核心就是,这两个线程运行这两个函数,一个抢到了lock1(叉子),一个抢到了lock2(刀),
"""
import time
from threading import Thread, Lock
def eat1(name):
lock1.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
lock2.acquire()
print('%s 抢到了刀' % name)
lock2.release()
lock1.release()
print('%s 开始吃面' % name)
def eat2(name):
lock2.acquire()
print('%s 抢到了刀' % name)
lock1.acquire()
print('%s 抢到了叉子' % name)
lock1.release()
lock2.release()
print('%s 开始吃面' % name)
if __name__ == '__main__':
lock1 = Lock()
lock2 = Lock()
l = ['egon', 'jason', '老刘']
for name in l:
t1 = Thread(target=eat1, args=(name, ))
t1.start()
l2 = ['qwe', 'asd', 'zxc']
for name in l2:
t2 = Thread(target=eat2, args=(name, ))
t2.start()
解决--Rlock
import time
from threading import Thread, Lock, RLock
def eat1(name):
lock1.acquire()
print('%s 抢到了刀' % name)
time.sleep(1)
lock2.acquire()
print('%s 抢到了叉子' % name)
lock2.release()
lock1.release()
print('%s 开始吃面' % name)
def eat2(name):
lock2.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
lock1.acquire()
print('%s 抢到了刀' % name)
lock1.release()
lock2.release()
print('%s 开始吃面' % name)
if __name__ == '__main__':
lock1 = lock2 = RLock()
l = ['egon', 'jason', '老刘']
for i in l:
t = Thread(target=eat1, args=(i, ))
t.start()
l2 = ['qwe', 'asd', 'zxc']
for name in l2:
t = Thread(target=eat2, args=(name, ))
t.start()
线程队列
线程下的数据是共享的,至于为什么用队列,那是因为队列是一个管道加锁,首页还是为了数据安全
先进先出
from queue import Queue
q = Queue()
q.put('qwe')
q.put('asd')
print(q.get())
========================>
qwe
先进后出
from queue import LifoQueue
q = LifoQueue()
q.put('qwe')
q.put('asd')
print(q.get())
====================>
asd
优先级队列
from queue import PriorityQueue
q = PriorityQueue()
q.put((20, 'qwe'))
q.put((10, 'zxc'))
q.put((30, 'asd'))
print(q.get())
===============>
(10, 'zxc')
优先级队列,传值的时候传一个元组,第一个是优先级,通常是数字与非数字之间的比较,数字越小优先级越高
进程池,线程池
基本方法
submit(fn, *args, **kwargs):异步提交任务
map(func, *iterables, timeout=None, chunksize=1):取代for循环submit的操作
shutdown(wait=True) : 相当于进程中的join()方法
result() : 取得结果
add_done_callback() : 回调函数
done(): 判断一个线程是否完成
cancle(): 取消某个任务
进程池和线程池
from concurrent.futures import ProcessPoolExecutor
def task(i):
print('我是子进程 %s' % i)
if __name__ == '__main__':
p_pool = ProcessPoolExecutor(3) # 这里可以设置池子大小
for i in range(3): # 循环开启三个进程
p_pool.submit(task, i) # 相当于之前的初始化进程对象 p = Process(target=task, args(i, ))
p_pool.shutdown() # 相当于 p.join() 不过这里呢不在需要用循环添加的方式去用.join()
print('我是主进程')
from concurrent.futures import ThreadPoolExecutor
def task(i):
print('我是子线程 %s' % i)
if __name__ == '__main__':
t_pool = ThreadPoolExecutor(3)
for i in range(3):
t_pool.submit(task, i)
t_pool.shutdown()
print('我是主线程')
map方法
from concurrent.futures import ProcessPoolExecutor
def task(i):
print('我是子进程 %s' % i)
if __name__ == '__main__':
p_pool = ProcessPoolExecutor(3)
p_pool.map(task, range(3)) # 这里就是取代了,循环创建
print('我是主进程')
回调函数
"""
需求:
创建多线程爬取网页的html代码, 用到回调函数返回一个函数的返回结果
"""
import requests
from concurrent.futures import ThreadPoolExecutor
def request(url):
response = requests.get(url)
if response.status_code == 200:
return response.text
def save(res):
print(res.result())
if __name__ == '__main__':
t_pool = ThreadPoolExecutor(2)
urls = [
'https://www.baidu.com',
'https://www.python.org',
]
for i in urls:
t_pool.submit(request, i).add_done_callback(save)
"""
回调函数就是用一个函数的返回值
"""
定时器
from threading import Timer
def test(name):
print('%s is sb' % name)
t = Timer(1,test, args=('hrj', )) # 1秒后执行这个函数
t.start()
协程
协程: 是单线程下的并发,又称微线程。
协程是一种用户级的轻量线程,协程是由用户自己调度的。
python的线程属于内核级别的,即由操作系统控制调度的(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
单线程内开启协程,一旦遇到io,就会从应用程序级别控制切换,以此来提升效率。
协程切换的开销更小,属于程序级别的切换,单线程内就可以实现并发的效果,最大限度的利用cpu。
协程无法利用多核,可以是一个程序开启多个进程,进程开启线程,线程开启协程
协程指的是单个线程,一旦遇到io阻塞,就阻塞整个线程
greenlet模块
from greenlet import greenlet
def eat(name):
print('%s 吃了一口' % name)
g2.switch('egon') # 吃一口玩一下,手动切换
print('%s 吃了第二口' % name)
g2.switch()
def play(name):
print('%s 玩了一下' % name)
g1.switch('egon')
print('%s 又玩了一下' % name)
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch('egon')
gevent模块
import gevent
import time
def eat(name):
print('%s 吃了一口' % name)
gevent.sleep(2) # 如果这里换位time.sleep(2)就会变成串行执行,原因就是GIL全局解释器锁,这个锁一遇到io立马释放锁。
print('%s 又吃了一口' % name)
def play(name):
print('%s 玩了一下' % name)
gevent.sleep(3)
print('%s 又玩了一下' % name)
start_time= time.time()
g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, 'egon')
g1.join()
g2.join()
print(time.time() - start_time)
import gevent
import time
from gevent import monkey
monkey.patch_time() # 这里用猴子补丁处理了一下,把time内部替换成了gevent等同于patch_all()
def eat(name):
print('%s 吃了一口' % name)
time.sleep(2)
print('%s 又吃了一口' % name)
def play(name):
print('%s 玩了一下' % name)
time.sleep(3)
print('%s 又玩了一下' % name)
start_time= time.time()
g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, 'egon')
g1.join()
g2.join()
print(time.time() - start_time)
猴子补丁
"""
需求:
henduo地方用到了json,但是ujson性能要比json的性能更高,如果想要把整个项目中的json全部替换成ujson
就要用到猴子补丁,只需要在入口文件加入:代码如下
"""
import json, ujson
def monkey_path_json():
json.__name__ = 'ujson'
json.dump = ujson.dump
json.load = ujson.load
monkey_path_json()