多线程编程中主要的问题是线程同步问题。
1.什么时间需要线程同步
访问共同的内存数据
2.为什么需要线程同步
多线程程序在执行时由于windows系统机制,会在线程之间来回切换俗称切时间片,当我们的程序没有进行线程同步处理时,Window切时间片不在我们的受控范围内,也就是我们的代码在执行时由于切时间片的原因代码可能会跑到一片被切走执行其他线程的代码,此时我们不知道代码执行到了何处,这种是不可控的,举例,我们在程序里创建了两个同样的线程,都对通一个全局变量执行自加运算,加法指令可以被拆分为3条基本的汇编指令,
a++;对应的汇编代码如线程1中的代码.
假如我们创建了两个同样的线程对全局变量a执行自加操作。由于我们未进行线程同步处理,系统运行时不停的切时间片,如下代码所述
线程1运行
0040102F mov eax,dword ptr [ebp-4]
00401032 add eax,1
切线程到2
00401035 mov dword ptr [ebp-4],eax
线程2运行
0040102? mov eax,dword ptr [ebp-4]将变量a的值送到eax
0040103? add eax,1eax加1的值赋值给eax
0040103? mov dword ptr [ebp-4],eax将eax的值赋值给a
线程2结束
运行完毕返回线程1执行剩余指令;
00401035 mov dword ptr [ebp-4],eax
线程1结束
在线程2中a的值为1
还记得线程1切走前eax的值吗?切走前eax为1
所以尽管两个线程都执行了一遍自加操作正确情况下值应该为2但是值还是1
这就是线程不同步造成的错误。
2。解决办法:
在线程回调中对于有访问共同数据的地方通过控制切换时间片来解决,也就是告诉系统。执行指定的指令时代码必须要跑完才能切换时间片。
怎么控制切时间片:(API)
1.事件对象(此函数有多重用途可以用来响应互斥体 CreateEvent创建的时间对象,CreateSemapHore()简单信号量等)
WaitForSingleObject(xxxhandle)
设置等待某线程执行完毕才切时间片,系统检测到这个函数时会通过传入的参数检测某线程是否执行完毕,没有执行完毕时就会让出时间片,不跑下面的代码,将时间片让个原线程,知道该线程执行完毕。
2.利用互斥体
M_hMutex=CreateMutex()
::WaitForSingleObject(pDlg->m_hMutex, INFINITE);
pDlg->m_nNum1++;
pDlg->m_nNum2++;
//释放所有权
ReleaseMutex(pDlg->m_hMutex);
3. M_event=::CreateEvent(NULL, TRUE, TRUE, NULL);通过创建事件对象
WaitForSingleObject(M_event);
Xxxxxxxx代码
Onbutten1()
{
SetEvent(M_event)//设置有效事件对象
}
OnButten2()
{
ResetEvent(m_hPause);设置无效事件对象
}
可以通过创建有名事件对象的方法来防止程序多开
4.临界区(常用)
InitializeCriticalSection(&m_CS);//初始化临界区
EnterCriticalSection(&pFileDlg->m_CS);
XXXXXXXXXXXXXXXXXXXXXx代码
LeaveCriticalSection(&pFileDlg->m_CS);
5.简单信号量
Createsenaphore()
m_hSemaphore = ::CreateSemaphore(NULL, 1, 2, NULL); //创建信号量
//等待信号量
::WaitForSingleObject(pDlg->m_hSemaphore, INFINITE);
pDlg->m_nNum1++;
pDlg->m_nNum2++;
//添加一个信号量
LONG lCount = 0;
::ReleaseSemaphore(pDlg->m_hSemaphore, 1, &lCount);
6.自加锁 自减锁,交换锁(只能做简单的加减,数据交换操作,主要通过锁系统总线来实现,这种性质决定了无法对大规模程序进行操作,在这种情况下操作系统也无法操作)
可以写一个死循环里面加一个自加锁来看看效果。
//lock xadd 锁系统总线 ==> 无法切换线程
InterlockedIncrement((LONG*)&pDlg->m_nNum1);
InterlockedIncrement((LONG*)&pDlg->m_nNum2);
7.利用事件对象进行简单的防程序多开
在WINAPP的initInstance()初始化中添加如下代码
以上有些函数时可以跨进程的具体哪些请查阅CSDN
//根据名称打开事件对象
HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE,
"CR17Eevent");
if (hEvent != NULL)
{
AfxMessageBox("程序已经运行");
return FALSE;
}
//防止多开
hEvent = ::CreateEvent(NULL, FALSE, FALSE,
"CR17Eevent");
上面这些API微软已经进行了封装,控制台下的多线程请不要使用CreateThread(),没有进行同步,可以使用_BeginThread()来创建多线程,此函数已经实现了同步,或者AfxBeginThread()。也通过修改变异选项来解决同步问题,修改工程设置选项C/C++/ Coder Generation下的Use Run Time Library为多线程共享链接或多线程静态链接来使用后期微软支持多线程的标注库函数。前期C标准库函数是不支持多线程的,包括C++因为这两种语言出现的时候还没考虑到多线程,所以早期标准的库函数是不支持多线程的,也就是微软的C++STL模板不支持多线程,后期微软对标注库函数进行了处理,使之支持多线程,STL需要需要自己解决同步问题,方法,继承微软的类模板,函数实现调用STL模板,函数在调前进行线程同步操作
例如
CLASS::Name()
{
临界区
STL::Name();
退出临界区
}