多线程教程之二---线程间同步 - 白永辉的专栏 - 博客频道 - CSDN.NET

标签: 多线程 程之 线程 | 发表时间:2011-10-16 10:39 | 作者:(author unknown) Yi
出处:http://blog.csdn.net/

白永辉的专栏

资料收集类的 都是比较经典的东西

分类: 多线程 2011-08-10 22:07 34人阅读 评论(0) 收藏 举报

当多个线程无限制的在同一段时间内访问同一资源时,有可能导致错误的结果的发生,例:

  1. #include <windows.h>  
  2. #include <stdio.h>  
  3.   
  4. long g_iNum1,g_iNum2;  
  5.   
  6. DWORD WINAPI SubThread(LPVOID lpParam)  
  7. {  
  8.     for(int i=0; i<10000; i++)  
  9.     {  
  10.         g_iNum1+=1;  
  11.         g_iNum2+=2;  
  12.     }  
  13.     return 0;  
  14. }  
  15.   
  16. void main()  
  17. {  
  18.     HANDLE hThreads[2];  
  19.     g_iNum1=0;  
  20.     g_iNum2=0;  
  21.     hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  22.     hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  23.     SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST);  
  24.     SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST);  
  25.     //等待两个线程都执行结束  
  26.     WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);  
  27.     printf("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2);  
  28. }  
#include <windows.h> #include <stdio.h> long g_iNum1,g_iNum2; DWORD WINAPI SubThread(LPVOID lpParam) { for(int i=0; i<10000; i++) { g_iNum1+=1; g_iNum2+=2; } return 0; } void main() { HANDLE hThreads[2]; g_iNum1=0; g_iNum2=0; hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL); hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL); SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST); SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST); //等待两个线程都执行结束 WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); printf("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2); }两个全局变量被两个线程竞争使用,而且没有保护。因此,最终输出的结果很有可能不是20000和40000。(如果 i 的上限设太小,一个线程分配的时间片就可以计算完成,就不会有竞争,结果也跟预期一样)

为解决线程同步处理机制。常用的同步处理机制包括互锁函数、临界区、和进程、线程、互斥量、信号量、事件等Windows内核对象。

1. 原子访问,互锁函数

互锁函数提供了一套多个线程同步访问一个简单变量的处理机制。
 LONG InterlockedIncrement(LONG volatile* lpAddend);
该函数提供多线程情况下,对一个变量以原子操作方式增加1
LONG InterlockedDecrement(LONG volatile* lpAddend);
该函数提供多线程情况下,对一个变量以原子操作方式减少1
LONG InterlockedExchange(LONG volatile* lpTarget,LONG lValue);
该函数提供在多线程情况下,以原子操作方式用lValue给lpTarget指向的目标变量赋值,并返回赋值以前的lpTarget指向的值。
LONG InterlockedExchangeAdd(LONG volatile* lpAddend,LONG lValue)
该函数提供在多线程情况下,以院子的操作方式将lpAddend指向的变量增加lValue,并返回调用前的lpAddend指向的目标变量的值。
示例:
  1. long g_iNum1,g_iNum2;  
  2.    
  3. DWORD WINAPI SubThread(LPVOID lpParam)  
  4. {  
  5.     for(int i=0; i<10000; i++)  
  6.     {  
  7.         InterlockedIncrement(&g_iNum1);  
  8.         InterlockedExchangeAdd(&g_iNum2,2);  
  9.     }  
  10.     return 0;  
  11. }  
  12.    
  13. void Test()  
  14. {  
  15.     HANDLE hThreads[2];  
  16.     g_iNum1=0;  
  17.     g_iNum2=0;  
  18.     hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  19.     hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  20.     SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST);  
  21.     SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST);  
  22.     WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);  
  23.     TRACE("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2);  
  24. }  
