多线程教程之三---线程的死锁
来源:互联网 发布:数据库excel导入 编辑:程序博客网 时间:2024/05/16 23:44
多个线程间如果相互等待对方拥有的资源,将可能发生死锁。
上一篇中我介绍了一种通过封闭Critical Section对象而方便的使用互斥锁的方式,文中所有的例子是两个线程对同一数据一读一写,因此需要让它们在这里互斥,不能同时访问。而在实际情况中可能会有更复杂的情况出现,就是多个线程访问同一数据,一部分是读,一部分是写。我们知道只有读-写或写-写同时进行时可能会出现问题,而读-读则可以同时进行,因为它们不会对数据进行修改,所以也有必要在C++中封装一种方便的允许读-读并发、读-写与写-写互斥的锁。要实现这种锁,使用临界区就很困难了,不如改用内核对象,这里我使用的是互斥量(Mutex)。
总体的结构与上一篇中的类似,都是写出一个对锁进行封装的基类,再写一个用于调用加、解锁函数的类,通过对第二个类的生命周期的管理实现加锁和解锁。这里涉及到两个新问题,一是加锁、解锁动作都有两种,一种是加/解读锁,一种是加/解写锁;二是为了允许读-读并发,这里只声明一个Mutex是不够的,必须要声明多个Mutex,而且有多少个Mutex就同时允许多少个读线程并发,之所以这么说,是因为我们要使用的API函数是WaitForMultipleObjects。
template <int maxReadCount = 3> //这里给一个缺省参数,尽量减少客户端代码量
bool lockSuccess; //因为有可能超时,需要保存是否等待成功
int readLockHandleIndex; //对于读锁,需要知道获得的是哪个互斥量
_RWLockBase* _pObj; //目标对象基类指针
public:
//这里通过第二个参数决定是加读锁还是写锁,第三个参数为超时的时间
RWLock(_RWLockBase* pObj, bool readLock = true, int timeout = 3000)
...{
_pObj = pObj;
lockSuccess = FALSE;
readLockHandleIndex = -1;
if(NULL == _pObj)
return;
if(readLock) //读锁
...{
DWORD retval = _pObj->ReadLock(timeout);
if(retval < WAIT_ABANDONED) //返回值小于WAIT_ABANDONED表示成功
...{ //其值减WAIT_OBJECT_0就是数组下标
readLockHandleIndex = retval - WAIT_OBJECT_0;
lockSuccess = TRUE;
}
}
else
...{
WORD retval = _pObj->WriteLock(timeout);
if(retval < WAIT_ABANDONED) //写锁时获得了所有互斥量,无需保存下标
lockSuccess = TRUE;
}
}
~RWLock()
...{
if(NULL == _pObj)
return;
if(readLockHandleIndex > -1)
_pObj->ReadUnlock(readLockHandleIndex);
else
_pObj->WriteUnlock();
}
bool IsLockSuccess() const ...{ return lockSuccess; }
};
这样一来,读/写锁的类也就完成了,使用时与InstanceLock类似:
1、被锁对象从RWLockBase<>类继承
2、需要加读锁时,声明一个RWLock实例,并指出要加的是读锁
3、需要加写锁时,声明一个RWLock实例,并指出要加的是写锁
这里还是要多说两句,虽然使用纯虚函数结合模板类,使得客户端代码量减到最少,但性能上有一些影响,因为声明了虚函数,则实例中必然存在4个字节的VPTR,调用虚函数时则要查找VTABLE,空间和时间上都有微小的牺牲。而如果不使用模板类,则没有虚函数的代价,但也有牺牲:不使用模板类则需要使用动态数组,动态数组本身需要程序运行时在堆上分配,这也需要时间;指向动态数组的指针也需要占用内存,所以空间上的开锁是一样的,时间上虽然动态分配内存需要的时间应该比虚函数的调用要慢一点,但初始化只需要一次,总体来说也是值得的。所以最终要使用哪一种,就看具体需要了。
这里也给出一个实验。这里所用的被锁类也上一篇类似,简单的从RWLockBase类继承:
class MyClass2: public RWLockBase<>
...{};
MyClass2 mc2;
看看两个线程函数:
//读线程
DWORD CALLBACK ReadThreadProc(LPVOID param)
...{
int i = (int)param;
RWLock lock(&mc2); //加读锁
if(lock.IsLockSuccess()) //如果加锁成功
{
Say("read thread %d started", i); //为了代码短一些,假设Say函数有这种能力
Sleep(1000);
Say("read thread %d ended", i);
}
else //加锁超时,则显示超时信息
Say("read thread %d timeout", i);
return 0;
}
//写线程
DWORD CALLBACK WriteThreadProc(LPVOID param)
...{
int i = (int)param;
RWLock lock(&mc2, false); //加写锁。
if(lock.IsLockSuccess())
...{
Say("write thread %d started", i);
Sleep(600);
Say("write thread %d ended", i);
}
else
Say("write thread %d timeout", i);
return 0;
}
主线程:
int i;
for(i = 0; i < 5; i++)
::CreateThread(0, 0, ReadThreadProc, (LPVOID)i, 0, 0);
for(i = 0; i < 5; i++)
::CreateThread(0, 0, WriteThreadProc, (LPVOID)i, 0, 0);
程序共开10个线程,5个读5个写。从RWLockBase类继承时我们使用了默认的模板参数,所以最多同时允许3个读线程。程序的运行结果如下:
001 [15:07:28.484]read thread 0 started
002 [15:07:28.484]read thread 1 started
003 [15:07:28.484]read thread 2 started
004 [15:07:29.484]read thread 0 ended
005 [15:07:29.484]read thread 3 started
006 [15:07:29.484]read thread 1 ended
007 [15:07:29.484]read thread 4 started
008 [15:07:29.484]read thread 2 ended
009 [15:07:30.484]read thread 3 ended
010 [15:07:30.484]read thread 4 ended
011 [15:07:30.484]write thread 0 started
012 [15:07:31.078]write thread 0 ended
013 [15:07:31.078]write thread 1 started
014 [15:07:31.484]write thread 2 timeout
015 [15:07:31.484]write thread 3 timeout
016 [15:07:31.484]write thread 4 timeout
017 [15:07:31.687]write thread 1 ended
前三行三个读线程取得读锁,之后等一秒(第4-8行),三个读线程都结束了,并且余下的两个读线程取得读锁,虽然这时剩下了一个互斥量没有使用,但因为其他的线程都请求加写锁,写锁与其他所有线程互斥,所以还不能取得写锁。再过一秒(第9-11行),后来的两个取得读锁的线程也结束了,则第一个写线程取得写锁。600毫秒之后(第12-13行)第一个写线程结束,第二个写线程开始。400毫秒之后(第14-16行)余下的三个写线程都超时了,再后第二个写线程也结束了。
SendMessage死锁的及PostThreadMessage消息丢失问题.
1. 如果UI线程调用WaitForMultipleObjects等待线程退出,而这个线程再往UI线程发送消息,则会发生死锁.感觉在WaitForMultipleObjects
内部实现是使用(while + handle + 未知)实现. 因为测试发现, 如果在UI线程执行 Sleep(x) 时, SendMessage也是无法响应的.
但例外的是: 如果在UI线程中调用弹出对话框,使用UI线程阻塞,UI线程依然可以接收到SendMessage发过来的消息,原因可能是对象框继续能
让消息泵运作,而Sleep或WaitForMultipleObjects则能使UI的消息泵停止,至始UI无响应. 见示例代码1.
2. PostThreadMessage消息丢失:
1) 在当UI线程弹出模式对话的情况,
2) 在用户在对UI窗口进行某些UI上的操作,比如调整窗口大小.
(详见:http://blog.csdn.net/yuanmanzheng/archive/2010/04/10/5471487.aspx)
1. 示例代码:
源码copy to clipboard打印?
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
WaitForMultipleObjects(..thread...); // (1). 在此等待线程thread退出,这里导致(2)发送的消息(所有消息)无法处理.
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY); // (2). 因为(1)的原因,这里将永远无法完成,这也导致(1)处无法退出.
return 0;
}
// 消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
WaitForMultipleObjects(..thread...); // (1). 在此等待线程thread退出,这里导致(2)发送的消息(所有消息)无法处理.
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY); // (2). 因为(1)的原因,这里将永远无法完成,这也导致(1)处无法退出.
return 0;
}
// 消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
解决办法就是使用PostMessage,还有可以使用下面代码,最好的办法就是避免写这样的代码.
源码copy to clipboard打印?
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
while (_is_running)
{
MSG msg;
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break ;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Sleep(0);
}
}
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY);
_is_running = false; // 设置为false,这个变量必须在开始线程时置true.
return 0;
}
// WM_TEST_NOTIFY消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
上一篇中我介绍了一种通过封闭Critical Section对象而方便的使用互斥锁的方式,文中所有的例子是两个线程对同一数据一读一写,因此需要让它们在这里互斥,不能同时访问。而在实际情况中可能会有更复杂的情况出现,就是多个线程访问同一数据,一部分是读,一部分是写。我们知道只有读-写或写-写同时进行时可能会出现问题,而读-读则可以同时进行,因为它们不会对数据进行修改,所以也有必要在C++中封装一种方便的允许读-读并发、读-写与写-写互斥的锁。要实现这种锁,使用临界区就很困难了,不如改用内核对象,这里我使用的是互斥量(Mutex)。
总体的结构与上一篇中的类似,都是写出一个对锁进行封装的基类,再写一个用于调用加、解锁函数的类,通过对第二个类的生命周期的管理实现加锁和解锁。这里涉及到两个新问题,一是加锁、解锁动作都有两种,一种是加/解读锁,一种是加/解写锁;二是为了允许读-读并发,这里只声明一个Mutex是不够的,必须要声明多个Mutex,而且有多少个Mutex就同时允许多少个读线程并发,之所以这么说,是因为我们要使用的API函数是WaitForMultipleObjects。
WaitForMultipleObjects函数的功能就是等待对象状态被设置,MSDN中对它的说明为:
Waits until one or all of the specified objects are in the signaled state or the time-out interval elapses.这是个很好用的函数,我们可以用它来等待某个或某几个对象,并且允许设置超时时间,等待成功时与超时时返回的值是不同的。如果返回的值比WAIT_ABANDONED小则表示等待成功。“等待成功”对于不同类型的内核对象有不同的意义,例如对于进程或线程对象,等待成功就表示进程或线程执行结束了;对于互斥量对象,则表示此对象现在不被任何其他线程拥有,并且一旦等待成功,当前线程即拥有了此互斥量,其他线程则不能同时拥有,直接调用ReleaseMutex函数主动释放互斥量。
与WaitForMultipleObjects类似的还有一个函数WaitForSingleObject,它的功能比较简单,只针对单一个对象,而WaitForMultipleObjects可以同时等待多个对象,并且可以设置是否等待所有对象。
上一篇文章中用的InstanceLockBase类里面封装了一个Critical Section对象,这里则要封装一组Mutex的Handle,那么这一组是多少个呢?它应该由使用此类的程序中定义,例如可以用动态数组的方法:
//基类:class RWLockBase //表示Read/Write Lock...{HANDLE* handles;protected:RWLockBase(int handleCount) ...{ handles = new HANDLE[handleCount]; }… };//子类:class MyClass: public RWLockBase ...{MyClass(): RWLockBase(3) ...{}… };这确实是个不错的办法,通过在子类构造函数的初始化段中调用基类构造函数并传参,使得这个动态数组得以正确初始化,不过这样看着不太爽,子类必须两次出现“RWLockBase”一词,能不能像InstanceLockBase那样只要继承了就好呢?答案是肯定的,只要用C++模板即可:
template <int maxReadCount>class RWLockBase...{ HANDLE handles[maxReadCount]; …};使用模板附带这么一个好处,因为模板参数是在编译期可以确定的,所以无需再用动态数组,直接在栈上分配即可。而使用模板引出一个新问题,就是相应的Lock类(RWLock)在构造时传的对象指针时的类型声明,直接写成RWLock(RWLockBase* pObj)肯定是不行的,因为必须指定模板参数,并且其值还必须与声明RWLockBase时所指定的值一致才行,从而客户端代码就必须两次指定模板参数值,不爽!解决的办法也是有一个,就是把RWLockBase变成夹层类,为它再声明一个基类,让RWLock接收的是基类指针,并把Lock、Unlock等函数放在基类中,声明为纯虚函数,实现写在夹层类中:
class _RWLockBase...{ friend class RWLock; protected: virtual DWORD ReadLock(int timeout) = 0; virtual void ReadUnlock(int handleIndex) = 0; virtual DWORD WriteLock(int timeout) = 0; virtual void WriteUnlock() = 0;};模板类RWLockBase从_RWLockBase继承,并对四个函数写出实现:
template <int maxReadCount = 3> //这里给一个缺省参数,尽量减少客户端代码量
class RWLockBase: public _RWLockBase...{HANDLE handles[maxReadCount];DWORD ReadLock(int timeout) //加读锁,只要等到一个互斥量返回即可...{return ::WaitForMultipleObjects(maxReadCount, handles, FALSE, timeout);}void ReadUnlock(int handleIndex) //解读锁,释放已获得的互斥量...{::ReleaseMutex(handles[handleIndex]);}DWORD WriteLock(int timeout) //加写锁,等到所有互斥量,从而与其他所有线程互斥...{ return ::WaitForMultipleObjects(maxReadCount, handles, TRUE, timeout);}void WriteUnlock() //解写锁,释放所有的互斥量...{for(int i = 0; i < maxReadCount; i++)::ReleaseMutex(handles[i]);}protected:WLockBase() //构造函数,初始化每个互斥量...{for(int i = 0; i < maxReadCount; i++)handles[i] = ::CreateMutex(0, FALSE, 0);}~RWLockBase() //析构函数,销毁对象...{for(int i = 0; i < maxReadCount; i++)::CloseHandle(handles[i]);}};
而相应的锁类也会稍复杂一些:
class RWLock
...{bool lockSuccess; //因为有可能超时,需要保存是否等待成功
int readLockHandleIndex; //对于读锁,需要知道获得的是哪个互斥量
_RWLockBase* _pObj; //目标对象基类指针
public:
//这里通过第二个参数决定是加读锁还是写锁,第三个参数为超时的时间
RWLock(_RWLockBase* pObj, bool readLock = true, int timeout = 3000)
...{
_pObj = pObj;
lockSuccess = FALSE;
readLockHandleIndex = -1;
if(NULL == _pObj)
return;
if(readLock) //读锁
...{
DWORD retval = _pObj->ReadLock(timeout);
if(retval < WAIT_ABANDONED) //返回值小于WAIT_ABANDONED表示成功
...{ //其值减WAIT_OBJECT_0就是数组下标
readLockHandleIndex = retval - WAIT_OBJECT_0;
lockSuccess = TRUE;
}
}
else
...{
WORD retval = _pObj->WriteLock(timeout);
if(retval < WAIT_ABANDONED) //写锁时获得了所有互斥量,无需保存下标
lockSuccess = TRUE;
}
}
~RWLock()
...{
if(NULL == _pObj)
return;
if(readLockHandleIndex > -1)
_pObj->ReadUnlock(readLockHandleIndex);
else
_pObj->WriteUnlock();
}
bool IsLockSuccess() const ...{ return lockSuccess; }
};
这样一来,读/写锁的类也就完成了,使用时与InstanceLock类似:
1、被锁对象从RWLockBase<>类继承
2、需要加读锁时,声明一个RWLock实例,并指出要加的是读锁
3、需要加写锁时,声明一个RWLock实例,并指出要加的是写锁
这里还是要多说两句,虽然使用纯虚函数结合模板类,使得客户端代码量减到最少,但性能上有一些影响,因为声明了虚函数,则实例中必然存在4个字节的VPTR,调用虚函数时则要查找VTABLE,空间和时间上都有微小的牺牲。而如果不使用模板类,则没有虚函数的代价,但也有牺牲:不使用模板类则需要使用动态数组,动态数组本身需要程序运行时在堆上分配,这也需要时间;指向动态数组的指针也需要占用内存,所以空间上的开锁是一样的,时间上虽然动态分配内存需要的时间应该比虚函数的调用要慢一点,但初始化只需要一次,总体来说也是值得的。所以最终要使用哪一种,就看具体需要了。
这里也给出一个实验。这里所用的被锁类也上一篇类似,简单的从RWLockBase类继承:
class MyClass2: public RWLockBase<>
...{};
MyClass2 mc2;
看看两个线程函数:
//读线程
DWORD CALLBACK ReadThreadProc(LPVOID param)
...{
int i = (int)param;
RWLock lock(&mc2); //加读锁
if(lock.IsLockSuccess()) //如果加锁成功
{
Say("read thread %d started", i); //为了代码短一些,假设Say函数有这种能力
Sleep(1000);
Say("read thread %d ended", i);
}
else //加锁超时,则显示超时信息
Say("read thread %d timeout", i);
return 0;
}
//写线程
DWORD CALLBACK WriteThreadProc(LPVOID param)
...{
int i = (int)param;
RWLock lock(&mc2, false); //加写锁。
if(lock.IsLockSuccess())
...{
Say("write thread %d started", i);
Sleep(600);
Say("write thread %d ended", i);
}
else
Say("write thread %d timeout", i);
return 0;
}
主线程:
int i;
for(i = 0; i < 5; i++)
::CreateThread(0, 0, ReadThreadProc, (LPVOID)i, 0, 0);
for(i = 0; i < 5; i++)
::CreateThread(0, 0, WriteThreadProc, (LPVOID)i, 0, 0);
程序共开10个线程,5个读5个写。从RWLockBase类继承时我们使用了默认的模板参数,所以最多同时允许3个读线程。程序的运行结果如下:
001 [15:07:28.484]read thread 0 started
002 [15:07:28.484]read thread 1 started
003 [15:07:28.484]read thread 2 started
004 [15:07:29.484]read thread 0 ended
005 [15:07:29.484]read thread 3 started
006 [15:07:29.484]read thread 1 ended
007 [15:07:29.484]read thread 4 started
008 [15:07:29.484]read thread 2 ended
009 [15:07:30.484]read thread 3 ended
010 [15:07:30.484]read thread 4 ended
011 [15:07:30.484]write thread 0 started
012 [15:07:31.078]write thread 0 ended
013 [15:07:31.078]write thread 1 started
014 [15:07:31.484]write thread 2 timeout
015 [15:07:31.484]write thread 3 timeout
016 [15:07:31.484]write thread 4 timeout
017 [15:07:31.687]write thread 1 ended
前三行三个读线程取得读锁,之后等一秒(第4-8行),三个读线程都结束了,并且余下的两个读线程取得读锁,虽然这时剩下了一个互斥量没有使用,但因为其他的线程都请求加写锁,写锁与其他所有线程互斥,所以还不能取得写锁。再过一秒(第9-11行),后来的两个取得读锁的线程也结束了,则第一个写线程取得写锁。600毫秒之后(第12-13行)第一个写线程结束,第二个写线程开始。400毫秒之后(第14-16行)余下的三个写线程都超时了,再后第二个写线程也结束了。
SendMessage死锁的及PostThreadMessage消息丢失问题.
1. 如果UI线程调用WaitForMultipleObjects等待线程退出,而这个线程再往UI线程发送消息,则会发生死锁.感觉在WaitForMultipleObjects
内部实现是使用(while + handle + 未知)实现. 因为测试发现, 如果在UI线程执行 Sleep(x) 时, SendMessage也是无法响应的.
但例外的是: 如果在UI线程中调用弹出对话框,使用UI线程阻塞,UI线程依然可以接收到SendMessage发过来的消息,原因可能是对象框继续能
让消息泵运作,而Sleep或WaitForMultipleObjects则能使UI的消息泵停止,至始UI无响应. 见示例代码1.
2. PostThreadMessage消息丢失:
1) 在当UI线程弹出模式对话的情况,
2) 在用户在对UI窗口进行某些UI上的操作,比如调整窗口大小.
(详见:http://blog.csdn.net/yuanmanzheng/archive/2010/04/10/5471487.aspx)
1. 示例代码:
源码copy to clipboard打印?
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
WaitForMultipleObjects(..thread...); // (1). 在此等待线程thread退出,这里导致(2)发送的消息(所有消息)无法处理.
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY); // (2). 因为(1)的原因,这里将永远无法完成,这也导致(1)处无法退出.
return 0;
}
// 消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
WaitForMultipleObjects(..thread...); // (1). 在此等待线程thread退出,这里导致(2)发送的消息(所有消息)无法处理.
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY); // (2). 因为(1)的原因,这里将永远无法完成,这也导致(1)处无法退出.
return 0;
}
// 消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
解决办法就是使用PostMessage,还有可以使用下面代码,最好的办法就是避免写这样的代码.
源码copy to clipboard打印?
void CMainDlg::test(int nVal) // 测试函数,在UI线程中调用.
{
_notify_thread_exit.notify_all();
while (_is_running)
{
MSG msg;
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break ;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Sleep(0);
}
}
}
int CMainDlg::thread() // 线程函数.
{
boost::mutex::scoped_lock lock(_mutex);
_notify_thread_exit.wait(lock);
SendMessage(WM_TEST_NOTIFY);
_is_running = false; // 设置为false,这个变量必须在开始线程时置true.
return 0;
}
// WM_TEST_NOTIFY消息响应函数.
LRESULT CMainDlg::OnTestNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
std::cout << "OnTestNotify ..." << std::endl;
return 0;
}
- 多线程教程之三---线程的死锁
- 【多线程】多线程教程之三---线程的死锁
- java多线程之 ---- 线程死锁
- java多线程之线程死锁
- Java多线程之线程同步和死锁
- Android线程—多线程之死锁解决办法
- 多线程编程之线程死锁问题
- Java多线程与并发(三)之死锁
- 【线程死锁】Android多线程死锁的产生以及如何避免
- Java菜鸟学习笔记--多线程篇(三):线程死锁
- Java多线程:线程死锁
- Java多线程:线程死锁
- Java多线程-线程死锁
- 多线程编程---线程死锁
- 多线程之三 线程通信
- 多线程专题之线程死锁原因之谜
- iOS开发教程之线程关于多线程的简单介绍
- Java多线程之死锁与线程间通信简单案例
- java的内存分析
- mysql移动目录后不可用的问题
- Response对象总结
- 给 ComboBox.Text 赋值未必可靠
- WCR RIAServices+Silverlight是个好东西.
- 多线程教程之三---线程的死锁
- linux实现zero copy遇到的问题
- git svn 解决中文路径名乱码
- SAP ABAP:如何隐藏你写的程序代码(悲剧的后门)
- 学习jQuery必须知道的几种常用方法
- C# Javascript引擎,如何在C#中执行现有的Javacript代码?
- asp.net网站中退出系统后通过后退键重新进入系统的解决方法
- synbian 资源文件编译
- setsockopt