所以我正在研究一个小的Python工具来对应用程序的API进行压力测试。
我有一个使用Threading的很好的脚本,但是随后我读到它需要手动编码来维护n个并发线程(意味着,在旧线程完成后立即开始新线程),并在这里提出建议: How to start a new thread when old one finishes?是要使用ThreadPool,我尝试如下:
def test_post():
print "Executing in " + threading.currentThread().getName() + "\n"
time.sleep(randint(1, 3))
return randint(1, 5), "Message"
if args.send:
code, content = post()
print (code, "\n")
print (content)
elif args.test:
# Create new threads
print threads
results_list = []
pool = ThreadPool(processes=threads)
results = pool.apply_async(test_post())
pool.close() # Done adding tasks.
pool.join() # Wait for all tasks to complete.
# results = list(pool.imap_unordered(
# test_post(), ()
# ))
# thread_list = []
# while threading.activeCount() <= threads:
# thread = LoadTesting(threadID=free_threads, name="Thread-" + str(threading.activeCount()), counter=1)
# thread.start()
# thread_list.append(thread)
print "Exiting Main Thread" + "\n"
else:
print ("cant get here!")
调用脚本时,得到一致的输出,例如:
4
在MainThread中执行
退出主线程
我不确定为什么。正如您在注释栏中所看到的,我尝试了不同的方法,但它仍然只执行一次。
我的目标是使脚本循环运行,始终随时运行n个线程。
test_post
(和分别为post
)函数返回HTTP响应代码,以及内容-我想稍后在响应代码不是200 OK
时使用它来打印/停止。 / p>
答案 0 :(得分:1)
您的第一个问题是您已经在MainThread
中调用了以下函数:
pool.apply_async(test_post())
......而不是传递test_post
作为要在工作线程中执行以下调用的参数:
pool.apply_async(test_post)
OP:我有一个使用Threading的漂亮脚本,但是我读到它需要手动编码来维护n个并发线程(这意味着,一旦旧线程完成,就开始新线程)... < / p>
您需要区分工作单元(作业,任务)和线程。首先使用池的全部目的是重新使用执行程序,无论是线程还是进程。实例化Pool时已经创建了工作程序,只要您不关闭Pool,所有初始线程都将保持活动状态。因此,您不必在意重新创建线程,只需在需要分配某些工作时就调用现有池的池方法。池将执行此作业(池方法调用)并从中创建任务。这些任务放在无限制的队列中。每当工人完成一项任务时,它将阻塞性地尝试get()
来inqueue
来完成一项新任务。
OP:Pool只执行一个线程而不是4个线程...我尝试了不同的方法,但仍然只执行一次。
pool.apply_async(func, args=(), kwds={}, callback=None, error_callback=None)
...是一个单次调用,单个任务生成作业。如果要执行多个func
,则必须多次调用pool.apply_async()
,或者使用类似
pool.map(func, iterable, chunksize=None)
...,它将一个函数映射到一个可迭代的对象上。 pool.apply_async
是非阻塞的,这就是为什么它是“异步”的。它立即返回一个AsyncResult
对象,您可以(阻塞地)调用.wait()
或.get()
。
通过注释变得很明显,您希望无尽和立即替换完成的任务(自行生成的输入流)...并且程序应停止在KeyboardInterrupt或结果没有特定值时。
您可以使用callback
的{{1}}参数在所有旧任务完成后立即安排新任务。困难在于如何同时使用MainThread来防止整个脚本过早结束,同时保持其对KeyboardInterrupt的响应。让MainThread处于循环睡眠状态,使其仍然可以立即对KeyboardInterrupt做出反应,同时防止提前退出。如果结果应停止该程序,则可以让回调终止该池。然后MainThread只需在其睡眠循环中包括对池状态的检查即可。
apply_async
带有KeyboardInterrupt的示例输出:
import time
from random import randint, choice
from itertools import count
from datetime import datetime
from threading import current_thread
from multiprocessing.pool import ThreadPool
def test_post(post_id):
time.sleep(randint(1, 3))
status_code = choice([200] * 9 + [404])
return "{} {} Message no.{}: {}".format(
datetime.now(), current_thread().name, post_id, status_code
), status_code
def handle_result(result):
msg, code = result
print(msg)
if code != 200:
print("terminating")
pool.terminate()
else:
pool.apply_async(
test_post, args=(next(post_cnt),), callback=handle_result
)
if __name__ == '__main__':
N_WORKERS = 4
post_cnt = count()
pool = ThreadPool(N_WORKERS)
# initial distribution
for _ in range(N_WORKERS):
pool.apply_async(
test_post, args=(next(post_cnt),), callback=handle_result
)
try:
while pool._state == 0: # check if pool is still alive
time.sleep(1)
except KeyboardInterrupt:
print(" got interrupt")
示例输出,由于不想要的返回值而终止:
$> python2 scratch.py
2019-02-15 18:46:11.724203 Thread-4 Message no.3: 200
2019-02-15 18:46:12.724713 Thread-2 Message no.1: 200
2019-02-15 18:46:13.726107 Thread-1 Message no.0: 200
2019-02-15 18:46:13.726292 Thread-3 Message no.2: 200
2019-02-15 18:46:14.724537 Thread-4 Message no.4: 200
2019-02-15 18:46:14.726881 Thread-2 Message no.5: 200
2019-02-15 18:46:14.727071 Thread-1 Message no.6: 200
^C got interrupt
请注意,在您的情况下,您的{@ {1}}调用次数也比$> python2 scratch.py
2019-02-15 18:44:19.966387 Thread-3 Message no.0: 200
2019-02-15 18:44:19.966491 Thread-4 Message no.1: 200
2019-02-15 18:44:19.966582 Thread-1 Message no.3: 200
2019-02-15 18:44:20.967555 Thread-2 Message no.2: 200
2019-02-15 18:44:20.968562 Thread-3 Message no.4: 404
terminating
次要多,以使初始分配具有一定的缓冲区来减少延迟。