负载测试脚本使用线程还是多进程

1 投票
1 回答
2871 浏览
提问于 2025-04-18 15:45

我写了一些代码来测试数据库的性能,目的是看看当多个用户同时查询数据库时,所花的时间是如何增加的。代码里有一个叫做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时,下一个查询之间会随机引入一些延迟。这种延迟可能会超过一分钟(见下图)。enter image description here

我使用的环境是:python3.4.1,pyodbc3.0.7;客户端运行在Windows 7/RHEL 6.5上。

我真的希望能让这个线程的方法正常工作。请问这种情况在使用线程时是正常的吗?还是我漏掉了什么命令?或者该如何重写呢?谢谢。

1 个回答

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进程是非常消耗资源的。

撰写回答