并发任务池
concurrent.futures模块提供了使用工作线程或进程池运行任务的接口。
线程池和进程池的API是一致的,所以应用只需要做最小的修改就可以在线程和进程之间进行切换
这个模块提供了两种类型的类与这些池交互。执行器(executor)用来管理工作线程或进程池,future用来管理计算的结果。
要使用一个工作线程或进程池,应用要创建适当的执行器类的一个实例,然后向它提交任务来运行。
每个任务启动时,会返回一个Future实例。需要任务的结果时,应用可以使用Future阻塞,直到得到结果。
目前已经提供了不同的API,可以很方便地等待任务完成,所以不需要直接管理Future对象。
Future对象
Future对象是一个比较奇特的对象,当然并不仅仅只是在这里有,像asyncio、tornado等,都有Future对象。怎么理解这个对象呢?Future直译是未来对象,换句话说,就是将我们的任务(函数)进行一层包裹,封装为未来对象。可以把Future看成是任务的一个容器,除了能够销毁任务,里面还包含了任务的执行状态。任务没有执行完没有关系,先占一个坑,绑定一个回调。我去异步执行其他的任务,当该任务完成后,通过future.set_result将任务的返回值设置进去,一旦设置了,那么会自动的触发回调函数,可以通过future.result将返回值获取出来。
类似于nodejs里面的promise
总结一下就是:
每一个任务都会对应一个Future对象,这里的任务就是我们要执行的函数,每个任务在提交到线程池当中运行的时候,都会产生一个Future对象,两者是一一对应的。这个Future对象就可以看成是任务的容器,保存了任务的执行状态等等。
当任务完成后,会将返回值设置到Future对象里面去,此时对应的Future对象也就完成了。
创建一个Future对象
当我们将一个任务提交到线程池里面运行时,会立即返回一个对象,这个对象就叫做Future对象,里面包含了任务的执行状态等等,当然我们也可以手动创建一个Future对象
from concurrent.futures import Future
# 创建一个Future对象
future = Future()
def callback(future):
print("当set_result的时候,执行回调,我也可以拿到返回值:", future.result())
# 通过调用add_done_callback方法,可以将该future绑定一个回调函数
# 这里只需要传入函数名即可,future会自动传递给callback的第一个参数
# 如果这里需要多个参数的话,怎么办呢?很简单,使用偏函数即可
future.add_done_callback(callback)
# 当什么时候会触发回调函数的执行呢?
# 当future执行set_result的时候
future.set_result("return value")
"""
当set_result的时候,执行回调,我也可以拿到返回值: return value
"""
值得注意的是:可以多次set_result,但是后面的会覆盖前面的,并且result()获取可以获取多次
from concurrent.futures import Future
# 创建一个Future对象
future = Future()
future.set_result(123)
future.set_result(456)
print(future.result()) # 456
print(future.result()) # 456
print(future.result()) # 456
通过提交任务创建一个Future对象
我们说过,将任务提交到线程池里面运行的时候,会立即返回,从而得到一个Future对象。这个Future对象里面就包含了任务的执行状态,比如此时是处于暂停、运行中还是完成等等,并且在任务执行完毕之后,还可以拿到返回值
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
# 创建一个线程池
# 里面还可以指定max_workers参数,表示最多创建多少个线程
# 如果不指定,那么每一个任务都会为其创建一个线程
executor = ThreadPoolExecutor()
# 通过submit就直接将任务提交到线程池里面了,一旦提交,就会立刻运行
# 提交之后,相当于开启了一个新的线程,主线程会继续往下走
# 参数按照函数名,对应参数提交即可,切记不可写成task("古明地觉", 16, 3),这样就变成调用了
future = executor.submit(task, "古明地觉", 16, 3)
# 由于我们n=3,所以会休眠3秒
print(future) # <Future at 0x226b860 state=running>
# 让主程序也休眠3s
time.sleep(3)
# 此时再打印
print(future) # <Future at 0x226b860 state=finished returned str>
"""
可以看到,一开始任务处于running,正在运行状态
3s过后,任务处于finished,完成状态,并告诉我们返回了一个str
"""
获取任务的返回值
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
future = executor.submit(task, "古明地觉", 16, 3)
start_time = time.perf_counter()
print(future.result()) # name is 古明地觉, age is 16, sleep 3s
print(f"耗时:{time.perf_counter() - start_time}") # 耗时:2.999359371
"""
可以看到,打印future.result()这一步花了将近3s。其实也不难理解,future.result()是干嘛的
就是为了获取任务的返回值,可以任务都还没有执行完毕,它又从哪里获取呢?
所以只能先等待任务执行完毕,将返回值通过set_result自动地设置到future里面之后,外界future.result()才能够获取到值
所以future.result()这一步实际上是会阻塞的,会等待任务执行完毕
"""
绑定回调
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
def callback(future):
print(future.result())
executor = ThreadPoolExecutor()
future = executor.submit(task, "古明地觉", 16, 3)
time.sleep(5)
future.add_done_callback(callback)
"""
name is 古明地觉, age is 16, sleep 3s
"""
# 等到任务执行完毕之后,依旧会获取到返回值
# 这里我加上time.sleep(5),只是为了证明即使等任务完成之后再去添加回调,依旧是可以的
# 任务完成之前添加回调,那么会在任务完成后触发回调
# 任务完成之后添加回调,由于任务完成,代表此时的future已经有值了,或者说已经set_result了,那么会立即触发回调
# 因此time.sleep(5)完全可以去掉
提交多个任务
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
futures = [executor.submit(task, "古明地觉", 16, 3),
executor.submit(task, "古明地觉", 16, 4),
executor.submit(task, "古明地觉", 16, 1),
]
# 此时都处于running
print(futures) # [<Future at 0x226b860 state=running>, <Future at 0x9f4b160 state=running>, <Future at 0x9f510f0 state=running>]
time.sleep(3)
# 主程序修庙3s后,futures[0]和futures[2]处于finished,futures[1]处于running
print(futures)
获取任务的返回值
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
futures = [executor.submit(task, "古明地觉", 16, 5),
executor.submit(task, "古明地觉", 16, 2),
executor.submit(task, "古明地觉", 16, 4),
executor.submit(task, "古明地觉", 16, 3),
executor.submit(task, "古明地觉", 16, 6),
]
"""
此时的futures里面相当于有了5个future,记做future1,future2,future3,future4,future5
"""
for future in futures:
print(future.result())
"""
name is 古明地觉, age is 16, sleep 5s
name is 古明地觉, age is 16, sleep 2s
name is 古明地觉, age is 16, sleep 4s
name is 古明地觉, age is 16, sleep 3s
name is 古明地觉, age is 16, sleep 6s
"""
# 当我们使用for循环的时候,实际上会依次遍历这5future,所以返回值的顺序就是我们添加的future的顺序
# 由于future1对应的任务休眠了5s,那么必须等到5s后,future1里面才会有值
# 但由于这五个任务是并发执行的,future2、future3、future4由于只休眠了2s、4s、3s,所以肯定会先执行完毕,然后执行set_result,将返回值设置到对应的future里
# 但是python的for循环,不可能在第一次迭代还没有结束,就去执行第二次迭代。
# 因为futures里面的几个future顺序已经一开始就被定好了,只有当第一个future.result()执行完成之后,才会执行第二个future.result()、第三个。。。
# 即便后面的任务已经执行完毕,但由于for循环的顺序,也只能等着,直到前面的future.result()执行完毕。
# 所以会先打印"name is 古明地觉, age is 16, sleep 5s",当这句打印完时,
# 由于后面的任务早已执行完毕,只是由于第一个future.result()太慢,又把路给堵住了,才导致后面的无法输出
# 因此第一个future.result()执行完毕之后,后面的3个future.result()会瞬间执行,从而立刻打印出
"""
name is 古明地觉, age is 16, sleep 2s
name is 古明地觉, age is 16, sleep 4s
name is 古明地觉, age is 16, sleep 3s
"""
# 最后一个任务由于是6s,因此再过1s后,打印"name is 古明地觉, age is 16, sleep 6s"
查看任务是否执行完毕
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
# 之前我们说过,可以打印future来查看任务的状态,其实还有一种方法来确定任务是否完成
future = executor.submit(task, "椎名真白", 16, 3)
while True:
if future.done():
print(f"任务执行完毕:{future.done()}")
break
else:
print(f"任务尚未执行完毕:{future.done()}")
time.sleep(1)
"""
任务尚未执行完毕:False
任务尚未执行完毕:False
任务尚未执行完毕:False
任务尚未执行完毕:False
任务执行完毕:True
"""
# 当任务尚未执行完毕的时候,future.done()是False,执行完毕之后打印为True
使用map来提交多个任务
使用map来提交会更简单一些,如果任务的量比较多,并且不关心某个具体任务设置回调的话,可以使用map
那么如何使用map提交任务呢?
# 如果我想将以下这种有submit提交的方式,改用map要怎么做呢?
"""
futures = [executor.submit(task, "椎名真白", 16, 5),
executor.submit(task, "古明地觉", 16, 2),
executor.submit(task, "古明地恋", 15, 4),
executor.submit(task, "坂上智代", 19, 3),
executor.submit(task, "春日野穹", 16, 6)]
"""
# 可以直接改成
"""
futures = executor.map(task,
["椎名真白", "古明地觉", "古明地恋", "坂上智代", "春日野穹"],
[16, 16, 15, 19, 16],
[5, 2, 4, 3, 6])
"""
map这样写确实是简化了不少,但是我们也可以看到使用这种方式就无法为某个具体的任务添加回调函数了。但是内部是如何实现的呢
# 可以看一下map的实现过程,由于map和submit都是ThreadPoolExecutor类下的一个方法,而这个类又继承自Executor这个基类
# 所以看看这个类的部分源码,将注释去掉,长这个样子
"""
class Executor(object):
def submit(self, fn, *args, **kwargs):
raise NotImplementedError()
def map(self, fn, *iterables, timeout=None, chunksize=1):
if timeout is not None:
end_time = timeout + time.time()
# 可以看到,map本质上还是调用了submit方法
fs = [self.submit(fn, *args) for args in zip(*iterables)]
# Yield must be hidden in closure so that the futures are submitted
# before the first iterator value is required.
def result_iterator():
try:
# reverse to keep finishing order
fs.reverse()
while fs:
# Careful not to keep a reference to the popped future
if timeout is None:
yield fs.pop().result()
else:
yield fs.pop().result(end_time - time.time())
finally:
for future in fs:
future.cancel()
return result_iterator()
"""
# 将map逻辑简单实现一下看看
def map(*iterables):
print([args for args in zip(*iterables)])
map(["椎名真白", "古明地觉", "古明地恋", "坂上智代", "春日野穹"],
[16, 16, 15, 19, 16],
[5, 2, 4, 3, 6])
# [('椎名真白', 16, 5), ('古明地觉', 16, 2), ('古明地恋', 15, 4), ('坂上智代', 19, 3), ('春日野穹', 16, 6)]
# 当进行遍历的时候,一次将参数传递给submit
# 所以map本质上还是调用了submit
# 记住:不可以这么添加,这样添加就错了。这样添加转化成submit就搞反了
"""
futures = executor.map(task,
['椎名真白', 16, 5],
['古明地觉', 16, 2],
['古明地恋', 15, 4],
['坂上智代', 19, 3],
['春日野穹', 16, 6])
"""
观察map
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
futures = executor.map(task,
["椎名真白", "古明地觉", "古明地恋", "坂上智代", "春日野穹"],
[16, 16, 15, 19, 16],
[5, 2, 4, 3, 6])
# 此时几个future是作为一个生成器返回的
print(futures) # <generator object Executor.map.<locals>.result_iterator at 0x0000000009F4C840>
# 由于这个底层调用的是submit,当我使用for循环的时候,执行的逻辑和submit还是一样的
# 唯一的区别是,此时不需要再调用result了,因为返回来的就是任务的返回值
"""
while fs:
# Careful not to keep a reference to the popped future
if timeout is None:
# 可以看到,直接帮我们调用result方法了,无需我们手动调用了
yield fs.pop().result()
else:
yield fs.pop().result(end_time - time.time())
"""
# 此时的future,相当于submit当中的future.result()
for future in futures:
print(future)
"""
name is 椎名真白, age is 16, sleep 5s
name is 古明地觉, age is 16, sleep 2s
name is 古明地恋, age is 15, sleep 4s
name is 坂上智代, age is 19, sleep 3s
name is 春日野穹, age is 16, sleep 6s
"""
from concurrent.futures import ThreadPoolExecutor
import time
import pprint
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
futures = executor.map(task,
["椎名真白", "古明地觉", "古明地恋", "坂上智代", "春日野穹"],
[16, 16, 15, 19, 16],
[5, 2, 4, 3, 6])
# 如果这里我改一下,改成list(futures)
# 分析:
# 由于futures是一个生成器,当我转化为list之后,会将里面所有的值全部生产出来
# 这就意味着,要将所有任务的返回值都获取到才行。
# 尽管我们不需要调用result,但result这一步是无法避免的,从源码也可以看出,调用map的时候内部帮我们自动处理了
# 但依旧是调用了future.result方法,调用的时候依旧会阻塞。futures此时是一个生成器,转化为list会将所有的值全部产出
# 而耗时最长的任务是6s,因此这一步会阻塞6s,6s过后,会打印所有任务的返回值
start_time = time.perf_counter()
pprint.pprint(list(futures))
print(f"总耗时:{time.perf_counter() - start_time}")
"""
['name is 椎名真白, age is 16, sleep 5s',
'name is 古明地觉, age is 16, sleep 2s',
'name is 古明地恋, age is 15, sleep 4s',
'name is 坂上智代, age is 19, sleep 3s',
'name is 春日野穹, age is 16, sleep 6s']
总耗时:6.00001767
"""
按照顺序等待任务
但是现在我有这么一个需求,就是哪个任务先完成,哪个就先返回,这要怎么做呢?
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
futures = [executor.submit(task, "椎名真白", 16, 5),
executor.submit(task, "古明地觉", 16, 2),
executor.submit(task, "古明地恋", 15, 4),
executor.submit(task, "坂上智代", 19, 3),
executor.submit(task, "春日野穹", 16, 6)]
for future in as_completed(futures):
print(future.result())
"""
name is 古明地觉, age is 16, sleep 2s
name is 坂上智代, age is 19, sleep 3s
name is 古明地恋, age is 15, sleep 4s
name is 椎名真白, age is 16, sleep 5s
name is 春日野穹, age is 16, sleep 6s
"""
# 只需要将futures传递给as_completed即可
这里提出一个问题
那就是as_completed里面可不可以传入map,换句话说,上面的那个例子可不可以这样写
futures = executor.map(task,
["椎名真白", "古明地觉", "古明地恋", "坂上智代", "春日野穹"],
[16, 16, 15, 19, 16],
[5, 2, 4, 3, 6])
for future in as_completed(futures):
print(future)
答案是显然不行的。为什么?看一看源码,顺便了解一下这个as_completed是如何做到按照任务完成的先后顺序返回的
def as_completed(fs, timeout=None):
if timeout is not None:
end_time = timeout + time.time()
# 将我们的传入的futures(多个future组成的列表)放到一个集合里
# 为什么要放到集合里,下面会说
fs = set(fs)
# 计算出future的总数量
total_futures = len(fs)
# 这个方法不用管
with _AcquireFutures(fs):
# 我们看到对于那些已经完成或者取消的任务,会放到一个finished的集合里
finished = set(
f for f in fs
if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
# 然后将finished从fs里面减掉,所以要把fs放到集合里面
# 剩下的则是pending状态
pending = fs - finished
waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
# 转化为列表
finished = list(finished)
try:
# 我们看到这里调用了yield from
# 为什么要在这里进行yield from
# 因为我们肯定是先提交任务对吧?然后再将futures放到as_completed里面
# 但是我们中间可能会执行其他的逻辑,有可能在我执行as_completed之前,一部分任务已经完成了
# 所以这一步是将已经完成的任务(准确的说是对应的future)一个一个的yield出来,此时yield出来的任务(future)是无序的。
# 因为在调用as_completed之前就已经完成了,所以此时谁先返回对我们的这个as_completed来说是没有意义的
yield from _yield_finished_futures(finished, waiter,
ref_collect=(fs,))
# 然后对pending,也就是没有完成的任务进行遍历
while pending:
if timeout is None:
wait_timeout = None
else:
wait_timeout = end_time - time.time()
if wait_timeout < 0:
raise TimeoutError(
'%d (of %d) futures unfinished' % (
len(pending), total_futures))
waiter.event.wait(wait_timeout)
with waiter.lock:
finished = waiter.finished_futures
waiter.finished_futures = []
waiter.event.clear()
# reverse to keep finishing order
finished.reverse()
# 这一步是继续yield,哪个先返回,哪个就先被yield出去
yield from _yield_finished_futures(finished, waiter,
ref_collect=(fs, pending))
finally:
# Remove waiter from unfinished futures
for f in fs:
with f._condition:
f._waiters.remove(waiter)
"""
所以里面出现了两个yield from
第一个是yield出已经完成的任务(future)
第二个是将后续先完成的任务一个一个的yield出去
"""
# 那么关于as_completed为什么不能传入map对象就已经很清楚了
"""
当在遍历的时候,需要检测任务的运行状态。可以对map来说,遍历,就直接相当于获取值了。
因此拿到的不是future对象,而是任务的返回值(或者说future.result())
其实从代码最开始的fs = set(fs)就能看出来了,之前说过fs使用map的话,得到的是生成器
如果再使用集合转化的话,那么会等到所有任务完成之后才会执行完毕
也就是说此时的fs应该是所有任务的返回值组成的元组,这样的话,后面执行肯定会报错的。
所以as_completed只能用于多个submit组成的列表
"""
如何取消一个任务
我们可以将任务添加到线程池当中,但是如果我们想取消怎么办呢?
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
future1 = executor.submit(task, "椎名真白", 16, 5)
future2 = executor.submit(task, "古明地觉", 16, 2)
future3 = executor.submit(task, "古明地恋", 15, 4)
# 取消任务,可以使用future.cancel
print(future3.cancel()) # False
但是我们发现调用cancel方法的时候,返回的是False,这是为什么?因为任务已经被提交到线程池里面了,任务已经运行了,只有在任务还没有运行时,取消才会成功。可这不矛盾了吗?任务一旦提交就会运行,只有不运行才会取消成功,这怎么办?还记得线程池的一个叫做max_workers的参数吗?控制线程池内线程数量的,我们可以将最大的任务数设置为2,那么当第三个任务进去的时候,就不会执行了,而是处于等待状态
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
print(f"sleep {n}")
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
# 此时最多只能同时执行两个任务
executor = ThreadPoolExecutor(max_workers=2)
future1 = executor.submit(task, "椎名真白", 16, 5)
future2 = executor.submit(task, "古明地觉", 16, 2)
future3 = executor.submit(task, "古明地恋", 15, 4)
print(future3.cancel()) # True
"""
sleep 5
sleep 2
"""
# 可以看到打印为True,说明取消成功了
# 而sleep 4也没有被打印
任务中的异常
如果任务当中产生了一个异常,同样会被保存到future当中。可以通过future.exception获取
from concurrent.futures import ThreadPoolExecutor
def task1():
1 / 0
def task2():
pass
executor = ThreadPoolExecutor(max_workers=2)
future1 = executor.submit(task1)
future2 = executor.submit(task2)
print(future1.exception()) # division by zero
print(future2.exception()) # None
# 或者
try:
future1.result()
except Exception as e:
print(e) # division by zero
等待所有任务完成
一种方法是遍历所有的future,调用它们result方法
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
future1 = executor.submit(task, "椎名真白", 16, 5)
future2 = executor.submit(task, "古明地觉", 16, 2)
future3 = executor.submit(task, "古明地恋", 15, 4)
# 这里是不会阻塞的
print(123)
for future in [future1, future2, future3]:
print(future.result())
print(456)
"""
123
name is 椎名真白, age is 16, sleep 5s
name is 古明地觉, age is 16, sleep 2s
name is 古明地恋, age is 15, sleep 4s
456
"""
使用wait方法
from concurrent.futures import ThreadPoolExecutor, wait
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
executor = ThreadPoolExecutor()
future1 = executor.submit(task, "椎名真白", 16, 5)
future2 = executor.submit(task, "古明地觉", 16, 2)
future3 = executor.submit(task, "古明地恋", 15, 4)
# 这里是不会阻塞的
print(123)
# 直到所有的future完成,这里的return_when有三个可选
# FIRST_COMPLETED,当任意一个任务完成或者取消
# FIRST_EXCEPTION,当任意一个任务出现异常,如果都没出现异常等同于ALL_COMPLETED
# ALL_COMPLETED,所有任务都完成,默认是这个值
# 会卡在这一步,直到所有的任务都完成
fs = wait([future1, future2, future3], return_when="ALL_COMPLETED")
# 此时返回的fs是DoneAndNotDoneFutures类型,里面有两个值,一个是done,一个是not_done
print(fs.done)
print(fs.not_done)
print(len(fs))
for f in fs.done:
print(f.result())
print(456)
"""
123
{<Future at 0x1df1400 state=finished returned str>, <Future at 0x2f08e48 state=finished returned str>, <Future at 0x9f7bf60 state=finished returned str>}
set()
2
name is 椎名真白, age is 16, sleep 5s
name is 古明地觉, age is 16, sleep 2s
name is 古明地恋, age is 15, sleep 4s
456
"""
使用上下文管理
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, age, n):
time.sleep(n)
return f"name is {name}, age is {age}, sleep {n}s"
with ThreadPoolExecutor() as executor:
future1 = executor.submit(task, "椎名真白", 16, 5)
future2 = executor.submit(task, "古明地觉", 16, 2)
future3 = executor.submit(task, "古明地恋", 15, 4)
print(future1.result())
print(future2.result())
print(future3.result())
# 直到with语句全部执行完毕,才会往下走
print(123)
"""
name is 椎名真白, age is 16, sleep 5s
name is 古明地觉, age is 16, sleep 2s
name is 古明地恋, age is 15, sleep 4s
123
"""
或者调用executor的shutdown
from concurrent.futures import ThreadPoolExecutor
import time
def task(n):
time.sleep(n)
print(f"sleep {n}")
executor = ThreadPoolExecutor()
future1 = executor.submit(task, 5)
future2 = executor.submit(task, 2)
future3 = executor.submit(task, 4)
print(123)
# 但此时也就无法拿到返回值了
executor.shutdown(wait=True)
print(456)
"""
123
sleep 2
sleep 4
sleep 5
456
"""