VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))
目录
线程同步的必要性:
2.解决同步问题的方法
2.1原子互锁家族函数
2.2Critical Sections(关键代码段、关键区域、临界区域)
2.3 互斥器Mutex
2.4 Semaphores(信号量)
2.5 Event Objects(事件)
线程同步的必要性:
我们知道操作系统的执行最小单位是线程,而一个进程包含了很多的线程,现在已经实现了
真正的并行,如双核cpu,在每个核心里开一个进程,则双核cpu就可以开两个并行运行的进程
而在每个进程类又可以开很多的线程,这里需要强调的是在两个核跑的两个进程是实实在在的并行的, 不会互相干扰,但是在每个核的进程中又运行了很多的线程,而这些线程并不是并行的,而是串行的,即操作系统会在线程中不停的切换执行,在切换执行很快时,给我们的感觉像是并行,但是实际并不是并行,这里大家需要注意,因此在同一个进程中的线程执行是串行的,这个大家需要理解。
在一个进程中,我们定义一个全局变量,此时有很多的线程都在调用或者修改这个全局变量,那么会不会存在这样的一个情况,就是其中一个进程刚修改了这个全局变量,还没来得及使用就被切换到其他线程去了,而被切换的这个线程刚好也要修改或者使用这个全局变量,那个此时会不会出现问题呢?但是肯定的,会导致无法预估的错误,这也就是线程在操作这个变量时需要遵守一定的规则的重要性的原因,而线程同步就是解决这样类似问题的方法,因此线程同步很重要。
1.出现情况的例子
//定义一个全局变量
int g_Num = 0;
UINT __cdecl ThreadProc(LPVOID pParam)
{//线程函数的目的是先进行累加在进行自减,程序执行完应该是g_Num = 0,对吧//但是运行情况会是什么情况呢?for (int idx = 0;idx<100;++idx){g_Num = g_Num + 1;CString strNum;Sleep(5);strNum.Format(_T("%d"), g_Num);g_Num = g_Num - 1;}return 0;
}void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProcfor (int idx = 1; idx<=50;++idx){AfxBeginThread(ThreadProc, NULL);}}void CThreadSynDlg::OnBnClickedResultBut()
{int realNum = g_Num;//经过调试会发现,得到的值都不是0,其原因就是线程在执行过程中可能就会被暂停//导致结果会不一样}
2.解决同步问题的方法
2.1原子互锁家族函数
简单来说,这种保护方法是基于,一旦一个线程去修改这个全局变量就不 允许其他线程再去修改这个全局变量。主要有一下函数:
① InterlockedIncrement 加1操作
② InterlockedDecrement 减1操作
③ InterlockedExchangeAdd加上“指定”的值,可以加上一个负数;
④ InterlockedExchange、InterlockedExchangePointer能够以原子操作的方式用第二个参数的值来取代第一个参数的值;
还有很多可以参数微软的文档及MSDN。一般情况下,在多线程中如果对于某一个变量的值进行改变的话使用以上的互锁函数比较方便,但是很多时候应用场合很复杂,比如对一个结构体进行操作,对类进行操作,对链表进行插入等等,上面的方法就无法满足了,需要引入更高级的方法及:Critical Sections(关键代码段、关键区域、临界区域)
//定义一个全局变量
int g_Num = 0;
UINT __cdecl ThreadProc(LPVOID pParam)
{//线程函数的目的是先进行累加在进行自减,程序执行完应该是g_Num = 0,对吧//但是运行情况会是什么情况呢?for (int idx = 0;idx<100;++idx){//g_Num = g_Num + 1;InterlockedIncrement((LONG*)&g_Num);CString strNum;Sleep(5);strNum.Format(_T("%d"), g_Num);//g_Num = g_Num - 1;InterlockedDecrement((LONG*)&g_Num);}return 0;
}void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProcfor (int idx = 1; idx<=50;++idx){AfxBeginThread(ThreadProc, NULL);}}void CThreadSynDlg::OnBnClickedResultBut()
{int realNum = g_Num;//经过调试会发现,得到的值都是0,其原因就是线程在执行过程中使用了原子锁进行操作,保证了安全性}
2.2Critical Sections(关键代码段、关键区域、临界区域)
//定义一个全局对象
CStringArray g_ArrString;
UINT __cdecl ThreadProc(LPVOID pParam)
{//每个线程函数的目的是在自身的idx的基础上进行100次循环累加,每次//循环都会把数字转换成字符串存储在动态数组对象中g_ArrString,//因为每个线程都会向数组中添加100个字符串,总共50个线程,如果程序正常执行那么字符串数组的元素个数应该为5000//但是运行结果会是什么情况呢?int startIdx = (int)pParam;for (int idx = startIdx;idx< startIdx+100;++idx){CString str;Sleep(1);str.Format(_T("%d"), idx);g_ArrString.Add(str);}return 0;
}void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProc//传入的参数为每个线程idx乘上10for (int idx = 1; idx<=50;++idx){AfxBeginThread(ThreadProc, (LPVOID)(idx*10));}//结果是虽然编译通过了,但是在执行过程中出错了,出现了异常//主要原因是多个线程同时操作动态数组,导致数组操作异常,因此需要//线程同步才能解决,同时使用原子方法是无法解决了,因此需要引入新的//解决方法即Critical Sections(关键代码段、关键区域、临界区域)}
Critical Sections(关键代码段、关键区域、临界区域)使用方法
建立一个Critical Sections对象
1.初始化: InitializeCriticalSection()
2.删除:DeleteCriticalSection()
3.进入:EnterCriticalSection() (可能造成阻塞,原因是一旦有一个线程使用了这个,其他线程在使用,只能等待之前使用
这个的离开 也就是接到5的信号,那么才能开始执行,中间会出现卡顿)
4.尝试进入:TryEnterCriticalSection() (不会造成阻塞)
5.离开:LeaveCriticalSection();
//详细的请参考微软MSDN的文档,搜索同步数据结构
//定义一个全局对象
CStringArray g_ArrString;
//定义一个Critical Sections的对象
CRITICAL_SECTION g_CS;
UINT __cdecl ThreadProc(LPVOID pParam)
{//每个线程函数的目的是在自身的idx的基础上进行100次循环累加,每次//循环都会把数字转换成字符串存储在动态数组对象中g_ArrString,//因为每个线程都会向数组中添加100个字符串,总共50个线程,如果程序正常执行那么字符串数组的元素个数应该为5000//但是运行结果会是什么情况呢?int startIdx = (int)pParam;for (int idx = startIdx;idx< startIdx+100;++idx){CString str;//Sleep(1);str.Format(_T("%d"), idx);//因为修改数组的是g_ArrString.Add(str);,所以在他前面加入即可,后面离开EnterCriticalSection(&g_CS);g_ArrString.Add(str);LeaveCriticalSection(&g_CS);}return 0;
}void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProc//传入的参数为每个线程idx乘上10//初始化关键代码段,同时记得删除InitializeCriticalSection(&g_CS);for (int idx = 1; idx<=50;++idx){AfxBeginThread(ThreadProc, (LPVOID)(idx*10));}//结果是虽然编译通过了,但是在执行过程中出错了,出现了异常//主要原因是多个线程同时操作动态数组,导致数组操作异常,因此需要//线程同步才能解决,同时使用原子方法是无法解决了,因此需要引入新的//解决方法即Critical Sections(关键代码段、关键区域、临界区域)}//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{//这里的主要目的是为了显示计数的个数CString strCount;INT_PTR nCount = g_ArrString.GetCount();strCount.Format(_T("%d"), nCount);MessageBox(strCount);for (INT_PTR idx=0;idx<nCount;++idx){OutputDebugString(g_ArrString.GetAt(idx));}//这里进行删除代码段DeleteCriticalSection(&g_CS);
}
结果是编译通过,执行正确,结果是5000
通过Critical Sections技术,可以避免多线程间的冲突问题,程序可以顺利的执行下去
下面就总结一下,该方法进行线程同步的特点
固有特点:
1.是一个用户模式的对象,不是系统的核心对象;
2.因为不是核心对象,所以执行速度快、有效率
3.因为不是核心对象,所以不能跨进程使用
4.可以多次“进入”,但必须多次“退出”
5.最好不要同时进入或等到多个Critical Sections,容易造成死锁;
什么是死锁呢?简单来说加入开启两个Critical Sections,第一线程进入第一个Critical Sections1,第二个线程进入
Critical Sections2,但是同时呢,进入的Critical Sections1的线程又要打算进入Critical Sections2,而进入Critical Sections2的线程需要 进入
Critical Sections1,结果这两个线程都在等,这样进入死循环了,各自出不来,导致死锁,只有线程死了才能解脱所以叫死锁。
所以避免死锁的最好办法就是进来少建立Critical Sections对象,这样就避免死锁了。
6.无法检测到进入到Critical Sections里面的线程当前是否已经退出,所以在Critical Sections尽量不要执行耗时的操作,,,
那么还有没有更好的方法进行线程间同步呢?当然有下面就介绍互斥器Mutex
2.3 互斥器Mutex
互斥器和2.2的关键代码段方法不同,他是系统的核心对象,所以速度上要比关键代码段方法慢点,当然他也有它的优点,使用方法方面和关键代码段方法差不多,我们来看看:
使用方法:
1.创建一个互斥器:CreateMutex;
2.打开一个已经存在的互斥器:OpenMutex;
3.获得互斥器的拥有权:WaitForSingleObject(),WaitForMultipleObjects(),,,等一类等待的函数,但是可能造成阻塞;
4.释放互斥器拥有权:ReleaseMutex;
5.关闭互斥器:CloseHandle;
具体例子如下:
创建是在 void CThreadSynDlg::OnBnClickedThreadsynBut()函数中
销毁是在 void CThreadSynDlg::OnBnClickedResultBut()函数中
处理是在线程函数中。
//定义一个全局对象
CStringArray g_ArrString;
//定义一个互斥器Mutex的句柄
HANDLE ghMutex = NULL;
UINT __cdecl ThreadProc(LPVOID pParam)
{//每个线程函数的目的是在自身的idx的基础上进行100次循环累加,每次//循环都会把数字转换成字符串存储在动态数组对象中g_ArrString,//因为每个线程都会向数组中添加100个字符串,总共50个线程,如果程序正常执行那么字符串数组的元素个数应该为5000//但是运行结果会是什么情况呢?int startIdx = (int)pParam;for (int idx = startIdx;idx< startIdx+100;++idx){CString str;//Sleep(1);str.Format(_T("%d"), idx);//定义一个单一的等待函数,第一次参数是互斥器的对象,第二个是等待时间,这里是一直等待//因为现在的互斥器是激活态,这线程一旦调用立刻返回,此时互斥器进入非激活态,如果这时候//有其他线程也是用这个WaitForSingleObject函数不会立刻返回,只能等待互斥器的在此激活DWORD dwWaitResult = WaitForSingleObject(ghMutex, INFINITE);switch (dwWaitResult){case WAIT_ABANDONED://这个其实就是没用获得互斥器的激活态,或者其他的线程正在使用case WAIT_OBJECT_0://如果返回的是该值,则说明当前的互斥器已经被当前线程拥有了,可以进场操作了g_ArrString.Add(str);ReleaseMutex(ghMutex);//如果处理完,就释放一下,使互斥器激活态,以便其他线程可以使用break;}}return 0;
}void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProc//传入的参数为每个线程idx乘上10//1.创建一个互斥器:CreateMutex;同时记得释放句柄//一旦创建成功,则处于激活态,也就是现在没有任何一个线程在调用它,可以谁是等待线程的调用//一旦线程线程调用则该互斥器立刻处于非激活态,其他线程在调用只有等待该互斥器的在此激活ghMutex = CreateMutex(NULL, FALSE, NULL);for (int idx = 1; idx<=50;++idx){AfxBeginThread(ThreadProc, (LPVOID)(idx*10));}}//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{//这里的主要目的是为了显示计数的个数CString strCount;INT_PTR nCount = g_ArrString.GetCount();strCount.Format(_T("%d"), nCount);MessageBox(strCount);for (INT_PTR idx=0;idx<nCount;++idx){OutputDebugString(g_ArrString.GetAt(idx));}//关闭互斥器CloseHandle(ghMutex);
}
总结:
命名标准:Mutex可以跨进程使用,所以其名称对整个系统而言是全局的,所以命名不能过于普通,类似Mutex、object等
最后独一无二且易识别
互斥器的特点:
1.是一个系统核心对象,所以有安全描述指针,用完了要CloseHandle关闭句柄,这些是内核对象的共同特征;
2.因为是核心对象,所以执行速度会比Critical Sections慢几乎100倍的时间
3.因为是核心对象,而且可以命名,所以可以跨进程使用;
4.Mutex使用正确的情况下不会发生死锁;
5.在“等待”一个Mutex的时候,可以指定“结束等待”的时间长度;
6.可以检测到当前拥有互斥器所有权的线程是否已经退出!wait。。。。函数会返回:WAIT_ABANDONED
2.4 Semaphores(信号量)
和前几个的处理方法不同,我们从前面的方法中可以看到,其实本质上都同一个时间只有一个线程处理一个对象,
而信号量和他们的区别在与信号量的是同一个时间多个线程同时处理多个对象,这里大家需要理解,前面的都是
一对一,而信号量是多对多。
使用方法:
1.创建一个信号量:CreateSemaphore();
2.打开一个已经存在的信号量:OpenSemaphore();
3.获得信号量的一个占有权:WaitForSingleObject()、 WaitForMultipleObjects()等一类等待函数。。。。。可能造成阻塞
4.释放信号量的占有权:ReleaseSemaphore()
5.关闭信号量:CloseHandle()
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName );
- lpSemaphoreAttributes
[输入]设置为NULL。 - lInitialCount
[in]指定信号量对象的初始计数。此值必须大于或等于零且小于或等于lMaximumCount。当信号量的计数大于零时,将发出信号状态;在信号量为零时,将不发出信号状态。每当等待函数释放等待信号量的线程时,计数就会减少一。通过调用ReleaseSemaphore函数将计数增加指定的数量。 - lMaximumCount
[in]指定信号量对象的最大计数。该值必须大于零。 - lpName
[in]指向以空值结尾的字符串的长指针,该字符串指定信号量对象的名称。名称限制为MAX_PATH字符,并且可以包含除反斜杠路径分隔符(\)之外的任何字符。名称比较区分大小写。
如果lpName与现有命名信号对象的名称匹配,则lInitialCount和lMaximumCount参数将被忽略,因为它们已在创建过程中设置。
每种对象类型(例如内存映射,信号量,事件,消息队列,互斥体和看门狗计时器)都有其自己单独的名称空间。空字符串(“”)被视为命名对象。在基于Windows桌面的平台上,同步对象都共享相同的名称空间。
来自 <https://docs.microsoft.com/en-us/previous-versions/aa911525(v=msdn.10)>
//定义一个全局对象
CStringArray g_ArrString;
//定义一个信号量的全局句柄
HANDLE ghSemaphore = NULL;
UINT __cdecl ThreadProc(LPVOID pParam)
{//每个线程函数的目的是在自身的idx的基础上进行100次循环累加,每次//循环都会把数字转换成字符串存储在动态数组对象中g_ArrString,//因为每个线程都会向数组中添加100个字符串,总共50个线程,如果程序正常执行那么字符串数组的元素个数应该为5000//但是运行结果会是什么情况呢?int startIdx = (int)pParam;CString strOut;while (TRUE){ //因为现在的互斥器是激活态,这线程一旦调用立刻返回,此时互斥器进入非激活态,如果这时候//有其他线程也是用这个WaitForSingleObject函数会立刻返回,因为这是多个线程可以同时进行操作//这里不同的是不需要等待,因为信号量可以多个线程同时操作他DWORD dwWaitResult = WaitForSingleObject(ghSemaphore, 0);switch (dwWaitResult){case WAIT_OBJECT_0://如果返回的是该值,则说明当前信号量已经被当前线程拥有了,可以进场操作了strOut.Format(_T("Thred %d: wait succeeded!"), GetCurrentThreadId());OutputDebugString(strOut);/*可以加入其它要干的活*/ReleaseSemaphore(ghSemaphore,1,NULL);//如果处理完,就释放一下break;case WAIT_TIMEOUT:strOut.Format(_T("Thread %d: wait timed out!"), GetCurrentThreadId());OutputDebugString(strOut);break;}}return 0;
}
//CreateSemaphore
//OpenSemaphore
//WaitForSingleObject() WaitForMultipleObjects()
//ReleaseSemaphore()void CThreadSynDlg::OnBnClickedThreadsynBut()
{//连续开辟了50个线程,每个线程的线程函数都是ThreadProc//传入的参数为每个线程idx乘上10//1.创建信号量ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);//其中在创建的过程中我们需要告诉他多少个线程访问多少个对象//第三个参数是有多少个对象可以执行//第二个参数是初始化时有多少个对象可以执行,一般要小于等于第三个参数的值且大于等于0ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);for (int idx = 1; idx<=20;++idx){AfxBeginThread(ThreadProc, (LPVOID)(idx*10));}}//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{//这里的主要目的是为了显示计数的个数CString strCount;INT_PTR nCount = g_ArrString.GetCount();strCount.Format(_T("%d"), nCount);MessageBox(strCount);for (INT_PTR idx=0;idx<nCount;++idx){OutputDebugString(g_ArrString.GetAt(idx));}//关闭信号量CloseHandle(ghSemaphore);
}
总结:
命名标准:Semaphore可以跨进程使用,所以其名称对整个系统而言是全局的,所以命名不能过于普通,类似Mutex、object等
最后独一无二且易识别
互斥器的特点:
1.是一个系统核心对象,所以有安全描述指针,用完了要CloseHandle关闭句柄,这些是内核对象的共同特征;
2.因为是核心对象,所以执行速度会比Critical Sections慢几乎100倍的时间(相对而言,现在的cpu很快了,几乎没差别了)
3.因为是核心对象,而且可以命名,所以可以跨进程使用;
4.Semaphore使用正确的情况下不会发生死锁;
5.在“等待”一个Semaphore的时候,可以指定“结束等待”的时间长度;
6.非排他性的占有,跟Critical Sections和Mutex不同,这两种而言是排他性的占有,即同一时间内只能有单一的线程获得目标并拥有操作的权利,而Semaphores则不是这样的,同一时间可以有多个线程获得目标并进行操作
如果上面程序使用信号量方式去做向CStringArray中添加节点的同步可以吗:
答案是可以的,此时只需要修改:
CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName );
的第三个参数为1即可,也就是说每次只允许一个线程对象对其操作,这样设置就和前面的互斥锁没什么两样了。
2.5 Event Objects(事件)
Event 方式是最具有弹性的同步机制,因为他的状态完全由你决定,不会像Mutex和Semaphores的状态一样会根据
waitforsingleObject----等类似的调用而改变,所以你需要精确的告诉Event对象该做什么事,以及什么时候去做.
使用方法:
1.创建一个事件对象:CreateEvent;
2.打开一个已经存在的事件对象:OpenEvent
3.获得事件的占有权:waitforsingleObject等函数,可能会阻塞
4.释放事件的占有权(设置为激发状态,以让其他等待的线程苏醒:setEvent
5.手动设置为非激发态,ResetEvent
6.关闭事件对象的句柄:closehandle
特点:
1.是一个系统核心对象,所以有安全描述指针,用完了要CloseHandle关闭句柄,这些是内核对象的共同特征;
2.因为是核心对象,所以执行速度会比Critical Sections慢几乎100倍的时间(相对而言,现在的cpu很快了,几乎没差别了)
3.因为是核心对象,而且可以命名,所以可以跨进程使用;
4.通常被用于overlapped I/O或者被用来设计某些自定义的同步对象。
https://docs.microsoft.com/zh-cn/windows/win32/sync/using-event-objects
#include <windows.h>
#include <stdio.h>#define THREADCOUNT 4 HANDLE ghWriteEvent;
HANDLE ghThreads[THREADCOUNT];DWORD WINAPI ThreadProc(LPVOID);void CreateEventsAndThreads(void)
{int i; DWORD dwThreadID; // Create a manual-reset event object. The write thread sets this// object to the signaled state when it finishes writing to a // shared buffer. ghWriteEvent = CreateEvent( NULL, // default security attributesTRUE, // manual-reset eventFALSE, // initial state is nonsignaledTEXT("WriteEvent") // object name); if (ghWriteEvent == NULL) { printf("CreateEvent failed (%d)\n", GetLastError());return;}// Create multiple threads to read from the buffer.for(i = 0; i < THREADCOUNT; i++) {// TODO: More complex scenarios may require use of a parameter// to the thread procedure, such as an event per thread to // be used for synchronization.ghThreads[i] = CreateThread(NULL, // default security0, // default stack sizeThreadProc, // name of the thread functionNULL, // no thread parameters0, // default startup flags&dwThreadID); if (ghThreads[i] == NULL) {printf("CreateThread failed (%d)\n", GetLastError());return;}}
}void WriteToBuffer(VOID)
{// TODO: Write to the shared buffer.printf("Main thread writing to the shared buffer...\n");// Set ghWriteEvent to signaledif (! SetEvent(ghWriteEvent) ) {printf("SetEvent failed (%d)\n", GetLastError());return;}
}void CloseEvents()
{// Close all event handles (currently, only one global handle).CloseHandle(ghWriteEvent);
}int main( void )
{DWORD dwWaitResult;// TODO: Create the shared buffer// Create events and THREADCOUNT threads to read from the bufferCreateEventsAndThreads();// At this point, the reader threads have started and are most// likely waiting for the global event to be signaled. However, // it is safe to write to the buffer because the event is a // manual-reset event.WriteToBuffer();printf("Main thread waiting for threads to exit...\n");// The handle for each thread is signaled when the thread is// terminated.dwWaitResult = WaitForMultipleObjects(THREADCOUNT, // number of handles in arrayghThreads, // array of thread handlesTRUE, // wait until all are signaledINFINITE);switch (dwWaitResult) {// All thread objects were signaledcase WAIT_OBJECT_0: printf("All threads ended, cleaning up for application exit...\n");break;// An error occurreddefault: printf("WaitForMultipleObjects failed (%d)\n", GetLastError());return 1;} // Close the events to clean upCloseEvents();return 0;
}DWORD WINAPI ThreadProc(LPVOID lpParam)
{// lpParam not used in this example.UNREFERENCED_PARAMETER(lpParam);DWORD dwWaitResult;printf("Thread %d waiting for write event...\n", GetCurrentThreadId());dwWaitResult = WaitForSingleObject( ghWriteEvent, // event handleINFINITE); // indefinite waitswitch (dwWaitResult) {// Event object was signaledcase WAIT_OBJECT_0: //// TODO: Read from the shared buffer//printf("Thread %d reading from buffer\n", GetCurrentThreadId());break; // An error occurreddefault: printf("Wait error (%d)\n", GetLastError()); return 0; }// Now that we are done reading the buffer, we could use another// event to signal that this thread is no longer reading. This// example simply uses the thread handle for synchronization (the// handle is signaled when the thread terminates.)printf("Thread %d exiting\n", GetCurrentThreadId());return 1;
}
VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))相关推荐
- 【Window】线程同步方式1——临界区(关键代码段)
第一节:[Window]创建线程的3种方式 第二节:[Window]线程同步概述 第三节:[Window]线程同步方式1--临界区(关键代码段) 第四节:[Window]线程同步方式2--互斥量 第五 ...
- VC++中多线程学习(MFC多线程)一(线程的创建、线程函数如何调用类成员呢?如何调用主对话框的成员?、MFC中的工作线程和界面线程的区别)
这里废话不多讲了,因为项目原因,需要开启线程进行处理,在不了解线程的情况下,直接百度一下,然后就使用了,结果可想而知,出现了异常,所以花了一天时间系统学习一下多线程,这里主要是针对win32编程方面的 ...
- VC++中多线程学习(MFC多线程)二(线程的相关操作、线程间的通信)
上一篇笼统介绍了如何创建线程以及线程如何和类成员函数通信,本篇将主要介绍: 线程的相关操作 1.线程的挂起和恢复:SuspendThread.ResumeThread 在线程创建并运行后,用户可以对线 ...
- C#多线程学习(四) 多线程的自动管理(线程池) (转载系列)——继续搜索引擎研究...
在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPo ...
- 秒杀多线程第六篇 经典线程同步 事件Event
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...
- C#多线程学习(五) 多线程的自动管理(定时器) (转载系列)——继续搜索引擎研究...
Timer类:设置一个定时器,定时执行用户指定的函数. 定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数. 初始化一个Timer对象: Timer timer ...
- C#多线程学习(一) 多线程的相关概念(转自xugang的blog)
C#多线程学习(一) 多线程的相关概念 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源. 而一个进程又是由多个线程所组成的. 什么是线程? 线程 ...
- [转载]C#多线程学习(一) 多线程的相关概念
原文地址:http://www.cnblogs.com/xugang/archive/2008/04/06/1138856.html 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中 ...
- 秒杀多线程第八篇 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
最新文章
- [AutoMapper]反射自动注册AutoMapper Profile
- 【C++】二叉树的先序、中序、后序遍历序列
- 数据库SQL语句 SELECT LIKE like用法详解
- eth0,eth1,eth2,lo是什么
- 20175126《Java程序设计》第四周学习总结
- BZOJ 1619 [Usaco2008 Nov]Guarding the Farm 保卫牧场:dfs【灌水】
- 使用Nginx做图片服务器时候,配置之后图片访问一直是 404问题解决
- element color-picker源码
- Macmini 2012Late硬盘异响和Mac下设置apm
- 新导智能融合定位可视化物联系统
- 【蓝牙系列】蓝牙5.4到底更新了什么(2)
- Excel受保护的工作表怎么操作?
- 华为k662c的虚拟服务器,华为k662c光猫怎么样? 华为K662c拆机技巧
- python 福利_发现一个舔狗福利!这个Python爬虫神器太爽了,自动下载妹子图片!...
- 武汉大学—华为 “遥感领域人工智能项目合作”
- Java用户权限管理
- uva662DP+回溯
- Dijkstra(迪杰特斯拉)算法(极简版)
- 微信多开App操作流程
- 手机屏幕分辨率全面解析手机屏幕分辨率全面解析 - QVGA HVGA WVGA VGA 指什么
热门文章
- 软件项目经理新手上路10 - 要的是计划,还是?
- 项目经理的第二手准备-坚强的挺着(4)
- 如何更好地优化大数据分析
- 怎样的数据分析才有价值
- 大数据分析软件具备哪些功能特点
- Node.js跨域请求解决方案
- 闭包 python_Python闭包思想与用法浅析
- HTML做出7个网页,HTML适用于除IE 7以外的每个网页浏览器。
- python压缩数据数组长度_python – 如何解压缩字节数组中的gzipped数据?
- php新浪获取ip接口,【php】利用新浪api接口与php获取远程数据的步骤,获取IP地址,并获取相应的IP归属地...