【发布时间】:2012-03-28 09:54:49
【问题描述】:
我目前正在 Linux 上用 C++ 编写一个网络爬虫/蜘蛛,我在更新数据库时遇到了一些问题。我是相当 C/C++ 新手,仅供参考。
数据库更新由单独的线程(使用 pthreads)执行,但如果在 main() 中执行也会存在同样的问题,所以我可能天真地丢弃了线程的东西作为任何事情的原因。
我正在为数据库 API 使用 libmysqlcppconn。
我正在使用带有 -O2 -Wall -pedantic 的 gcc 版本 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1) 进行编译,并且可以干净地编译。
尽管如此,当调用下面的函数 commitChangesToDatabase() 时,它基本上从 std::map (url_queue) 中挑选出项目,将它们放入 std::vector (更新) 并从原始 std 中删除所述项目: :map,然后继续迭代 std::vector,为向量中的每个项目执行 MySQL 准备语句。这就是它失败的地方。
它是随机的:
- 没有任何错误输出的崩溃(没有段错误,没有堆栈跟踪,没有任何内容)
- 检测到 glibc 内存损坏导致崩溃(请参阅此处的输出:http://pastie.org/private/wlkuorivq5tptlcr7ojg)
- 报告 MySQL 服务器已消失(捕获异常),但仍在尝试(未崩溃)
我尝试将准备好的语句切换为简单的 executeUpdate(),但无济于事。 我已经尝试消除挑选项目的步骤,而只是在找到要更新的项目时执行更新,在 url_queue 的第一个循环中。
此应用程序中的其他函数也使用准备好的语句(另一个 UPDATE)并且工作正常。这些函数也由单独的线程运行。
我会通过 valgrind 运行应用程序,但坦率地说,我不了解大部分输出,所以它对我没有多大帮助 - 但如果有人想要它的输出,请告诉我运行它的选项有,我会提供的。
我不知道如何从这里开始。任何人都知道出了什么问题?
struct queue_item_t {
int id;
int sites_id;
int priority;
int depth;
int handler;
int state; // 0 = Pending, 1 = Working, 2 = Completed, 3 = Checked
double time_allowed_crawl;
bool status;
bool was_redirected;
double time;
double time_end;
double time_curl;
double size;
std::string hash;
std::string url;
std::string file;
std::string host;
};
void commitChangesToDatabase()
{
map< string, queue_item_t >::iterator it, end;
sql::PreparedStatement *pstmt;
int i = 0;
if (!url_queue.size()) {
return;
}
pthread_mutex_lock(&dbCommitMutex);
pthread_mutex_lock(&itemMutex);
cout << "commitChangesToDatabase()" << endl;
pstmt = dbPrepareStatement("UPDATE crawler_queue SET process_hash = NULL, date_crawled = NOW(), url = ?, hash = ? WHERE id = ?");
for (it = url_queue.begin(); it != url_queue.end();)
{
if (it->second.state == 2)
{
pstmt->setString(1, it->second.url);
pstmt->setString(2, it->second.hash);
pstmt->setInt(3, it->second.id);
try {
pstmt->executeUpdate();
++i;
} catch (sql::SQLException &e) {
cerr << "# ERR: SQLException in " << __FILE__;
cerr << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;
cerr << "# ERR: " << e.what();
cerr << " (MySQL error code: " << e.getErrorCode();
cerr << ", SQLState: " << e.getSQLState() << " )" << endl;
}
url_queue.erase(it++);
}
else {
++it;
}
}
delete pstmt;
cout << "~commitChangesToDatabase()" << endl;
pthread_mutex_unlock(&itemMutex);
pthread_mutex_unlock(&dbCommitMutex);
}
// this function is defined in another file but is written here just to show the contents of it
sql::PreparedStatement *dbPrepareStatement(const std::string &query)
{
return con->prepareStatement(query);
}
编辑:
有些人似乎认为问题出在 url_queue 集合上的迭代上,但是我已经排除了这一点,但注释掉了在数据库上运行的所有内容,而不是迭代。此外,这里的迭代是原始版本的简化(但工作)版本,它从地图中挑选项目,抛出一个向量并从地图中删除,如下所示,并且该部分程序运行良好 - 它 only 在使用数据库时崩溃。
for (it = url_queue.begin(); it != url_queue.end();)
{
if (it->second.state == 2)
{
update_item.type = (!it->second.was_redirected ? 1 : 2);
update_item.item = it->second;
updates.push_back(update_item);
url_queue.erase(it++);
}
else {
++it;
}
}
编辑 2:
valgrind --leak-check=yes 的输出:http://pastie.org/private/2ypk0bmawwsqva3ikfazw
【问题讨论】:
-
您可以在您的问题中添加
queue_item_t的声明吗? -
另外,您不应该从您正在迭代的集合中删除项目。它可能会使迭代器无效。另外,您总是将迭代器增加两次,这是有意跳过每个第二项吗?
-
我在 StackOverflow 上找到了从您正在迭代的集合中擦除的方法,该方法建议在每次迭代时评估集合的 .begin() 和 .end()。抱歉,双倍增加是一个不影响问题的错误 - for(;;) 中的 ++it 不应该存在。