day 23 递归锁、线进程池、异步协程
内容回顾
1.GIL锁:全局解释器锁,在解释器上的一把大锁,线程必须获得这把锁,才能执行。只针对于cpython解释器
2.GIL锁和线程锁有什么区别?有了GIL锁,为什么还要线程锁?
-本身GIL锁和线程锁,都是线程级别的锁,GIL是内置的,解释器里的
-线程锁:开发者定义的
3.多核cpu:
如果是计算密集型:开进程
io密集型:开线程
4.开启线程的两种方式(对比进程)
5.进程和线程的比较
-进程id比较
-开启效率的比较
-共享变量
6.Thread类及threading模块下的其他方法
7.线程join:等待子线程执行完成
8.守护线程:如果主线程执行完成,子线程也结束。
9.互斥锁(同步锁):为了保证并发情况下数据的安全,把对数据的操作过程变成串行,牺牲了效率,保证了数据安全。
10.信号量、Event
今日内容
1.死锁问题(递归锁,可重入锁)
1.所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
2 可重入锁,递归锁
# RLock:可重入锁,可以重复acquire,获得几次就要释放几次
from threading import Thread, Lock, RLock
import time
import random
def eat1(name, lock_1, lock_2):
lock_1.acquire()
print(f"{name}:拿到了筷子lock_1")
time.sleep(random.random())
lock_2.acquire()
print(f"{name}:拿到了叉子lock_2")
print(f"{name}开始吃饭")
time.sleep(random.random())
lock_2.release()
print(f"{name}放下了叉子lock_2")
lock_1.release()
print(f"{name}放下了筷子lock_1")
def eat2(name, lock_1, lock_2):
lock_2.acquire()
print(f"{name}:拿到了叉子lock_2")
time.sleep(random.random())
lock_1.acquire()
print(f"{name}:拿到了筷子lock_1")
print(f"{name}开始吃饭")
time.sleep(random.random())
lock_1.release()
print(f"{name}放下了筷子lock_1")
lock_2.release()
print(f"{name}放下了叉子lock_2")
if __name__ == '__main__':
# lock_1 = Lock()
# lock_2 = lock_1
lock_1 = RLock()
lock_2 = lock_1
for i in ['lem', 'rui', 'emt', 'lam']:
t = Thread(target=eat1, args=(i, lock_1, lock_2))
t.start()
for i in ['p1', 'p2', 'p3']:
t = Thread(target=eat2, args=(i, lock_1, lock_2))
t.start()
2.线程队列
1.线程Queue,线程和线程间数据交互与共享
2.线程间数据共享可以使用共享变量(可能会存在并发安全的问题)
from threading import Thread
from queue import Queue, LifoQueue, PriorityQueue # 线程Queue
import time
# def task(queue):
# time.sleep(3)
# queue.put('lem')
#
#
# if __name__ == '__main__':
# queue = Queue()
#
# t = Thread(target=task, args=(queue,))
# t.start()
#
# res = queue.get()
# print(res)
# Queue:先进先出
# LifoQueue:后进先出
# PriorityQueue:优先级队列
if __name__ == '__main__':
# queue1 = Queue()
# queue1.put(1)
# queue1.put(2)
# print(queue1.get())
>>>>>>>>1
# queue2 = LifoQueue()
# queue2.put(1)
# queue2.put(2)
# print(queue2.get())
>>>>>>>>2
# 数字越小,优先级越高。
queue3 = PriorityQueue()
queue3.put((1,'lem'))
queue3.put((50,'dd'))
queue3.put((100,'xx'))
print(queue3.get())
print(queue3.get())
>>>>>>>>(1,'lem')
>>>>>>>>(50,'dd')
3.进程池、线程池
# 线程池,进程池都在concurrent.futures中
import random
import time
import os
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Pool
def task(n):
print(os.getpid(), '开始执行')
time.sleep(random.random())
return n * n
def callback(result):
print(result)
print(result.result())
if __name__ == '__main__':
# 开进程池
# ProcessPoolExecutors实例化得到一个对象
pool_p = ProcessPoolExecutor(max_workers=3)
# ls = []
# for i in range(10):
# # 把任务提交到进程池执行
# res = pool_p.submit(task, i)
# ls.append(res)
#
# # 等待所有子进程执行完成,主进程在执行
# pool_p.shutdown()
#
# for i in ls:
# print(i.result())
#
# print("主进程")
"简写 ======================================="
# 与上面等价
# map取代for循环,第一个参数时要执行的任务,第二个参数时可迭代对象,迭代一次的结果会传给任务
# pool_p.map(task, range(10))
"回调 ======================================="
for i in range(10):
pool_p.submit(task, i).add_done_callback(callback)
"""
submit
shutdown
result
map
add_done_callback:回调
"""
4.协程介绍
1.协程:程序级别的切换,单线程下实现并发
python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
5.greenlet模块
from greenlet import greenlet
def eat(name):
print(name, '1')
g2.switch(name)
print(name, '2')
g2.switch()
def play(name):
print(name, '3')
g1.switch()
print(name, '4')
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch('lem')
6.gevent模块
# gevent 遇到IO可以自动切换
import gevent
import time
# ### 使用time的io,不会切,并且还是串行
# def eat(name):
# print(name, '1')
# # 遇到了io
# gevent.sleep(3)
# print(name, '2')
#
#
# def play(name):
# print(name, '3')
# gevent.sleep(2)
# print(name, '4')
#
#
# r1 = gevent.spawn(eat, 'lem')
# r2 = gevent.spawn(play, 'lem')
#
# t1 = time.time()
# # r1.join()
# # r2.join()
# gevent.joinall([r1, r2]) # 相当于上面的2个
# print('等待协程任务完成,再执行')
#
# print(time.time() - t1)
# 猴子补丁:把原来的IO全都替换成gevent的IO
from gevent import monkey;monkey.patch_all()
### 使用time的io,不会切,并且还是串行
def eat(name):
print(name, '1')
# 遇到了io
time.sleep(3)
print(name, '2')
def play(name):
print(name, '3')
time.sleep(2)
print(name, '4')
r1 = gevent.spawn(eat, 'lem')
r2 = gevent.spawn(play, 'lem')
t1 = time.time()
# r1.join()
# r2.join()
gevent.joinall([r1, r2]) # 相当于上面的2个
print('等待协程任务完成,再执行')
print(time.time() - t1)
6.0 为什么其他的IO不能切换任务,要全部替换成gevent的IO
由于GIL的锁,当线程中遇到IO操作时会释放GIL锁,cpu会调度到别的线程去执行。
所有异步框架中后面都得用异步。
7.asyncio模块
# 内置模块 python 3.4 推出这个模块,python作者主导的
import asyncio
import time
import threading
# 这个函数是协程函数
async def task():
res=threading.current_thread().getName()
print(res)
print('xxx')
await asyncio.sleep(2)
print('协程执行完成')
async def task2():
res=threading.current_thread().getName()
print(res)
print('2222')
await asyncio.sleep(3)
print('222协程执行完成')
ctime=time.time()
tasks=[task(),task2()]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(time.time()-ctime)
# asyncio.run(asyncio.wait(tasks))
作业
1 使用greenlet写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快
2 基于gevent协程,实现一个高并发的TCP服务端
# 1 使用greenlet写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快
from greenlet import greenlet
import time
def plus():
s = 0
for i in range(10000):
s += i + 1
# g2.switch()
return s
def csh():
s = 1
for i in range(10000):
s *= i + 1
# g1.switch()
return s
t1 = time.time()
# g1 = greenlet(plus)
# g2 = greenlet(csh)
# g1.switch()
plus()
csh()
print(time.time()-t1)
>>>>>>>>
0.0359039306640625
0.02892303466796875
# 2 基于gevent协程,实现一个高并发的TCP服务端
import socket
from gevent import monkey;monkey.patch_all()
import gevent
def reply(conn, addr):
while True:
try:
data = conn.recv(1024)
print(addr, data.decode())
conn.send(data.upper())
except Exception as e:
print(e)
break
sk = socket.socket()
sk.bind(("localhost", 9000))
sk.listen()
while True:
conn, addr = sk.accept()
gevent.spawn(reply, conn, addr)
# 客户端
import socket
client = socket.socket()
client.connect(("localhost", 9000))
while True:
msg = input("输入:").strip()
client.send(msg.encode())
data = client.recv(1024)
print(data.decode())