long g_iNum1,g_iNum2;  DWORD WINAPI SubThread(LPVOID lpParam) { for(int i=0; i<10000; i++) { InterlockedIncrement(&g_iNum1); InterlockedExchangeAdd(&g_iNum2,2); } return 0; }  void Test() { HANDLE hThreads[2]; g_iNum1=0; g_iNum2=0; hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL); hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL); SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST); SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST); WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); TRACE("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2); }

2. 临界区

临界区是一段连续的代码区域,它要求在执行前获得对某些共享数据的独占的访问权。如果一个进程中的所有线程中访问这些共享数据的代码都放在临界区中,就能够实现对该共享数据的同步访问。临界区只能用于同步单个进程中的线程。
例:
  1. //多个线程共享的全局数据  
  2. long g_iNum1,g_iNum2;  
  3. //实例化临界区对象  
  4. CRITICAL_SECTION g_sec;  
  5.    
  6. DWORD WINAPI SubThread(LPVOID lpParam)  
  7. {  
  8.     for(int i=0; i<10000; i++)  
  9.     {  
  10.         //进入临界区,临界区对象的引用计数加1,同一个线程可以多次调用  
  11.         //EnterCriticalSection,但是如果调用n次EnterCriticalSection以后,  
  12.         //必须再调用n次的LeaveCriticalSection,使临界区对象的引用计数变为0,  
  13.         //其它的线程才能进入临界区  
  14.         EnterCriticalSection(&g_sec);  
  15.         g_iNum1++;  
  16.         g_iNum2+=2;  
  17.         //离开临界区  
  18.         LeaveCriticalSection(&g_sec);  
  19.     }  
  20.     return 0;  
  21.     }  
  22.    
  23. void Test()   
  24. {  
  25.     HANDLE hThreads[2];  
  26.     g_iNum1=0;  
  27.     g_iNum2=0;  
  28.     //初始化临界区对象  
  29.     InitializeCriticalSection(&g_sec);  
  30.     hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  31.     hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  32.     SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST);  
  33.     SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST);  
  34.     WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);  
  35.     //释放临界区对象  
  36.     DeleteCriticalSection(&g_sec);  
  37.     TRACE("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2);  
  38. }  
//多个线程共享的全局数据 long g_iNum1,g_iNum2; //实例化临界区对象 CRITICAL_SECTION g_sec;  DWORD WINAPI SubThread(LPVOID lpParam) { for(int i=0; i<10000; i++) { //进入临界区,临界区对象的引用计数加1,同一个线程可以多次调用 //EnterCriticalSection,但是如果调用n次EnterCriticalSection以后, //必须再调用n次的LeaveCriticalSection,使临界区对象的引用计数变为0, //其它的线程才能进入临界区 EnterCriticalSection(&g_sec); g_iNum1++; g_iNum2+=2; //离开临界区 LeaveCriticalSection(&g_sec); } return 0; }  void Test() { HANDLE hThreads[2]; g_iNum1=0; g_iNum2=0; //初始化临界区对象 InitializeCriticalSection(&g_sec); hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL); hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL); SetThreadPriority(hThreads[0],THREAD_PRIORITY_LOWEST); SetThreadPriority(hThreads[1],THREAD_PRIORITY_LOWEST); WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); //释放临界区对象 DeleteCriticalSection(&g_sec); TRACE("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2); }

3. 内核对象(用的比较多)

临界区非常适合于在同一个进程内部以序列化的方式访问共享的数据。然而,有时用户希望一个线程与其他线程执行的某些操作取得同步,这就需要使用内核对象来同步线程。

常用的内核对象有进程、线程、互斥量、信号量和事件,其他的还包括文件、控制台输入、文件变化通知、可等待的计时器。

每一个内核对象在任何时候都处于两种状态之一:信号态(signaled)和无信号态(nonsignaled)。线程在等待其中的一个或多个内核对象时,如果在等待的一个或多个内核对象处于无信号态,线程自身将被系统挂起,直到等待的内核对象变为有信号状态时,线程才恢复运行。
常用的等待函数有2个:
DWORD WaitForSingleObject( //等待单个内核对象
HANDLE hHandle, //指向内核对象的句柄
DWORD dwMilliseconds //等待的毫秒数,如果传入INFINITE,则无限期等待。
);
WaitForSingleObject函数返回值

返回值含义: 

   WAIT_OBJECT_0 对象处于有信号状态 
   WAIT_TIMEOUT 对象在指定时间内没有变为有信号状态 
   WAIT_ABANDONED 对象是一个互斥量,由于被放弃了而变为有信号状态 
   WAIT_FAILED 发生了错误。调用GetLastError可以得到详细的错误信息 

DWORD WaitForMultipleObjects( //等待多个对象

DWORD nCount, //对象的个数
CONST HANDLE *lpHandles, //对象句柄数组
BOOL bWaitAll, //是否要等到所有的对象都变为信号态
DWORD dwMilliseconds //等待的毫秒数,如果传入INFINITE,则无限期等待。
)
3.1. 互斥量
互斥量类似于临界区,但它能够同步多个进程间的数据访问。

