对于线程资源共用问题,实际上也没有一个最完美的解决方案,没有最适合的,只有根据情况来分析,找合适的方法,我最近在做角色之间的位置信息交换,已经实现了一部分了。感觉很爽
其中角色的位置在变,还要同时通知给所有其他的玩家,这其实应该是件头痛的问题
我前面的文章已经说明,我吧地形划分为一个个的tile,那么角色的信息只发送给身边9个tile里面其他的玩家,刚如果换了,tile,那么还要通知最后离开的三个tile里面的玩家,该角色已经不在可视范围之内了。这样的信息。那么就存tile里面同时要增加也要删除也要读取并且(发送)的处理
这些信息是具有高并发量的。如果处理的不好,角色是会很卡的。
我想了很久终于用一个巧妙的方法解决了这个难题,应该会比较高效的。
我定义了个这样的结构:
//某个线程的临时资源,在锁定一个资源的时候,可以根据需要,需要处理的数据拷贝到这个线程本地资源中去
//然后解锁再去处理这些数据,所占用的时间仅仅迭代和copy的时间,处理过程可以根据需要来决定是否应该同步(如果不同步,就不用占用临界锁了)
//可以从一定程度上,减少线程对资源的占有时间,减少线程碰撞机会,充分提高系统性能
//这种处理手法来源于java里面的ThreadLocal,里面的集合就是用来做为线程本地资源的。
//进程里面开辟的线程数量不会太多,本地资源应该有很多分类
typedef struct _LocalThreadResource
{
DWORD ThreadId; //当前线程的id
//copy-process处理,这个特点都是从头往尾顺序加数据,不存在插入数据的情况,访问也是从头到尾,用vector最合适了
//并且处理完了也不用清理(清理也要花时间),下次使用,复制的时候再从头到尾以iterator的方式copy
vector<LPSESSION> mNotifingUsers;//本次线程需要通知给周围的角色id
DWORD mNotifingUsersLen; //保留上面集合的有效数据的集合的长度
//根据需要添加更多的资源
} LocalThreadResource;
这个结构通过ThreadIdThreadId 关联到线程中去,当前的线程ID可以用GetThreadID来获取,
对于读取数据和处理的过程大致如下:
void TileInfo::Insert(LPSESSION playerid)
{
mlock.lock();
players.insert(playerid);
mlock.unlock();
}
void TileInfo::Remove(LPSESSION playerid)
{
mlock.lock();
players.erase(playerid);
mlock.unlock();
}
void TileInfo::SendMsgToPlayersInTile(BasePack *pack,int len, char *from)
{
//得到当前的线程id
DWORD currentThreadId = GetCurrentThreadId();
//根据线程id得到当前线程的本地资源
LocalThreadResource *localresource = gServer->LocalThreadResourceMap[currentThreadId];
localresource->mNotifingUsers.resize(10);
//加锁
mlock.lock();
//把需要处理的数据复制到线程本地资源,也许你对copy耿耿于怀,后来我思考了一下,其实不存在重新创建,而且数据量不大,简单的复制,速度是很快的,最大的好处,就是锁不用锁那么久,这点损失无妨。
copy(players.begin(), players.end(), localresource->mNotifingUsers.begin());
//记录需要处理的数据的长度
localresource->mNotifingUsersLen = players.size();
//解锁
mlock.unlock();
int i = 0;
std::vector<LPSESSION>::iterator itor = localresource->mNotifingUsers.begin();
//处理需要处理的线程本地数据
for(; i < localresource->mNotifingUsersLen && itor!= localresource->mNotifingUsers.end(); itor++, i ++)
{
LPSESSION lpSession = (LPSESSION) *itor;
gServer->SendInUdp(lpSession, (char *)pack, len, from);
}
}
//把读取和处理过程完全分开,读数据和处理异步(减少锁占用的时间),读和写通过临界区来互斥。
我最终采纳了这个方案