【发布时间】:2012-01-21 09:45:39
【问题描述】:
注意(更新):无需阅读整面墙的文字。问题+解决方案在下面进一步说明。
我想一劳永逸地找出为什么我在创建多线程应用程序时总是遇到这些错误。为了简单起见,我剪掉了很多代码,所以如果你尝试编译它,它将无法工作。
这是一个排队系统。实体将被分成 5 个一组。 例如,如果一个实体离开第 3 组,第 3 组将从另一个优先级较低且尚未满的组中抢夺一个实体。
我现在所做的就是创建实体并将它们排队,这发生在主线程上。之后,我启动了一个将删除 20 个实体的提升线程。这是发生读取访问冲突的地方,我无法弄清楚是什么原因造成的。你如何调试这样的东西?
main.h
//Externs, so all files can access the objects. Created in main.cpp .
#include "queue.h"
typedef std::map<unsigned int, Entity*> entityMap; //id,entity
extern entityMap entitymap;
typedef std::map<unsigned int, unsigned int> entityPositionMap; //Position,entityID
extern entityPositionMap entitypositionmap;
extern LFDGroups lfdgroups;
extern Queue queue;
main.cpp
typedef std::map<unsigned int, Entity*> entityMap;
entityMap entitymap;
typedef std::map<unsigned int, unsigned int> entityPositionMap; //Position,id
entityPositionMap entitypositionmap;
LFDGroups lfdgroups;
Queue queue;
struct threadStarter2
{void operator()(){
for(unsigned int i = 0;i<20;i++) queue.removeFakeEntity();
}};
int main()
{
threadStarter1 startit1;
boost::thread thrd1(startit1);
whatNext(); //Prevent program exit
return 0;
}
queue.cpp
#include "main.h"
#include "queue.h"
Queue::Queue() //Lets add some entities and queue them at application start
{
for(unsigned int i = 0;i<20;i++){ addFakeEntity(false); }
//Queue all entities
std::map<unsigned int, Entity*>::iterator p;
for(p = entitymap.begin(); p != entitymap.end(); p++)
{
boost::mutex::scoped_lock lock(lfdgroups.access_groupsLookingForMore);
lfdgroups.addMember(p->second->id);
lock.unlock();
}
}
void Queue::addFakeEntity(bool queuenow)
{
Entity *entity = new Entity;
entity->id = entitymap.size();
entity->LFDGroupID = NULL;
entity->LFD = false;
entitymap.insert(std::pair<unsigned int,Entity*>(entity->id,entity));
if(queuenow)
{
entity->LFD = true;
boost::mutex::scoped_lock lock(lfdgroups.access_groupsLookingForMore);
lfdgroups.addMember(entity->id);
lock.unlock();
}
}
void Queue::removeFakeEntity()
{
//Remove random entity from random group
unsigned int groupID = getrand(0,lfdgroups.groupHeap.size()-1);
boost::mutex::scoped_lock lock(lfdgroups.access_groupsLookingForMore); //Thread safety
lfdgroups.groupHeap[groupID]->removeMember(lfdgroups.groupHeap[groupID]->arrayOfEntityIDs[getrand(0,lfdgroups.groupHeap[groupID]->arrayOfEntityIDs.size()-1)],true);
lock.unlock();
}
如你所见,主线程此时没有做任何事情,而 boost 线程正在一个一个地删除实体。
这是删除实体时实际发生的情况 (lfdgroups.groupHeap[groupID]->removeMember())
bool groupObject::removeMember(unsigned int entityID,bool snatchIt)
{
boost::mutex::scoped_lock lock(access_group);
for (unsigned int i = 0;i<arrayOfEntityIDs.size();i++)
{
if(arrayOfEntityIDs[i] == entityID) //Messy way of selecting the entity we are looking for.
{
std::stringstream ss;
ss.str(""); ss.clear(); ss << "Removed member " << entityID << " from group " << id << " which has " << groupSize() - 1 << " members left." << std::endl;
echo(ss.str());
arrayOfEntityIDs.erase(arrayOfEntityIDs.begin() + i);
if(snatchIt){ std::cout << "We have to snatch" << std::endl; snatch();}
else{ std::cout << "We don't have to snatch" << std::endl;}
return true;
}
}
lock.unlock();
return true;
}
snatchit 设置为 true,所以;
void groupObject::snatch()
{
boost::mutex::scoped_lock lock(snatching);
groupObject* thisgroup = NULL;
thisgroup = this;
if(!thisgroup) return;
std::cout << thisgroup->id << " need snatch? " << thisgroup->snatchCriteria() << std::endl;
if(!thisgroup->snatchCriteria()) return; //Do we even need to snatch a player (group < 5?) //This is a little redundant atm
std::map<unsigned int, unsigned int>::iterator p2;
for(p2 = entitypositionmap.begin(); p2 != entitypositionmap.end(); p2++)
{
Entity* entity = thisgroup->getEntity(p2->second);
if(entity != NULL && entity->LFDGroupID != thisgroup->id)
{
groupObject* targetgroup = NULL;
targetgroup = lfdgroups.getGroupObject(entity->LFDGroupID);
if(targetgroup != NULL && targetgroup->migrateCriteria())
{
lfdgroups.getGroupObject(thisgroup->id)->addMember(entity->id,false);
std::stringstream ss;
ss.str(""); ss.clear(); ss << "Snatched " << entity->id << " from " << targetgroup->id << " for: " << thisgroup->id << std::endl;
echo(ss.str());
break;
}
}
}
lock.unlock();
}
刚刚发生的事情是,该小组检查了队列中的所有实体,直到找到一个可以抢夺的实体。之后lfdgroups.getGroupObject(thisgroup->id)->addMember(entity->id,false); 发生(所以在组对象中添加成员,不要与lfdgroups 的添加成员混淆)。
bool groupObject::addMember(unsigned int entityID,bool snatchIt)
{
Entity* entity = getEntity(entityID);
if(entity == NULL) return false;
groupObject* group = lfdgroups.getGroupObject(entity->LFDGroupID);
if(group != NULL){ if(!group->removeMember(entityID,snatchIt)){return false;} }
if(elegibleCheck(entityID))
{
arrayOfEntityIDs.push_back(entityID);
entity->LFDGroupID = id;
std::stringstream ss;
ss.str(""); ss.clear(); ss << "Added member " << entityID << " to group " << id << " which has " << groupSize() << " members." << std::endl;
echo(ss.str());
return true;
}
return false;
}
所以我们刚刚从它的组中删除了实体,并将 snatchIt 设置为 false 以防止循环。之后,我们简单地将 entity->LFDGroupID 更新为当前组的 id,这就是它的结束。
抱歉,代码负载过多。并非所有代码都是相关的,但它允许您遵循删除实体函数所采用的路径。我的主要问题是;如何快速调试访问冲突?
当应用程序崩溃时,断点会出现在一个名为“vector”的文件中
size_type size() const
{ // return length of sequence
return (this->_Mylast - this->_Myfirst); //Breakpoint here.
}
更新(解决方案):
我首先注释掉 Queue::removeFakeEntity() 函数用来找出问题所在的各个步骤。我立即发现问题出在lfdgroups.groupHeap[groupID]->removeMember(lfdgroups.groupHeap[groupID]->arrayOfEntityIDs[getrand(0,lfdgroups.groupHeap[groupID]->arrayOfEntityIDs.size()-1)],true);
所以我把它切成小块
unsigned int entityID = lfdgroups.groupHeap[groupID]->arrayOfEntityIDs[getrand(0,lfdgroups.groupHeap[groupID]->arrayOfEntityIDs.size()-1)];
lfdgroups.groupHeap[groupID]->removeMember(entityID,true);
错误仍然发生。断点发生在向量大小返回时,所以我将其隔离;
void Queue::removeFakeEntity()
{
std::cout << "size: " << lfdgroups.groupHeap[groupID]->arrayOfEntityIDs.size() << std::endl;
}
错误仍然发生了。所以我尝试手动输入 groupID。
void Queue::removeFakeEntity()
{
std::cout << "size: " << lfdgroups.groupHeap[1]->arrayOfEntityIDs.size() << std::endl;
std::cout << "size: " << lfdgroups.groupHeap[2]->arrayOfEntityIDs.size() << std::endl;
std::cout << "size: " << lfdgroups.groupHeap[4]->arrayOfEntityIDs.size() << std::endl;
}
工作得很好!一定是groupID有问题。
void Queue::removeFakeEntity()
{
std::cout << "Heap size: " << lfdgroups.groupHeap.size() << std::endl;
}
这很奇怪,它显示的是“4”。但是等一下!计算第一个元素 (0) 应该是 5,对吧?
错了,我从 1 开始计算 groupID,因为我想保留 0 作为返回值。我应该选择NULL。昨天做决定的时候我什至考虑过。 “我会忘记我做过那个!”。为什么我从来不听自己的?
所以问题在于unsigned int groupID = getrand(0,lfdgroups.groupHeap.size()-1);,具有讽刺意味的是,这是我遇到问题的函数的第一行..
【问题讨论】:
-
如果在 size() 中遇到访问冲突,则在指向向量的空指针上调用 size()。
-
I'm cutting a lot of code out to keep it simple so it won't work if you try to compile it.在调查的早期阶段,您应该形成一个 testcase,其中涉及剪切代码.. 但不能只是粗略地删除代码块:逻辑上从程序中删除它们。代码应该在整个过程中保持可编译,直到你最终得到一个可能只有一点原始程序功能的小程序,但问题很明显。那时,您很可能已经发现了问题的原因。这称为调试。 -
@LightnessRacesinOrbit 这就是我通常做的事情。但我认为我是唯一这样做的 n00b 并且 必须 有一个更简单的方法。这真的是专业的方法吗?这非常耗时。
-
@natli:当然!编程很费时间。但是,随着您在寻找特定类型错误的最佳位置方面获得经验,这种技术确实会通过练习变得更快。
-
@LightnessRacesinOrbit 好的,我以老式的方式完成并修复了它。这甚至不是多线程问题,我想我可以将其归因于累!我只希望有更快的方法。这个非常简单,但随着应用程序变得越来越复杂,它可能需要几天的时间!
标签: c++