线程间的同步机制(无规矩不成方圆)

线程同步的必要性:
多个线程同时访问一个全局变量就会出现问题,解决问题:
线程A在修改全局变量的时候,让其他线程等一会儿,等线程A修改完毕之后其他线程再进行修改,在线程A修改的时候,其他线程可以干一点别的事情。
在MFC程序中,我们创建如下对话框:
在这里插入图片描述
并在.cpp文件中写入一下代码:

int g_Num = 0;

UINT __cdecl ThreadProc(LPVOID lpParameter)
{
  for(int idx=0;idx<100;++idx)
  {
   g_Num=g_Num+1;
   CString strNum;
   strNum.Format(_T("%d"),g_Num);
   g_Num=g_Num-1;
  }
  return 0;
}

void C线程同步Dlg::OnBnClickedButton2()
{
 // TODO: 在此添加控件通知处理程序代码
 for(int idx = 1;idx <=50; ++idx)
 {
  AfxBeginThread(ThreadProc, NULL);//循环创建五十个线程
 }
}

void C线程同步Dlg::OnBnClickedButton1()
{
 // TODO: 在此添加控件通知处理程序代码
 int realNum = g_Num;
}

如果线程间同步,最后查看的值应该是0,但该代码里没有添加一丁点同步的内容在里面,因此不能让多线程保证了效率而没有正常运行,必须要进行同步处理。
解决方案:①原子互锁家族函数(针对某一个变量值的改变)
加1操作:InterlockedIncrement()函数,返回被+1之后的值,参数有一个,为Long 类型的地址;
减1操作:InterlockedDecrement()函数;
加上一个指定的值:InterlockedExchangeAdd()函数,可以加负数即减,参数Ⅰ:被加数的地址,参数Ⅱ:加多少,该函数返回被加后的值;
以原子操作的方式,用第二个参数的值取代第一个参数的值:InterlockedExchange或InterlockedExchangePointer,两个参数。
修改代码如下,即可保证最后读到的值为0:

int g_Num = 0;

UINT __cdecl ThreadProc(LPVOID lpParameter)
{
  for(int idx=0;idx<100;++idx)
  {
   //g_Num=g_Num+1;
   InterlockedIncrement((LONG *)&g_Num);
   CString strNum;
   strNum.Format(_T("%d"),g_Num);
   //g_Num=g_Num-1;
   InterlockedDecrement((LONG *)&g_Num);
  }
  return 0;
}

void C线程同步Dlg::OnBnClickedButton2()
{
 // TODO: 在此添加控件通知处理程序代码
 for(int idx = 1;idx <=50; ++idx)
 {
  AfxBeginThread(ThreadProc, NULL);//循环创建五十个线程
 }
}

void C线程同步Dlg::OnBnClickedButton1()
{
 // TODO: 在此添加控件通知处理程序代码
 int realNum = g_Num;
}

②Critical Sections(译为关键代码段/关键区域/临界区域)(多线程间操作更为复杂的东西,对结构体赋值,对链表插入和删除)
没有同步处理的线程,该程序会直接崩掉(在g_ArrString.Add(str);该语句出崩掉):

CStringArray g_ArrString;

UINT __cdecl ThreadProc(LPVOID lpParameter)
{
 int startIdx = (int)lpParameter;//拿出传递过来的整数
 for(int idx=startIdx;idx<startIdx+100;++idx)
  {
   CString str;
   str.Format(_T("%d"),idx);//把整数格式化为字符串
   g_ArrString.Add(str);//放入字符串数组里面 用add Api
  }
  return 0;
}

void C线程同步Dlg::OnBnClickedButton2()
{
 // TODO: 在此添加控件通知处理程序代码
 for(int idx = 1;idx <=50; ++idx)
 {
  AfxBeginThread(ThreadProc, (LPVOID)(idx*10));//循环创建五十个线程,第一个线程就是1*10=10,第二个就是2*10=20
 }
}

void C线程同步Dlg::OnBnClickedButton1()
{
 // TODO: 在此添加控件通知处理程序代码
 CString strCount;
 INT_PTR nCount = g_ArrString.GetCount();//获取数组里面的数目
 strCount.Format(_T("%d"),nCount);
 MessageBox(strCount);//看一下个数是不是5000
 for(INT_PTR idx = 0;idx <nCount;++idx)
 OutputDebugString(g_ArrString.GetAt(idx));//把5000个字符串打印出来
}

同步处理步骤
1.在使用之前一定要初始化,用InitializeCriticalSection();
2.如果不用了,一定要在程序退出来之前把关键代码段进行删除,用DeleteCriticalSection()函数;
3.当有一个线程正在改全局对象的时候,在改之前调用EnterCriticalSection()函数,表明我已经进入了,别的线程等一会儿,因为该线程已经调用该函数进去开始改了,别的线程调用后就会卡住,直到该线程执行完毕,调用LeaveCriticalSection()函数,表明事情办理完毕,告诉大家一声;
4.但除了调用EnterCriticalSection()函数还可以调用TryEnterCriticalSection()函数,后者没有阻塞状态,有返回值Ture 或者 Flase,且会退出线程等待下一次进入。
上述函数都需要一个全局类型的变量,看例子即可
不要在EnterCriticalSection()和LeaveCriticalSection()之间放太多语句,让其他程序等太长时间,影响效率。

CStringArray g_ArrString;
CRITICAL_SECTION g_CS;//里面的成员不需要知道,只是同步函数需要

UINT __cdecl ThreadProc(LPVOID lpParameter)
{
 int startIdx = (int)lpParameter;//拿出传递过来的整数
 for(int idx=startIdx;idx<startIdx+100;++idx)
  {
   CString str;
   str.Format(_T("%d"),idx);//把整数格式化为字符串
   EnterCriticalSection(&g_CS);
   g_ArrString.Add(str);//放入字符串数组里面 用add Api
   LeaveCriticalSection(&g_CS);
  }
  return 0;
}

void C线程同步Dlg::OnBnClickedButton2()
{
 // TODO: 在此添加控件通知处理程序代码
 InitializeCriticalSection(&g_CS);//初始化,知道有这么个东西就行了
 for(int idx = 1;idx <=50; ++idx)
 {
  AfxBeginThread(ThreadProc, (LPVOID)(idx*10));//循环创建五十个线程,第一个线程就是1*10=10,第二个就是2*10=20
 }
}

void C线程同步Dlg::OnBnClickedButton1()
{
 // TODO: 在此添加控件通知处理程序代码
 CString strCount;
 INT_PTR nCount = g_ArrString.GetCount();//获取数组里面的数目
 strCount.Format(_T("%d"),nCount);
 MessageBox(strCount);//看一下个数是不是5000
 for(INT_PTR idx = 0;idx <nCount;++idx)
 OutputDebugString(g_ArrString.GetAt(idx));//把5000个字符串打印出来
 DeleteCriticalSection(&g_CS);//用完之后删除
}

猜你喜欢

转载自blog.csdn.net/qq_42308217/article/details/108475496