示例:

  1. #include <windows.h>  
  2. #include <stdio.h>  
  3.   
  4. long g_iNum1,g_iNum2;  
  5. //同步对象  
  6. HANDLE g_hMutex=CreateMutex(NULL, FALSE, NULL);  
  7.    
  8. DWORD WINAPI SubThread(LPVOID lpParam)  
  9. {  
  10.     for(int i=0; i<1000000; i++)  
  11.     {  
  12.         //等待互斥量,如果互斥量处于信号态,该函数返回,同时  
  13.         //将g_hMutex变为无信号态  
  14.         WaitForSingleObject(g_hMutex,INFINITE);  
  15.         g_iNum1++;  
  16.         g_iNum2+=2;  
  17.         //是互斥量重新处于信号态  
  18.         ReleaseMutex(g_hMutex);  
  19.     }  
  20.     return 0;  
  21. }  
  22.   
  23. void main()   
  24. {  
  25.     HANDLE hThreads[2];  
  26.     g_iNum1=0;  
  27.     g_iNum2=0;  
  28.     //创建互斥量  
  29.     g_hMutex=CreateMutex(NULL,FALSE,"MutexForSubThread");  
  30.     hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  31.     hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  32.     WaitForMultipleObjects(2,hThreads,TRUE,INFINITE);  
  33.     //关闭互斥量  
  34.     CloseHandle(g_hMutex);  
  35.     printf("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2);  
  36. }  
#include <windows.h> #include <stdio.h> long g_iNum1,g_iNum2; //同步对象 HANDLE g_hMutex=CreateMutex(NULL, FALSE, NULL); DWORD WINAPI SubThread(LPVOID lpParam) { for(int i=0; i<1000000; i++) { //等待互斥量,如果互斥量处于信号态,该函数返回,同时 //将g_hMutex变为无信号态 WaitForSingleObject(g_hMutex,INFINITE); g_iNum1++; g_iNum2+=2; //是互斥量重新处于信号态 ReleaseMutex(g_hMutex); } return 0; } void main() { HANDLE hThreads[2]; g_iNum1=0; g_iNum2=0; //创建互斥量 g_hMutex=CreateMutex(NULL,FALSE,"MutexForSubThread"); hThreads[0]=CreateThread(NULL,0,SubThread,NULL,0,NULL); hThreads[1]=CreateThread(NULL,0,SubThread,NULL,0,NULL); WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); //关闭互斥量 CloseHandle(g_hMutex); printf("g_iNum1:%d g_iNum2:%d\n",g_iNum1,g_iNum2); }测试感觉速度很慢,不知道是不是内核态和用户态切换的原因;

另外,hMutex=NULL; 的时候都可以运行...

在上例中,主线程创建互斥量,并在创建的同时使互斥量处于信号态。两个子线程在运行过程中,通过调用WaitForSingleObject等待该互斥量,如果此时互斥量处于无信号态,则在等待线程被系统挂起,一直等到互斥量变为信号态才继续运行,WaitForSingleObject返回的同时,将使互斥量再次变为无信号态。最后线程通过调用ReleaseMutex使互斥量变为信号态。

3.2. 信号量
信号量内核对象用于资源计数。每当线程调用WaitForSingleObject函数并传入一个信号量对象的句柄,系统将检查该信号量的资源计数是否大于0,如果大于0,系统就将资源计数减去1,并唤醒线程。如果资源计数为0,系统就将线程挂起,直到另外一个线程释放了该对象,释放信号量意味着增加它的资源计数。
信号量与临界区和互斥量不同,它不属于任何线程。因此可以在一个线程中增加信号量的计数,而在另一个线程中减少信号量的计数。
但是在使用过程中,信号量的使用与互斥量非常相似,互斥量可以看作是信号量的一个特殊版本,即可以将互斥量看作最大资源计数为1的信号量。
通过调用CreateSemaphore函数可以创建一个信号量:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes;
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName
);
参数lMaximumCount指定信号量的最大计数。参数lInitialCount指定信号量的初始计数。参数lpName指定对象的名称。其他进程中的线程使用该名称调用CreateSemaphore函数或OpenSemaphore函数得到信号量的句柄:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
线程使用ReleaseSemaphore函数释放信号量。
BOOL ReleaseSemaphore(
 HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
该函数可以一次增加信号量的大于1的计数,参数lReleaseCount指出一次增加的量。函数在参数lpPreviousCount中返回该函数调用前的信号量的计数。
信号量常用在如下情况下。M个线程对N个共享资源的访问,其中M>N
下面示例模拟32个线程对4个数据库连接对象的访问:
  1. //数据库连接对象的包装  
  2. class CMyConnection  
  3. {  
  4. private:  
  5. BOOL m_bUse;  
  6. public:  
  7. LPVOID m_pObj; //模拟连接对象  
  8. friend class CMyConnectionPool;  
  9. };  
  10. //数据库缓冲池  
  11. class CMyConnectionPool  
  12. {  
  13. protected:  
  14.     CMyConnection *m_pConn; //  
  15.     int m_iCount;  
  16.     HANDLE m_hSemaphore; //信号量  
  17. public:  
  18.     CMyConnectionPool(int iCount)  
  19.     {  
  20.         m_iCount=iCount;  
  21.         m_pConn=new CMyConnection[iCount];  
  22.         for(int i=0; i<m_iCount; i++)  
  23.         {  
  24.             m_pConn[i].m_bUse=FALSE;  
  25.             //m_pConn[i].m_pObj=...模拟连接对象的初始化  
  26.             m_hSemaphore=CreateSemaphore(NULL,iCount,iCount,  
  27.             _T("SemaphoreForMyConnectionPool"));  
  28.         }  
  29.     }  
  30.     ~CMyConnectionPool()  
  31.     {  
  32.         delete []m_pConn;  
  33.         CloseHandle(m_hSemaphore);  
  34.     }  
  35.    
  36.     CMyConnection *GetConnection()  
  37.     {  
  38.         WaitForSingleObject(m_hSemaphore,INFINITE);  
  39.         for(int i=0; i<m_iCount; i++)  
  40.         {  
  41.             if(m_pConn[i].m_bUse==FALSE)  
  42.             {  
  43.                 m_pConn[i].m_bUse=TRUE;  
  44.                 return m_pConn+i;  
  45.             }  
  46.         }  
  47.         ASSERT(FALSE);//应该永远都不会执行到这里  
  48.         return NULL;  
  49.     }  
  50.    
  51.     void ReleaseConnection(CMyConnection *pConn)  
  52.     {  
  53.         pConn->m_bUse=FALSE;  
  54.         ReleaseSemaphore(m_hSemaphore,1,NULL);  
  55.     }  
  56. };  
  57.    
  58. //实例化4个数据库连接对象,并放进缓冲池中使用。  
  59. CMyConnectionPool g_pool(4);  
  60.    
  61. DWORD WINAPI SubThread(LPVOID lpParam)  
  62. {  
  63.     CMyConnection *pConn=g_pool.GetConnection();  
  64.     static long lSubThreadCount=0;  
  65.     InterlockedIncrement(&lSubThreadCount);  
  66.     TRACE(_T("当前线程ID:%X, 子线程数:%d\n"),  
  67.     GetCurrentThreadId(),  
  68.     lSubThreadCount);  
  69.     Sleep(2000);//模拟数据库的访问  
  70.     InterlockedDecrement(&lSubThreadCount);  
  71.     g_pool.ReleaseConnection(pConn);  
  72.     return 0;  
  73. }  
  74.    
  75. void Test()   
  76. {  
  77.     const int iThreadCount=32;  
  78.     HANDLE hThreads[iThreadCount];  
  79.     for(int i=0; i<m_icount; i++)  
  80.         hThreads[i]=CreateThread(NULL,0,SubThread,NULL,0,NULL);  
  81.     WaitForMultipleObjects(iThreadCount,hThreads,TRUE,INFINITE);  
  82. }  
//数据库连接对象的包装 class CMyConnection { private: BOOL m_bUse; public: LPVOID m_pObj; //模拟连接对象 friend class CMyConnectionPool; }; //数据库缓冲池 class CMyConnectionPool { protected: CMyConnection *m_pConn; // int m_iCount; HANDLE m_hSemaphore; //信号量 public: CMyConnectionPool(int iCount) { m_iCount=iCount; m_pConn=new CMyConnection[iCount]; for(int i=0; i<m_iCount; i++) { m_pConn[i].m_bUse=FALSE; //m_pConn[i].m_pObj=...模拟连接对象的初始化 m_hSemaphore=CreateSemaphore(NULL,iCount,iCount, _T("SemaphoreForMyConnectionPool")); } } ~CMyConnectionPool() { delete []m_pConn; CloseHandle(m_hSemaphore); }  CMyConnection *GetConnection() { WaitForSingleObject(m_hSemaphore,INFINITE); for(int i=0; i<m_iCount; i++) { if(m_pConn[i].m_bUse==FALSE) { m_pConn[i].m_bUse=TRUE; return m_pConn+i; } } ASSERT(FALSE);//应该永远都不会执行到这里 return NULL; }  void ReleaseConnection(CMyConnection *pConn) { pConn->m_bUse=FALSE; ReleaseSemaphore(m_hSemaphore,1,NULL); } };  //实例化4个数据库连接对象,并放进缓冲池中使用。 CMyConnectionPool g_pool(4);  DWORD WINAPI SubThread(LPVOID lpParam) { CMyConnection *pConn=g_pool.GetConnection(); static long lSubThreadCount=0; InterlockedIncrement(&lSubThreadCount); TRACE(_T("当前线程ID:%X, 子线程数:%d\n"), GetCurrentThreadId(), lSubThreadCount); Sleep(2000);//模拟数据库的访问 InterlockedDecrement(&lSubThreadCount); g_pool.ReleaseConnection(pConn); return 0; }  void Test() { const int iThreadCount=32; HANDLE hThreads[iThreadCount]; for(int i=0; i<m_icount; i++) hThreads[i]=CreateThread(NULL,0,SubThread,NULL,0,NULL); WaitForMultipleObjects(iThreadCount,hThreads,TRUE,INFINITE); }3.3. 事件对象
与互斥量和信号量不同,互斥量和信号量用于控制对共享数据的访问,而事件发送信号表示某一操作已经完成。有两种事件对象:手动重置事件和自动重置事件。手动重置事件用于同时向多个线程发送信号;自动重置事件用于向一个线程发送信号。
如果有多个线程调用WaitForSingleObject或者WaitForMultipleObjects等待一个自动重置事件,那么当该自动重置事件变为信号态时,其中的一个线程会被唤醒,被唤醒的线程开始继续运行,同时自动重置事件又被置为无信号态,其他线程依旧处于挂起状态。从这一点看,自动重置事件有点类似于互斥量。
手动重置事件不会被WaitForSingleObject和WaitForMultipleObjects自动重置为无信号态,需要调用相应的函数才能将手动重置事件重置为无信号态。因此,当手工重置事件有信号时,所有等待该事件的线程都将被激活。
事件对象使用CreateEvent函数创建:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
参数bManualReset为TRUE时,指定创建的是手动重置事件,否则为自动重置事件;
bInitialState表示事件对象被初始化时是信号态还是无信号态;
参数lpName指定事件对象的名称,其他进程中的线程可以通过该名称调用CreateEvent或者OpenEvent函数得到该事件对象的句柄。
HANDLE OpenEvent(
 DWORD dwDesiredAccess,
 BOOL bInheritHandle,
 LPCTSTR lpName
);
无论自动重置事件对象还是手工重置事件对象,都可以通过SetEvent函数设置为信号态:
BOOL SetEvent(
HANDLE hEvent
);
无论自动重置事件对象还是手工重置事件对象,都可以通过ResetEvent函数设置为无信号态:
BOOL ResetEvent(
HANDLE hEvent
);
不过对于自动重置事件不必执行ResetEvent,因为系统会在WaitForSingleObject或者WaitForMultipleObjects返回前,自动将事件对象置为无信号态。
事件对象使用完毕后,应调用CloseHandle函数关闭它。
查看WINAPI函数调用失败原因的描述的辅助函数,供参考:
  1. CString GetLastErrorStr()  
  2. {  
  3.     CString sRet=_T("");  
  4.     LPVOID lpMsgBuf;  
  5.     DWORD dwError=GetLastError();  
  6.     if(dwError)  
  7.     {  
  8.         if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |   
  9.                 FORMAT_MESSAGE_FROM_SYSTEM |  
  10.                 FORMAT_MESSAGE_IGNORE_INSERTS,  
  11.                 NULL,  
  12.                 dwError,  
  13.                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language  
  14.                 (LPTSTR) &lpMsgBuf,  
  15.                 0,  
  16.             NULL))  
  17.         {  
  18.             sRet.Format(_T("%08X: %s"),dwError,lpMsgBuf);  
  19.       sRet.Replace(_T("\r\n"),_T(""));  
  20.         LocalFree(lpMsgBuf);  
  21.         }  
  22.     }  
  23.     return sRet;  
  24. }  
