小结
上节课回顾
'''
线程锁(Lock):本质就是互斥锁(*****)
使用目的:保证数据安全
死锁问题:线程1抢到了锁A没释放
线程2抢到了锁B没释放
线程1往下执行需要锁B
线程2往下执行需要锁A
解决方案:递归锁(RLock)
特点:只有同一个线程下可以多次acquire,acquire了几次就要release几次
信号量:
实现:定制了锁的个数,也就意味着最多有几个线程可以抢到锁头,这几个线程就可以实现并发
GIL(****)
什么是GIL:
Cpython中有GIL锁(全局解释器锁),GIL锁本质就是一把互斥锁,
GIL锁导致了 python 的同一个进程同一时刻下只有一个线程在执行代码
为什么要有?
因为Cpython自带的垃圾回收机制不是线程安全的,所以要有GIL锁
多进程vs多线程(*****)
计算密集型任务===》推荐使用多进程解决===》要利用多核优势,并行的去计算
io密集型任务===》推荐使用多线程解决===》大部分时间都在io,并且开启一个线程比开启一个进程速度要快的多
大部分的需求都是io密集型,因为大部分的软件都是基于网络的(存在网络io)
cpu主要负责计算
########### 今日内容 ###########
线程queue 线程定时器 (*)
线程queue三种用法
1.先进先出
2.先进后出
3.优先级 优先级判断 通常用数字表示 数字小的先出
多线程并发socket服务端(*****)
具体看代码,要看明白
进程池,线程池(****)
必须把进程池、线程池的概念搞懂
能看懂代码
协程:
'''
线程queue
###### 先进先出 ######
# import queue
# q = queue.Queue() #拿到线程queue 对象
# q.put('123') # 往queue里放数据
# q.put('456')
# q.put('789')
#
# print(q.get()) #123 #取出 queue里面的数据(这是先进先出)
# print(q.get()) #456
# print(q.get()) #789
# q.task_done()
# q.task_done()
# q.task_done()
# q.join()
'''
task_done() 和 join()
每次从queue中get一个数据之后,当处理好相关问题,最后调用该方法,
以提示q.join()是否停止阻塞,让线程向前执行或者退出;
q.join(),阻塞,直到queue中的数据均被删除或者处理。为队列中的每一项都调用一次
如:上述例子,如果put了三次(相当于放了三个值),而只get了两次(取
出了两个值),然后用了两次task_done(),那么最后的join()会阻塞,
知道把队列里的数据全部处理完后才停止阻塞
'''
######## 堆栈 先进后出 #########
# import queue
# q = queue.LifoQueue() #堆栈 先进后出
# q.put('123')
# q.put('456')
# q.put('789')
#
# print(q.get())#789
# print(q.get())#456
# print(q.get())#123
######## 优先级 ########
import queue
q = queue.PriorityQueue() #可以根据优先级取数据
# 通常这个元组的第一个值是int类型 数小的先出
q.put((50, '123'))
q.put((60, '456'))
q.put((10, '789'))
print(q.get()) #(10, '789')
print(q.get()) #(50, '123')
print(q.get()) #(60, '456')
线程定时器
from threading import Timer
import time
def task():
print('线程执行了')
time.sleep(2)
print('线程结束了')
t = Timer(5, task) #定时5s后开启线程,不会阻塞下面的代码
t.start()
print('123123') #正常执行,123123
进程池和线程池
'''
进程池线程池:
池的功能限制进程数或线程数。
什么时候需要限制?
当并发的任务数量远远大于计算机所能承受的范围,
即无法一次性开启过多的任务数量,就应该考虑去限制我进程数或者线程数,
从而保证服务器不蹦
'''
#1.导入进程池或线程池模块
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time
# 2.构造一个工作
def task(i):
#print(f'{currentThread().name} 在执行 {i}')
print(f'{current_process().name} 在执行 {i}')
time.sleep(1)
return i**2
if __name__ == '__main__':
#pool = ThreadPoolExecutor(4) # 池子里只有4个线程
#3.定制池中进程或线程 数量
pool = ProcessPoolExecutor(4) #池子里只有4个进程
fu_list = []
# 首先考虑开启任务数量(开启任务较多考虑使用进程池线程池)
for i in range(20):
#pool.submit(task, i) #提交池,把要处理的函数名及其它参数放进去
#4. 提交池,传相应参数
future = pool.submit(task, i) #task任务要做20次,4个进程负责做这个事
#print(future.result()) #如果没有结果一直等待拿到结果,这样导致了所有任务都在串行
fu_list.append(future)
#5.关闭池
pool.shutdown() #关闭了池入口,会等待所有的任务执行完,结束阻塞
for fu in fu_list:
#6.打印结果
print(fu.result())
进程池和线程池02
'''
进程池线程池:
池的功能限制进程数或线程数
什么时候限制?
当并发的任务数量远远大于计算机所能承受的范围,即无法一次性开启过多的任务数量
这时就应该考虑去限制开启的进程数或线程数,从而保证服务器不崩
理解为提交任务的两种方式
同步:提交了一个任务,必须等任务执行完了(拿到返回值),才能执行下一行代码
异步:提交了一个任务,,不要等执行完了,可以直接执行下一行代码
'''
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import currentThread
from multiprocessing import current_process
import time
def task1(i):
#print(f'{currentThread().name} 在执行 {i}')
print(f'{current_process().name} 在执行 {i}')
time.sleep(1)
return i**2
def parse(future):
# 处理拿到的结果
print(future.result())
if __name__ == '__main__':
#pool = ThreadPoolExecutor(4)
pool = ProcessPoolExecutor(4)
for i in range(20):
future = pool.submit(task1, i)
future.add_done_callback(parse)
# 为当前任务绑定一个函数,在当前任务执行结束的时候会触发这个函数
#会把future对象作为参数传函数
#这个称之为回调函数,处理完了回来就调用这个函数
协程
'''
python的 线程用的是操作系统 原生的线程
协程:单线程下实现并发
并发:切换+保存状态
多线程:操作系统帮你实现的(同一进程同一时间只能运行一个线程),如果遇到io会被切换,执行时间过长也会切换,实现一个雨露均沾的效果
什么样的协程是有意义的?
遇到io切换的时候才有意义
具体:
协程概念本质是程序员抽象出来的,操作系统根本不知道协程的存在,
也就是说来了一个线程我自己遇到io,我自己线程内部直接切到自己
的别的任务上,操作系统根本发现不了,也就是实现了单线程下效率最高
优点:
自己控制切换要比操作系统切换快的多
缺点:
对比多线程
自己要检测所有的io,但凡有一个阻塞整体都要跟着阻塞
对比多进程
无法利用多核优势
为什么要有协程(遇到io切换 )?
自己控制切换要比操作系统切换快的多,降低了单个线程的io时间
'''
# import time
# def eat():
# print('eat 1')
# # 疯狂的计算没有io
# time.sleep(2)
#
# def play():
# print('play 1')
# # 疯狂的计算没有io
# time.sleep(3)
# play()
# eat() #5s
# import time
# def func():
# while True:
# 1000000+1
# yield
# def func2():
# g = func()
# for i in range(100000000):
# a=i + 1
# next(g)
# print(a)
# start = time.time()
# func2()
# end = time.time()
# print(end - start) #24.284388780593872
import time
def func():
for i in range(100000000):
i + 1
def func2():
for i in range(100000000):
i + 1
start= time.time()
func()
func2()
end= time.time()
print(end - start) #16.235928535461426
'''对比:通过yeild切换运行的时间反而比串行 更消耗时间,这样实现的协程是没有意义的'''
协程02
from gevent import monkey
monkey.patch_all() #打了一个补丁,可以实现捕获非gevent的io
import gevent
import time
def eat():
print('eat 1')
time.sleep(2)
print('eat 2')
def play():
print('play 1')
time.sleep(3)
print('play 2')
start = time.time()
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
end = time.time()
print(end - start) #3.00417160987854
### 利用gvent模块在捕获非gvent的io,实现单线程并发,提高效率