Day 34 并发编程4
Day 34 并发编程4
死锁
死锁指的是某个资源被占用后一直得不到释放没导致其他需要这个资源的线程刚进入阻塞状态,造成死锁现象
情况1:
- 对同一把互斥锁多次执行acquire将导致死锁(调用acquire上锁但并未执行release解锁)
- 给acquire加上超时,可以保证线程不会卡死
情况2:
- 一个共享资源不止一把锁,被不同的线程/进程拥有这些锁的钥匙,造成这些进程/线程互相等待对方释放,导致死锁的产生
- 抢锁必须按照相同的顺序来执行,同时给抢锁加上超时,如果超时则放弃
递归锁(了解)
与普通锁的区别
所线程之间都有互斥的效果
不同在于,同一个线程可以对这个锁执行多次acquire
同一个线程必须保证加锁的次数和解锁的次数相同,其他线程才能够抢到这把锁
信号量
可以限制同时并发执行的公共代码的线程数量
如果限制数量为1,则与普通互斥锁没有区别
from threading import Thread,Semaphore,currentThread
import time
s=Semaphore(5) #设置并发量为5
def task():
s.acquire()
time.sleep(2)
print(currentThread().name)
s.release()
for i in range(10):
Thread(target=task).start()
信号量不是用来解决安全问题的,而是用来限制最大的并发量
GIL(全局解释器锁)
在cpython中,这个全局解释器锁,或者称为GIL,是一个互斥锁,是为了阻止多个本地线程在同一时间执行python字节码
因为cpython的内存管理是非线程安全的,这个锁是非常必要的,因为其他越来越多的特性都依赖这个特性
为什么需要这把锁
线程安全问题具体的表现
cpython解释器与python程序之间的关系
python程序本质就是一堆字符串,所以运行一个python程序时,必须要开启一个解释器,但是在一个python程序中解释器只有一个,所有的代码都要交给他来解释执行
当有多个线程都要执行代码时就会产生线程安全问题
cpython解释器与GC(垃圾清扫代码)的问题
python会自动帮我们处理垃圾,清理垃圾也是一对代码,也需要开启一个线程来执行,也就是说就算程序没有自己开启线程,内部也有多个线程,GC线程与我们程序中的线程就会产生安全问题
例如线程a中需要定义一个变量
- 步骤:申请一块空的内存空间,吧数据放进去,引用计数加1
- 如果在进行到第二部的时候,cpu切换到了GC线程,这样的话,由于数据的引用计数为0,那么数据就会被当做垃圾清除
- 解决方案:在python外层加一层锁
带来的问题
GIL是一把互斥锁,互斥锁会导致效率降低
具体表现为几遍开启了多线程,且CPu也是多核的,也无法并行执行,因为同一时间只有一个解释器在执行
如何解决
没有办法解决,只能尽量避免GIL锁影响我们的效率
-
使用多进程能够实现并行,从而更好的利用cpu
-
对任务进行区分
-
计算密集型:没有IO操作,大部分时间都在计算,例如人脸识别,图像处理
由于多线程不能并行,应该用多进程,将任务分给不同的cpu核心
-
IO密集型:计算任务比较少,大部分时间都在等待IO操作
由于网络IO速度对比与cpu处理速度非常慢,缩写成并不会造成太大的影响
另外如果有大连给客户端链接服务,进程根本起不来
-
关于性能的讨论
之所以加锁是为了解决安全问题
由于有了锁,导致cpython中多线程不能并行只能并发
- python是一门语言,而GIL是cpython解释器的问题
- 如果是单核cpu,GIL不会造成任何影响
- 由于目前大多数程序是基于网络的,网络速度对比CPU是非常慢的,导致既是多核cpu也无法提高效率
- 对于IO密集型任务,不会有太大的影响
- 如果没有这把锁,我们程序将必须自己来解决安全问题
性能测试
计算密集型:多进程胜
# 计算密集型
from multiprocessing import Process
from threading import Thread
import time
def task():
for i in range(100000000000):
1+1
if __name__ == '__main__':
start_time=time.time()
ps=[]
for i in range(5):
# p=Process(target=task)
p=Thread(target=task)
p.start()
ps.append(p)
for i in ps:
i.join()
print(time.time()-start_time)
IO密集型:多线程胜
# 计算密集型
from multiprocessing import Process
from threading import Thread
import time
def task():
for i in range (10):
with open(r'D:\Project\test\text.txt','rt',encoding='utf-8') as fr:
fr.read()
if __name__ == '__main__':
start_time=time.time()
ps=[]
for i in range(5):
# p=Process(target=task)
p=Thread(target=task)
p.start()
ps.append(p)
for i in ps:
i.join()
print(time.time()-start_time)
GIL锁和自定义锁的区别
GIL锁住的是解释器级别的数据
自定义锁 锁住的是解释器意外的共享资源,例如因胖上的问题件
对于这种不属于解释器的数据资源就应该自己加锁处理
线程池和进程池
线程池就是装线程的容器
为什么要装到容器中
- 避免频繁的创建和销毁线程/进程带来的资源开销
- 可以限制同时存在的线程/进程数量,以保证服务器不会因为资源不足而导致崩溃
- 帮我们管理了线程/进程的生命周期
- 管理了任务的分配
import os,time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import activeCount,enumerate,currentThread
# #创建一个线程池,最多可以放置20个线程
# pool=ThreadPoolExecutor(20)
#
# def task():
# print(currentThread().name)
#
# # 提交任务到池中
# pool.submit(task)
# pool.submit(task)
#
# print(enumerate())
#创建一个进程池
def task():
time.sleep(1)
print(os.getpid())
if __name__ == '__main__':
pool=ProcessPoolExecutor(2)
pool.submit(task)
pool.submit(task)
pool.submit(task)
如果进程不结束,池里面的进程或线程也是一直存在的
同步异步
阻塞 非阻塞 程序的状态
并发 并发 串行 处理任务的方式
同步 异步 提交任务的方式
同步
提交任务后必须在原地等待任务结束(不切换到其他任务)
异步
提交任务后不需要在原地等待,可以继续往下执行代码
异步效率高于同步,异步任务将导致一个问题,就是任务的发起方不知道任务合适处理完毕
解决方法:
-
轮询:重复的隔一段时间就问一次
效率低,无法及时获取结果
-
异步回调:让任务的执行放主动通知
# 异步回调使用案例
from threading import Thread
def task(callback):
print('run')
for i in range(100000):
1+1
callback('ok')
def finished(res):
print('mission complete',res)
print('start')
t=Thread(target=task,args=(finished,))
t.start()
print('over')
# 线程池中回调的案例
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def task(num):
time.sleep(1)
print(num)
return 'hello python'
def callback(obj):
print(obj.result())
pool=ThreadPoolExecutor()
res=pool.submit(task,123)
res.add_done_callback(callback)
print(over)
Event事件
线程间状态同步
把一个任务丢到守护线程中,这个任务将一部执行,.如何获取到这个任务的执行状态
执行状态和执行结果不是同一个概念
如果需要拿到执行结果,可以采用异步回调
假设
一个线程负责启动服务器,启动服务器需要花一定时间
另一个线程作为客户端,要连接服务器,必须保证服务器已经启动
要获取状态可以采用轮询的方法,但是浪费了cpu资源,而且可能会造成延迟,不能立即获取状态,就可以使用时间来完成状态同步
事件的本质就是一个标志,可以是false或是true,特殊之处是包含于一个wait函数,可以阻塞当前线程,直到状态从false变为True
from threading import Thread,Event
import time
e=Event()
def start_server():
print('starting server')
time.sleep(2)
print('server started')
e.set()
def connect_server():
e.wait() #等待事件从false变为True
if e.is_set():
print('连接服务器成功')
t1=Thread(target=start_server)
t2=Thread(target=connect_server)
t1.start()
t2.start()