CString GetLastErrorStr() { CString sRet=_T(""); LPVOID lpMsgBuf; DWORD dwError=GetLastError(); if(dwError) { if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL)) { sRet.Format(_T("%08X: %s"),dwError,lpMsgBuf);  sRet.Replace(_T("\r\n"),_T("")); LocalFree(lpMsgBuf); } } return sRet; }
分享到:
查看评论

  暂无评论

您还没有登录,请[登录][注册]
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料

    xiaobai1593
    • 访问:7134次
    • 积分:787分
    • 排名:第7415名
    • 原创:62篇
    • 转载:54篇
    • 译文:0篇
    • 评论:6条
    文章搜索
    文章分类
    最新评论
公司简介|招贤纳士|广告服务|银行汇款帐号|联系方式|版权声明|法律顾问|问题报告
北京创新乐知信息技术有限公司 版权所有, 京 ICP 证 070598 号
世纪乐知(北京)网络技术有限公司 提供技术支持
江苏乐知网络技术有限公司 提供商务支持
Email:[email protected]
Copyright © 1999-2011, CSDN.NET, All Rights Reserved
GongshangLogo

相关 [多线程 程之 线程] 推荐:

Java多线程之synchronized

- - CSDN博客推荐文章
这里通过三个测试类阐述了synchronized应用的不同场景. 首先是最基本的synchronized Method的使用.  * @see 概述:Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor) .  * @see 说明:当synchronized关键字修饰一个方法时,则该方法为同步方法 .

