锁
昨日内容回顾
IPC机制
进程间通信
进程与进程之间是数据隔离的
管道/队列(管道+锁)
队列:先进先出
堆栈:先进后出
q.put() 放入值
q.get() 获取队列里面的值(同一时刻只能有一个任务来队列中获取数据)
两者在存放值和取值的时候都会出现阻塞的情况(队列满了,队列空了)
生产者消费者模型
生产者:生产数据(做包子的)
消费者:处理数据(吃包子的)
两者之间的通信介质:队列/管道
供需不平衡:
队列: 生产者生产的数据放到队列里面
消费者去队列里面获取数据
线程理论
把操作系统比喻成工厂
进程:资源单位(工厂里面的车间)
线程:执行单位(车间里面的流水线)
任何一个进程都自带一个"主"线程
进程中的线程数据是共享的,
开起进程的开销要远远大于开启线程的开销
开启进程:申请空间,拷贝代码都需要耗时
开启线程:开销极小,几乎在代码执行的同时线程就已经创建
如何起线程?(跟起进程一样一样的)
两种起线程的方式:
1.通过制定target起
2.通过继承类的方式起
线程join
主线程等待子线程运行完毕
线程之间数据共享
多个线程操作同一份数据???出现数据不安全的情况
设计到多个线程或进程操作同一份数据的时候,通常都需要将并行并发变成串行
虽然牺牲了效率但是提高了数据的安全性
针对不同的数据,需要加不同的锁
锁(独立卫生间)
其他属性和方法
current_thread().name
active_count 当前活跃的线程数
守护线程
主线程必须等待所有非守护线程的结束才能结束
1.全局解释器锁GIL
'''
定义:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
'''
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
-
GIL其实就是一把互斥锁(牺牲了效率但是保证了数据的安全)。
-
线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行
-
同一时刻同一个进程内多个线程无法实现并行,但是可以实现并发
-
为什么要有GIL是因为它内部的垃圾回收机制不是线程安全的
-
垃圾回收机制也是一个任务,跟你的代码不是串行运行,如果是串行会明显有卡顿
-
这个垃圾回收到底是开进程还是开线程?肯定是线程,线程肯定也是一段代码,所以想运行也必须要拿到python解释器
-
假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!
-
也就意味着在Cpython解释器上有一把GIL全局解释器锁
-
同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行
1.python中的多线程到底有没有用?
单核情况下:四个任务
多核情况下:四个任务
计算密集型:一个任务算十秒,四个进程和四个线程,肯定是进程快
IO密集型:任务都是纯io情况下,线程开销比进程小,肯定是线程好
垃圾回收机制
1.引用计数
2.标记清除
3.分代回收
同一个进程下的多个线程不能实现并行但是能够实现并发,多个进程下的线程能够实现并行
问题:python多线程是不是就没有用了呢?
四个任务:计算密集的任务 每个任务耗时10s
单核情况下:
多线程好一点,消耗的资源少一点
多核情况下:
开四个进程:10s多一点
开四个线程:40s多一点
四个任务:IO密集的任务 每个任务io 10s
单核情况下:
多线程好一点
多核情况下:
多线程好一点
多线程和多进程都有自己的优点,要根据项目需求合理选择
# 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) # 本机为12核
start=time.time()
for i in range(12):
# p=Process(target=work) #耗时8s多
p=Thread(target=work) #耗时44s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
# IO密集型
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为12核
start=time.time()
for i in range(400):
p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
#### 2.GIL与自定义互斥锁
不同的数据需要加不同的锁才能保证数据的安全,GIL锁只是对线程加锁,对数据并没有加锁的效果
```python
from threading import Thread,Lock
import time
mutex=Lock()
n=100
def task():
global n
with mutex:
temp=n
time.sleep(0.1)
n=temp-1
if __name__ == '__main__':
l=[]
for i in range(100):
t=Thread(target=task)
l.append(t)
t.start()
for t in l:
t.join()
print(n)
# 对于修改不同的数据,需要加不同的锁进行处理
3.死锁与递归锁(了解)
自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一
递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到我的人
from threading import Thread,Lock,RLock
import time
# mutexA=Lock()
# mutexB=Lock()
mutexB=mutexA=RLock() # 抢锁之后会有一个计数 抢一次计数加一 针对的是第一个抢到我的人
class Mythead(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print('%s 抢到A锁' %self.name)
mutexB.acquire()
print('%s 抢到B锁' %self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print('%s 抢到了B锁' %self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到了A锁' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(100):
t=Mythead()
t.start()
4.信号量(了解)
自定义的互斥锁如果是一个厕所,那么信号量就相当于公共厕所,门口挂着多个厕所的钥匙。抢和释放跟互斥锁一致
from threading import Thread,Semaphore
import time
import random
sm = Semaphore(5) # 公共厕所里面有五个坑位,在厕所外面放了五把钥匙
def task(name):
sm.acquire()
print('%s正在蹲坑'%name)
# 模拟蹲坑耗时
time.sleep(random.randint(1,5))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task,args=('伞兵%s号'%i,))
t.start()
5.Event事件
一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
from threading import Thread,Event
import time
event = Event() # 造了一个红绿灯
def light():
print('红灯亮着的')
time.sleep(3)
print('绿灯亮了')
event.set()
def car(name):
print('%s 车正在等红灯'%name)
event.wait()
print('%s 车加油门飙车走了'%name)
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(10):
t = Thread(target=car,args=('%s'%i,))
t.start()
6.线程queue
同一个进程下的线程数据都是共享的为什么还要用queue?queue本身自带锁的功能,能够保证数据的安全
# 我们现在的q只能在本地使用,后面我们会学基于网络的q
import queue
queue.Queue() #先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
queue.LifoQueue() #后进先出->堆栈
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
queue.PriorityQueue() #优先级
q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get())