1.线程回调
回调:
在线程池/进程池
每次提交任务 都会返回一个表示任务的对象 Future对象
Future对象具备一个绑定方法 add_done_callback 用于指定回调函数
add 意味着可以添加多个回调函数
如果直接使用Thread的话 如何完成回调:
from threading import Thread
import time
# res = None
def call_back(res):
print("任务结果拿到了:%s" % res)
def parser(res):
print("任务结果拿到了:%s" % res)
def task(callback):
# global res
print("run")
time.sleep(1)
# # return 100
res = 100 # 表示任务结果
callback(res) # 执行回调函数 并传入任务结果
t = Thread(target=task,args=(parser,))
t.start()
# t.join()
# print(res)
print("over")
2.线程中的队列
from queue import Queue,LifoQueue,PriorityQueue
1.Queue() 先进先出
# 与进程中的Joinablequeue 使用方式一模一样 但是 不具备IPC
# q = Queue()
#
# q.put("123")
# q.put("456")
#
# print(q.get())
# print(q.get())
#
# # print(q.get(block=True,timeout=3))
# q.task_done()
# q.task_done()
# q.join()
# print("over")
2.LifoQueue
last in first out 后进先出 先进 后出 模拟堆栈
# 除顺序以外别的都一样
# lq = LifoQueue()
#
# lq.put("123")
# lq.put("456")
#
# print(lq.get())
# print(lq.get())
3. PriorityQueue
具备优先级的队列
# 可以存储一个可以比较大小的对象 比较越小的优先级越高
自定义对象 不能使用比较运算符 所以不能存储
q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可
以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))
print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
3.事件EVENT
事件Event:
事件,表示发生了某件事情,我们可以去关注某个事件然后采取一
些行动
本质上事件是用来线程间通讯的 ,用于状态同步
from threading import Thread,Event
import time
boot_event = Event()
# boot_event.clear() 回复事件的状态为False
# boot_event.is_set() 返回事件的状态
# boot_event.wait()等待事件发生 ,就是等待事件被设置
为True
# boot_event.set() 设置事件为True
def boot_server():
print("正在启动服务器......")
time.sleep(3)
print("服务器启动成功!")
boot_event.set() # 标记事件已经发生了
def connect_server():
boot_event.wait() # 等待事件发生
print("链接服务器成功!")
t1 = Thread(target=boot_server)
t1.start()
t2 = Thread(target=connect_server)
t2.start()
4.协程
协程的目的就是要在单线程中实现并发
并发:
多个任务看起来是同时运行 本质是切换+保存状态
1.生成器中yiled实现并发效果
# 使用生成器来实现 单线 并发多个任务
import time
# def func1():
# a = 1
# for i in range(10000000):
# a += 1
# # print("a run")
# yield
#
# def func2():
# res = func1()
# a = 1
# for i in range(10000000):
# a += 1
# # print("b run")
# next(res)
#
# st = time.time()
# func2()
# print(time.time() - st)
def func1():
a = 1
for i in range(10000000):
a += 1
def func2():
a = 1
for i in range(10000000):
a += 1
st = time.time()
func1()
func2()
print(time.time() - st)
经过测试 单线程并发并不能提高性能 , 对于计算密集型
任务而言
对于IO操作而言 必须具备能够检测io操作 并自动切换其他
区任务 ,这才提高效率
2.greenlet
greenlet需要手动切换,而且不能 检测IO ,是对yie
ld的封装
import greenlet
import time
def task1():
print("task1 run")
g2.switch()
print("task1 over")
g2.switch()
def task2():
print("task2 run")
g1.switch()
time.sleep(2)
print("task2 over")
g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch()
# g2.switch()
print("主 over")
3.gevent
gevent 是协程 ,为轻量级线程 ,也称之为微线程,
是应用程序级别的任务调度方式
提高效率:
在Cpython中有GIL锁 导致多线程不能并行执行丧失了 多
核优势,即使开启了多线程也只能并发, 这时候完全 可以
使用协程来实现并发
协程的优缺点:
优点:不会占用更多无用的资源
缺点:如果是计算任务 使用协程反而降低效率
IO密集型任务 采用协程
5.猴子补丁
本质就是把原本阻塞的代码 悄悄换成非阻塞代码
例如 Queue.get(block=false)
当执行get而取不到值时 会抛出异常 只需捕获异常 然后
在发生时切换到其他任务 ,就可以实现遇到IO 切换任务
# gevent 不具备检测IO的能力 需要为它打补丁 打上补
丁之后就能检测IO
# 注意补丁一定打在最上面 必须保证导入模块前就打好补丁
from gevent import monkey
monkey.patch_all() #
from threading import current_thread
import gevent,time
def task1():
print(current_thread(),1)
print("task1 run")
# gevent.sleep(3)
time.sleep(3)
print("task1 over")
def task2():
print(current_thread(),2)
print("task2 run")
print("task2 over")
# spawn 用于创建一个协程任务
g1 = gevent.spawn(task1)
g2 = gevent.spawn(task2)
# 任务要执行,必须保证主线程没挂 因为所有协程任务
都是主线在执行 ,必须调用join来等待协程任务
# g1.join()
# g2.join()
# 理论上等待执行时间最长的任务就行 , 但是不清楚
谁的时间长 可以全部join
gevent.joinall([g1,g2])
print("over")
协程:
在Cpython 多线程无法并行 执行 ,在IO密集任务中
并且并发量较大,无法开启更多的线程时,造成后续的任
务无法处理,即使前面都在等待IO
就可以使用单线程 实现并发,当某一个任务处于IO阻塞时
,可以切换到其他的任务来执行,可以充分利用CPU时间片
,如果任务量足够,可以占用CPU直到超时
最终的解决方案:多进程 +单线程 + 协程
对于更多的任务:
1.集群 所有服务器干的活都一样
2.分布式 每个服务器就干某个活