小结
上节课回顾
'''
cpu最小的执行单位是线程
进程:资源的集合
线程:执行单位
操作系统---》工厂
进程---》车间
线程---》流水线
cpu---》电源
进程的内存空间彼此隔离,互不干扰
线程共享同一进程内的资源
'''
# 开启方式一
from threading import Thread
# import time
# def task():
# print('开启子线程 start')
# time.sleep(2)
# print('开启子线程 end')
#
# if __name__ == '__main__': #(开启线程,这个main 可以写也可以不写,因为线程不需要申请新空间(资源),直接在进程中开启就行)
# t = Thread(target=task)
# t.start()
# print('主线程')
# 开启方式二
# from threading import Thread
# import time
# class Myt(Thread):
# def run(self):
# print('开启子线程 start')
# time.sleep(2)
# print('开启子线程 end')
# if __name__ == '__main__':
# t = Myt() #类实例化得到对象
# t.start()
### 创建速度
# 进程需要申请内存空间 所以创建慢
# 线程相当于直接告诉操作系统去开启线程,不需要申请空间,所以快
### 线程共享资源
### 线程的join方法:等待被join的线程结束
# 进程的join方法:等待被join的进程结束
### 线程的其它相关用法:getname setname enumerate()(查看当前活跃的进程) currentThread(查看当前进程对象)
### 守护线程:守护的是进程运行周期
课程引入
x = 100
def func():
global x
print(x) #100
change()
print(x) #50
def change():
global x
x = 50
func()
线程锁
from threading import Thread, Lock
import time
lock = Lock()
x = 0
def task():
global x
lock.acquire()
for i in range(200000):
x += 1
# 分析:
# t 的 x 刚拿到0 保存状态 就被切了
# t1 的 x 拿到0 进行 +1 1
# t 又获得运行了 x = 0 +1 1
# 思考:一个加了几次1? 加了两次1 真实运算出来的数字本来应该+2 实际只+1
# 这就产生了数据安全问题(通过线程锁解决)
lock.release()
if __name__ == '__main__':
t = Thread(target=task)
t1 = Thread(target=task)
t2 = Thread(target=task)
t.start()
t1.start()
t2.start()
t.join()
t1.join()
t2.join()
#time.sleep(10)
print(x)
#因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题
死锁问题
from threading import Thread, Lock
mutex1 = Lock()
mutex2 = Lock()
import time
class Myt(Thread):
def run(self): #(执行方法)
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2')
mutex2.release()
print(f'{self.name} 释放了 锁2')
mutex1.release()
print(f'{self.name} 释放了 锁1')
def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1')
mutex1.release()
print(f'{self.name} 释放了 锁1')
mutex2.release()
print(f'{self.name} 释放了 锁2')
if __name__ == '__main__':
for i in range(3):
t = Myt()
t.start()
# 两个线程
# 线程1拿到了(锁头2)想要往下执行需要(锁头1)
# 线程2拿到了(锁头1)想要往下执行需要(锁头2)
# 互相都拿到了彼此想要执行的必须条件,互相都不放手里的锁头,导致了死锁问题
递归锁
from threading import Thread,Lock,RLock
# 递归锁(RLock) :在同一个线程内可以被多次acquire
# 如何释放 :内部相当于维护了计数器,也就是说同一个线程,acquire了几次,就要被release几次
mutex1 = RLock()
mutex2 = mutex1 #(这样写就表示两把锁是同一把锁)
#mutex2 = RLock()#这样写就表示这是两把锁,线程拿到的就不是同一把锁(不能这样写)
import time
class Myt(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
mutex1.release()
print(f'{self.name} 释放了 锁1')
def task2(self):
mutex2.acquire()
print(f'{self.name} 抢到了 锁2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 抢到了 锁1 ')
mutex1.release()
print(f'{self.name} 释放了 锁1 ')
mutex2.release()
print(f'{self.name} 释放了 锁2 ')
for i in range(3):
t = Myt()
t.start()
信号量
from threading import Thread, currentThread,Semaphore
import time
def task():
sm.acquire()
print(f'{currentThread().name} 在执行')
time.sleep(3)
sm.release()
if __name__ == '__main__':
sm = Semaphore(5) #表示同时只有5个线程可以获得semaphore,即可以限制最大连接数为5)
for i in range(15):
t = Thread(target=task)
t.start()
'''信号量:同进程一样,Semaphore管理一个内置计数器,每当调用acquire()
时内置计数器 -1;调用release()时内置计数器 +1;计数器不能小于0,当计数器为0时,
acquire()将阻塞线程直到其它线程调用release()
'''
GIL锁
'''
在Cpython解释器中有一把GIL锁(全局解释器锁),GIL锁本质也是一把互斥锁
GIL锁:导致了同一个进程下,同一时间只能运行一个线程,无法利用多核优势
同一个进程下多个线程只能实现并发不能实现并行
为什么要有GIL?
因为Cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁
导致了同一进程下,同一时间只能运行一个线程,无法利用多核优势
分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案有:
方案一:开启四个进程
方案二:一个进程下,开启四个线程
计算密集型 推荐使用多进程
分析:每个任务都要计算10s
多线程:在同一时间只能一个线程会被执行,也就意味着每个10s都不能省,分开每个都要计算10s,共40+s
多进程:可以并行的执行多个线程,四个任务一起执行,需要时间为10s+开启进程的时间
io密集型 推荐使用多线程
分析:因为是io密集型,四个任务每个任务大部分时间都在io
每个任务io 10s,但真正需要运行代码的时间只要0.5s
多线程可以实现并发,每个线程io时间不怎么占用cpu,10s + 四个任务计算时间,相比多进程,节约开启进程的时间
多进程可以实现并行,10s + 1个任务的执行时间 + 进程开启的时间
'''
多进程vs多线程
from threading import Thread
from multiprocessing import Process
import time
# 计算密集型
# def work1():
# res = 0
# for i in range(100000000):
# res *= i
# if __name__ == '__main__':
# t_list = []
# start = time.time()
# for i in range(4):
# t = Thread(target=work1)
# t_list.append(t)
# t.start()
# #p = Process(target=work1)
# for t in t_list:
# t.join()
# end = time.time()
# print('多线程',end - start) #多线程 31.762816667556763
#if __name__ == '__main__':
# p_list = []
# start = time.time()
# for i in range(4):
# p = Process(target=work1)
# p_list.append(p)
# p.start()
# for p in p_list:
# p.join()
# end = time.time()
# print('多线程', end - start)##17.476999282836914
# io 密集型
def work1():
x = 1+1
time.sleep(5)
print(x)
# 多线程
# if __name__ == '__main__':
# t_list = []
# start = time.time()
# for i in range(3):
# t = Thread(target=work1)
# t_list.append(t)
# t.start()
# for t in t_list:
# t.join()
# end = time.time()
# print('多线程',end - start) #多线程 5.001286029815674
# 多进程
if __name__ == '__main__':
p_list = []
start = time.time()
for i in range(3):
p = Process(target=work1)
p_list.append(p)
p.start()
for t in p_list:
t.join()
end = time.time()
print('多线程',end - start) #5.848334312438965