负载测试脚本使用线程还是多进程
我写了一些代码来测试数据库的性能,目的是看看当多个用户同时查询数据库时,所花的时间是如何增加的。代码里有一个叫做User的类(下面有展示),它的对象是通过解析XML文件创建的。
class User(object):
def __init__(self, id, constr):
self.id = id
self.constr = constr
self.queryid = list()
self.queries = list()
def openConn(self):
self.cnxn = pyodbc.connect(self.constr)
logDet.info("%s %s"%(self.id,"Open connection."))
def closeConn(self):
self.cnxn.close()
logDet.info("%s %s"%(self.id,"Close connection."))
def executeAll(self):
self.openConn()
for n,qry in enumerate(self.queries):
try:
cursor = self.cnxn.cursor()
logTim.info("%s|%s|beg"%(self.id, self.queryid[n]))
cursor.execute(qry)
logTim.info("%s|%s|end"%(self.id, self.queryid[n]))
except Exception:
cursor.rollback()
logDet.exception("Error while running query.")
self.closeConn()
我使用pyODBC来连接数据库。程序会生成两个日志,一个是详细日志(logDet),另一个只有时间记录(logTim)。所有的User对象会被存储在一个列表里。每个用户的查询也会放在一个列表中(而不是放在一个线程安全的队列里)。
为了模拟多个用户同时操作,我尝试了几种不同的方法:
def worker(usr):
usr.executeAll()
选项1:使用multiprocessing.Pool
pool = Pool(processes=len(users))
pool.map(worker, users)
选项2:使用threading.Thread
for usr in users:
t = Thread(target=worker, args=(usr,))
t.start()
这两种方法都能工作。在我的测试中,我尝试了用户数量为2、6、……、60,每个用户有4个查询。根据查询时间的记录,每个查询结束到下一个查询开始之间的延迟应该少于一秒,也就是说查询应该是一个接一个地进行。这在使用multiprocessing时确实是这样的,但在使用threading时,下一个查询之间会随机引入一些延迟。这种延迟可能会超过一分钟(见下图)。
我使用的环境是:python3.4.1,pyodbc3.0.7;客户端运行在Windows 7/RHEL 6.5上。
我真的希望能让这个线程的方法正常工作。请问这种情况在使用线程时是正常的吗?还是我漏掉了什么命令?或者该如何重写呢?谢谢。
1 个回答
当你使用基于threading
的方法时,你会为每个用户启动一个线程,最多可以有60个线程。这些线程在进行输入输出操作时,必须争抢一个叫做GIL的东西。这会带来很多额外的开销。如果你使用一个ThreadPool
,限制线程数量在一个较小的范围内(比如2 * multiprocessing.cpu_count()
),即使用户数量更多,你可能会看到更好的效果:
from multiprocessing.pool import ThreadPool
from multiprocessing import cpu_count
pool = ThreadPool(processes=cpu_count()*2)
pool.map(worker, users)
你可能还想限制同时运行的进程数量,因为这样可以节省内存。启动60个同时运行的Python进程是非常消耗资源的。