我正在构建一个使用Multithreading.Pool一次调用多个项目的脚本,我的结果很奇怪。
作为首发,这是我的(简化)代码,但经过测试和工作(或失败:p):
#!/usr/bin/env python
import MySQLdb
import time
import multiprocessing
import sys
import logging
TIMEOUT = 2500 # ms, 2,5 seconds
MAX_TIMEOUT = 120000 # ms, 2 minutes
DB_PARAMS = {
'user': 'root',
'passwd': '******',
'db': 'my_db'
}
class CheckState:
def __call__(self, row):
print "Calling for %s" % (row[0], )
# Retrieving n-1 instance
entry_id = row[0]
print entry_id
last_log = database.find_unique("SELECT status FROM entry_logs WHERE entry_id = %s ORDER BY occured DESC LIMIT 1;", (entry_id, ))
print entry_id, last_log
class DatabaseBridge:
def __init__(self, *args, **kwargs):
self.cnx = MySQLdb.connect (**kwargs)
self.cnx.autocommit(True)
self.cursor = self.cnx.cursor()
def query_all(self, query, *args):
self.cursor.execute(query, *args)
return self.cursor.fetchall()
def find_unique(self, query, *args):
rows = self.query_all(query, *args);
if len(rows) == 1:
return rows[0]
return None
def execute(self, query, params):
self.cursor.execute(query, params)
return self.cursor.rowcount
def close(self):
self.cursor.close()
self.cnx.close()
database = DatabaseBridge(**DB_PARAMS)
def main():
start_time = time.time()
pool = multiprocessing.Pool(multiprocessing.cpu_count())
try:
logging.info("===================================")
rows = database.query_all("SELECT id FROM entries WHERE is_disabled = 0 AND removed IS NULL AND IFNULL(address, '') != '';")
if len(rows) > 0:
pool_timeout = len(rows) * TIMEOUT
if pool_timeout > MAX_TIMEOUT:
pool_timeout = MAX_TIMEOUT
result = pool.map_async(CheckState(), rows)
pool.close()
pool.join()
logging.info("Running for %d seconds max", float(pool_timeout)/1000)
result.get(timeout=float(pool_timeout)/1000) # Maximum Timeout allowed for security reasons !
end_time = time.time() - start_time
logging.info("Took approx %.2f seconds to run.", end_time)
database.close()
pool.terminate()
return 0
except Exception, err:
end_time = time.time() - start_time
print "An error occured with the script"
print "Took approx %.2f seconds to run." % (end_time, )
logging.error("Took approx %.2f seconds to run.", end_time)
logging.exception("Script failed")
pool.terminate()
database.close()
return 1
if __name__ == "__main__":
sys.exit(main())
在我的数据库中,表条目中有10行,在表entry_logs中,我有n *个条目行。
entry_logs中的状态可以是OK,DOWN或INVALID。
在我的数据库中,只有entry_id 5和10有DOWN或INVALID,所有其他8个总是有状态OK
现在,当我运行我的脚本时,我得到了这个:
Calling for 1
1
Calling for 2
2
Calling for 3
3
Calling for 4
4
1 ('OK',)
Calling for 6
6
Calling for 7
7
Calling for 5
5
6 ('OK',)
Calling for 8
8
8 ('OK',)
Calling for 9
9
9 ('OK',)
Calling for 10
10
10 ('OK',)
5 ('OK',)
4 ('INVALID',)
3 ('OK',)
7 ('OK',)
这里的关键是:
5 ('OK',)
10 ('OK',)
4 ('INVALID',)
这是不可能的, 5和10应该返回INVALID,4应该返回OK
所以我猜我的线程正在弄乱DatabaseBridge
类,并且返回的数据在线程之间交叉而不是在正确的线程中返回,但是,我该如何解决?
奖励点:有时(不是每次),我的脚本都会挂起,永远不会返回。尽管我在timeout
方法上强制get()
,但我必须杀死主要的进程和一个孩子(可能是被吊死的那个)。你知道为什么吗?
答案 0 :(得分:0)
好的,经过长时间的大量搜索,我认为我找到了原因,但解决方案(或替代方案)必须实施。
事实上,这个问题得到了很好的解释here:
MySQLdb用户指南,该模块支持1级。(“线程可以共享模块,但不能连接。”)。
简而言之:我不应该为查询使用相同的连接,因为它不能保证为正确的线程返回结果。而这正是我所经历的。
现在我有两个选择:
1 - 使用Mysql Pool 2 - 将代码重写为Pool,只需要花费很长时间来计算,而不是整个工作。
我想我会选择第二个,更简单;)