最近因为自己主持的项目出现些问题,太忙了,所以好久都没有继续写东西和大家进行探讨制作开发部分了。在这一节中就要向大家介绍另外一个重要的部分,并且也是最头疼的部分:线程同步和数据保护。 关于线程的概念我在前面的章节中已经介绍过了,也就在这里不累赘—“重复再重复”了。有一定线程基础的人都知道,线程只要创建后就如同脱缰的野马,对于这样的一匹野马我们怎么来进行控制和处理呢?简单的说,我们没有办法进行控制。因为我们更本就没有办法知道CPU什么时候来执行他们,执行他们的次序又是什么? 有人要问没有办法控制那我们如何是好呢?这个问题也正是我这里要向大家进行解释和说明的,虽然我们不能够控制他们的运行,但我们可以做一些手脚来达到我们自己的意志。 这里我们的做手脚也就是对线程进行同步,关于同步的概念大家在《操作系统》中应该都看过吧!不了解的话,我简单说说:读和写的关系(我读书的时候,请你不要在书上乱写,否则我就没有办法继续阅读了。) 处理有两种:用户方式和内核方式。 用户方式的线程同步由于有好几种:原子访问,关键代码段等。 在这里主要向大家介绍关键代码段的处理(我个人用的比较多,简单实用)。先介绍一下它的一些函数,随后提供关键代码段的处理类供大家参考(比较小,我就直接贴上来了) VOID InitializeCriticalSection( //初始化互斥体 LPCRITICAL_SECTION lpCriticalSection // critical section ); VOID DeleteCriticalSection( //清除互斥体 LPCRITICAL_SECTION lpCriticalSection // critical section ); VOID EnterCriticalSection( //进入等待 LPCRITICAL_SECTION lpCriticalSection // critical section ); VOID LeaveCriticalSection( //释放离开 LPCRITICAL_SECTION lpCriticalSection // critical section ); 以上就是关于关键代码段的基本API了。介绍就不必了(MSDN)。而我的处理类只是将这几个函数进行了组织,也就是让大家能够更加理解关键代码端 .h class CCriticalSection //共享变量区类 { public: CCriticalSection(); virtual ~CCriticalSection(); void Enter(); //进入互斥体 void Leave(); //离开互斥体释放资源 private: CRITICAL_SECTION g_CritSect; }; .cpp CCriticalSection::CCriticalSection() { InitializeCriticalSection(&g_CritSect); } CCriticalSection::~CCriticalSection() { DeleteCriticalSection(&g_CritSect); } void CCriticalSection::Enter() { EnterCriticalSection(&g_CritSect); } void CCriticalSection::Leave() { LeaveCriticalSection(&g_CritSect); } 由于篇幅有限关键代码段就说到这里,接下来向大家简单介绍一下内核方式下的同步处理。 哎呀!这下可就惨了,这可是要说好多的哦!书上的罗罗嗦嗦我就不说了,我就说一些我平时的运用吧。首先内核对象和一般的我们使用的对象是不一样的,这样的一些对象我们可以简单理解为特殊对象。而我们内核方式的同步就是利用这样的一些特殊对象进行处理我们的同步,其中包括:事件对象,互斥对象,信号量等。对于这些内核对象我只向大家说明两点: 1.内核对象的创建和销毁 2.内核对象的等待处理和等待副作用 第一:内核对象的创建方式基本上而言都没有什么太大的差别,例如:创建事件就用HANDLE CreateEvent(…..),创建互斥对象 HANDLE CreateMutex(…….)。而大家注意的也是这三个内核对象在创建的过程中是有一定的差异的。对于事件对象我们必须明确指明对象是人工对象还是自动对象,而这种对象的等待处理方式是完全不同的。什么不同下面说(呵呵)。互斥对象比较简单没什么说的,信号量我们创建必须注意我们要定义的最大使用数量和初始化量。最大数量>初始化量。再有如果我们为我们的内核对象起名字,我们就可以在整个进程中共用,也可以被其他进程使用,只需要OPEN就可以了。也就不多说了。 第二:内核对象的等待一般情况下我们使用两个API: DWORD WaitForSingleObject( //单个内核对象的等待 HANDLE hHandle, // handle to object DWORD dwMilliseconds // time-out interval ); DWORD WaitForMultipleObjects( //多个内核对象的等待 DWORD nCount, // number of handles in array CONST HANDLE *lpHandles, // object-handle array BOOL fWaitAll, // wait option DWORD dwMilliseconds // time-out interval ); 具体怎么用查MSDN了。 具体我们来说等待副作用,主要说事件对象。首先事件对象是分两种的:人工的,自动的。人工的等待是没有什么副作用的(也就是说等待成功后,要和其他的对象一样要进行手动释放)。而自动的就不一样,但激发事件后,返回后自动设置为未激发状态。这样造成的等待结果也不一样,如果有多个线程在进行等待事件的话,如果是人工事件,被激活后所有等待线程成执行状态,而自动事件只能有其中一个线程可以返回继续执行。所以说在使用这些内核对象的时候,要充分分析我们的使用目的,再来设定我们创建时候的初始化。简单的同步我就说到这里了。下面我就将将我们一般情况下处理游戏服务器处理过程中的数据保护问题分析: 首先向大家说说服务器方面的数据保护的重要性,图例如下: 用户列表 用户删除 用户数据修改 使用数据 加入队列 对于上面的图例大家应该也能够看出在我们的游戏服务器之中,我们要对于我们用户的操作是多么的频繁。如此频繁的操作我们如果不进行处理的话,后果将是悲惨和可怕的,举例:如果我们在一个线程删除用户的一瞬间,有线程在使用,那么我们的错误将是不可难以预料的。我们将用到了错误的数据,可能会导致服务器崩溃。再者我们多个线程在修改用户数据我们用户数据将是没有办法保持正确性的。等等情况都可能发生。怎么样杜绝这样的一些情况的发生呢?我们就必须要进行服务器数据的保护。而我们如何正确的保护好数据,才能够保持服务器的稳定运行呢?下面说一下一些实际处理中的一些经验之谈。 1.我们必须充分的判断和估计我们服务器中有那些数据要进行数据保护,这些就需要设计者和规划者要根据自己的经验进行合理的分析。例如:在线用户信息列表,在线用户数据信息,消息列表等。。。。。 2.正确和十分小心的保护数据和正确的分析要保护的数据。大家知道我们要在很多地方实现我们的保护措施,也就是说我们必须非常小心谨慎的来书写我们的保护,不正确的保护会造成系统死锁,服务器将无法进行下去(我在处理的过程中就曾经遇到过,头都大了)。正确的分析要保护的数据,也就是说,我们必须要估计到我们要保护的部分的处理能够比较快的结束。否则我们必须要想办法解决这个问题:例如: DATA_STRUCT g_data; CRITICAL_SECTION g_cs; EnterCriticalSection(&g_cs); SendMessage(hWnd,WM_ONEMSG,&g_data,0); LeaveCriticalSection(&g_cs); 以上处理就有问题了,因为我们不知道SendMessage()什么时候完成,可能是1/1000豪秒,也可能是1000年,那我们其他的线程也就不用活了。所以我们必须改正这种情况。 DATA_STRUCT g_data; CRITICAL_SECTION g_cs; EnterCriticalSection(&g_cs); PostMessage(hWnd,WM_ONEMSG,&g_data,0); LeaveCriticalSection(&g_cs); 或者 DATA_STRUCT temp_data; EnterCriticalSection(&g_cs); temp_data = g_cs; LeaveCriticalSection(&g_cs); SendMessage(hWnd,WM_ONEMSG,& temp_data,0); 3.最好不要复合保护用户数据,这样可能会出现一些潜在的死锁。 简而言之,服务器的用户数据是一定需要进行保护,但我们在保护的过程中就一定需要万分的小心和谨慎。这篇我就说到这里了,具体的还是需要从实践中来进行学习,下节想和大家讲讲服务器的场景处理部分。先做事去了。呵呵!!有好的想法和建议的和我交流探讨,先谢谢了。 |
联系客服