Java多线程之wait()和notify()

- - CSDN博客推荐文章
直接看测试代码吧,细节之处,详见注释.  * Java多线程之wait()和notify()的妙用 .  * @see 问题:同时启动两个线程和同时启动四个线程,控制台打印结果是不同的 .  * @see      同时启动两个线程时,控制台会很规律的输出1010101010101010 .  * @see      同时启动四个线程时,控制台起初会规律的输出10101010,一旦某一刻输出一个负数,那么后面的输出就会"一错再错" .

Java多线程之wait和notify

- - ITeye博客
最近在看Java特种兵,看到多线程部分,对wait和notify不是很理解,于是写了代码来帮助理解.              wait方法通过参数可以指定等待的时长. 如果没有指定参数,默认一直等待直到被通知. notify方法是通知某个正在等待这个对象的控制权的线程可以继续运行. 调用wait方法时候,必须加上synchronized同步块,不然会抛出java.lang.IllegalMonitorStateException异常.

Java多线程之内存可见性

- - CSDN博客推荐文章
一、JAVA内存模型简介. JAVA Merory  Model描述了JAVA程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节. 所有的变量都保存在主内存中,但是每个线程都有自己的独立工作内存,保存该线程使用到的变量的一个副本. 1.线程对共享变量的操作只能在独立的工作内存中进行,不能在主内存中直接读写;.

JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

- - CSDN博客综合推荐文章
当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try....catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等. 可以看到在多线程中通过try....catch试图捕获线程的异常是不可取的.

Java Thread多线程

- - CSDN博客推荐文章
Java Thread多线程. Java 多线程例子1 小例子. super("zhuyong");//设置线程的名字,默认为“TestThread”. Java 多线程例子2 前台线程(用户线程) 后台线程(守护线程 ). 1,setDaemon(true)后就是后台线程(守护线程 ),反之就是前台线程(用户线程).

多线程教程之二---线程间同步 - 白永辉的专栏 - 博客频道 - CSDN.NET

- Yi - blog.csdn.net
资料收集类的 都是比较经典的东西.  安装Chrome浏览器下载资源送30个下载分.                                        500元移动大会门票开抢. 2011移动开发者大会亮点之二:七大论坛神秘嘉宾闪亮登场.                “IT适合你吗.

[转]GDB调试多线程

- - 小彰
GDB 多线程调试基本命令 实现简介 以及一个问题的解决. 一直对GDB多线程调试接触不多,最近因为工作有了一些接触,简单作点记录吧. 先介绍一下GDB多线程调试的基本命令. 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID. 切换当前调试的线程为指定ID的线程.

java多线程总结

- - Java - 编程语言 - ITeye博客
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 对于直接继承Thread的类来说,代码大致框架是:. class 类名 extends Thread{. * @author Rollen-Holt 继承Thread类,直接调用run方法.             System.out.println(name + "运行     " + i);.

[转]Oracle Parallel 多线程

- - Oracle - 数据库 - ITeye博客
对于一个大的任务,一般的做法是利用一个进程,串行的执行,如果系统资源足够,可以采用parallel技术,把一个大的任务分成若干个小的任务,同时启用n个进程/线程,并行的处理这些小的任务,这些并发的进程称为并行执行服务器(parallel executeion server),这些并发进程由一个称为并发协调进程的进程来管理.