问题描述:(网络编程 WINSOCKET TCP)用户A登录系统,建立线程A,登录成功后,系统建立用户A的客户端列表,并改变用户A的连接状态为已连接。此时连接线程保持。之后,用户A在异地登再次录系统,建立线程B,系统检测用户A先前有连接,就关闭它,并等待先前的线程退出。在先前线程就是线程A退出之后,重新建立用户A的客户端列表。线程A退出时,需要释放资源,删除客户端列表信息,改变连接状态等。问题发生在这个时候:用户再次登录系统时线程B通过互斥锁访问客户端列表,当发现存在此用户连接时,等待线程A退出,线程A退出后,重建客户列表,很明显在互斥未释放时,等待线程A退出,要发生死锁。请问,有什么办法解决这样的问题吗?
线程B:
进入互斥区间
waitforsingleobject()等待线程A退出
离开互斥区间线程A:
进入互斥区间
释放资源
离开互斥区间这样,当线程B进入互斥区间后,等待线程A退出时,线程A无法进入互斥区间,所以也无法退出,导致线程B和线程A死锁。
问题代码:
线程B:
进入互斥区间
waitforsingleobject()等待线程A退出
离开互斥区间线程A:
进入互斥区间
释放资源
离开互斥区间
线程A肯定进入不了互斥区间。
因为A不退出,B无法重建客户端列表。在A没退出的情况下,B重新建立客户端列表,那么当线程A退出的时候,可能会把新建立的客户端列表给删除掉。
就是1个用户,系统只能保存一份有关此用户的客户端列表。用户第一次登录时,系统建立一个次用户ID唯一的客户端列表,等线程(假如是线程A)退出时,删除此客户端列表。
等用户再次登录时,系统需要检查此用户之前是否存在连接,如果存在,强制之前的连接(线程A)下线,再线程A下线之后,重建用户列表。
为啥要加锁呢
照你这么说那A首次连接的时候列表哪来的?
如果A可以正常实现连接那再次有连接申请的时候完成可以先处理掉A再从头处理B啊
{
SOCKET s; //客户端SOCKET句柄
HANDLE hRecvThread; //客户端线程句柄
}THREAD_INFO;THREAD_INFO* pThreadInfo = new THREAD_INFO;
pThreadInfo->s = s; //监听得到的客户端SOCKET句柄
pThreadInfo->hRecvThread = hRecvThread; //为客户端创建的数据接收线程句柄//接收数据时,连接登录命令处理
case 'L': //连接握手命令
{
//如果之前存在此设备的连接线程,就关闭它
//资源清理工作
if (m_Map_ThreadListLock.AcquireWriteLock())
{
map<string,THREAD_INFO*>::iterator pItor;
pItor = m_Map_ThreadList.find(sDeviceNO);
if (pItor != m_Map_ThreadList.end())
{
closesocket(pItor->second->s);
//等待线程退出
if (WAIT_OBJECT_0 == WaitForSingleObject(pItor->second->hRecvThread,INFINITE))
{
CloseHandle(pItor->second->hRecvThread);
pItor->second->hRecvThread = NULL;
delete pItor->second;
pItor->second = NULL;
}
}
m_Map_ThreadList[sDeviceNO] = pThreadInfo;
m_Map_ThreadListLock.ReleaseWriteLock();
} //.....建立客户端列表
................. //完成连接
}
break;
线程B:
进入互斥区间
检查并设置本地标志
离开互斥区间
通知A退出。
如果本地标志:waitforsingleobject()等待线程A退出
分配资源
线程A:
进入互斥区间
释放资源
离开互斥区间
UINT Recvthread(PVOID pVoid)
{
TRHEADPARAM* pThreadParam = (TRHEADPARAM*)pVoid; THREAD_INFO* pThreadInfo = new THREAD_INFO;
pThreadInfo->s = pThreadParam->s; //监听得到的客户端SOCKET句柄
pThreadInfo->hRecvThread = pThreadParam->hRecvThread; //为客户端创建的数据接收线程句柄
//.......部分代码省略
while(TRUE)
{
//接收数据
int iRet = recv(ClientSocket,sz_RecvBuff,sizeof(sz_RecvBuff),0);
if (iRet > 0 )
{
//处理出具
Run(pThreadInfo , sz_RecvBuff, iRet);
}
else
break;
} //删除客户端列表
..........
//释放线程参数
delete pThreadParam;
pThreadParam = NULL;
}void Run(THREAD_INFO* pInfo, char* szBuff, int ilen)
{
BYTE nOrder = szBuff[...]; //得到命令标识
//接收数据时,连接登录命令处理
case 'L': //连接握手命令
{
//如果之前存在此设备的连接线程,就关闭它
//资源清理工作
if (m_Map_ThreadListLock.AcquireWriteLock())
{
map<string,THREAD_INFO*>::iterator pItor;
pItor = m_Map_ThreadList.find(sDeviceNO);
if (pItor != m_Map_ThreadList.end())
{
closesocket(pItor->second->s);
//等待线程退出
if (WAIT_OBJECT_0 == WaitForSingleObject(pItor->second->hRecvThread,INFINITE))
{
CloseHandle(pItor->second->hRecvThread);
pItor->second->hRecvThread = NULL;
delete pItor->second;
pItor->second = NULL;
}
}
m_Map_ThreadList[sDeviceNO] = pThreadInfo;
m_Map_ThreadListLock.ReleaseWriteLock();
} //.....建立客户端列表
................. //完成连接
}
break;
}
兄弟,我有几个疑问:
线程B:
进入互斥区间
检查并设置本地标志 //这个检查并设置本地标志,具体是做什么,这个标志是什么意思?
离开互斥区间
通知A退出。
如果本地标志:waitforsingleobject()等待线程A退出
分配资源这个思路,是否考虑到多线程并发的问题,就是当用户A,同时在两个或更多电脑上登录的情况?
引用jxingcn
“我已经用这种方法解决了,但还是存在一个小问题,就是用户退出之后,下面这个结构体指针资源不能释放,只有再用户再次登录的时候,才能释放上次连接的资源”可以考虑搞个资源释放线程,每当这个线程要退出时,加入清理队列。
在清理函数中等待这个线程结束,清理资源。
你这样会存在一个问题:
用户A,在两个地方同时登录:
线程A: 线程B:
进入互斥区 进入互斥区
检查无等待线程标识 检查无等待线程标识
离开互斥区 离开互斥区
此时线程切换到线程B:
进入互斥区 进入互斥区
建立客户端列表 建立客户端列表
离开互斥区 离开互斥区这样,建立起的客户端列表就不正确了,可能是B线程的,也可能是A线程的