多线程(四)多线程同步_Critical Section临界区
临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。和使用mutex一样,它们都是以原子操作方式来对共享资源进行访问。
临界区又叫关键代码段,与上一篇的mutex互斥体实现的功能一样,都是为了让多线程同步
从上面图片可以看到二者的区别,如果是在当前进程进行线程同步,只需要采用临界区即可
如果需要跨进程,就采用互斥体,最常用的一种情况就是通过在程序初始化时先打开斥体对象,如果失败就创建一个互斥体对象,反之就结束进程,以此来保证程序在任意时间只运行一份
临界区中所有API都需要传递一个C R I T I C A L _ S E C T I O N结构指针,这个结构不需要对其中成员进行赋值,只需要定义。当使用相关API时,传递结构地址即可
注意:在进入临界区和退出临界区之间的代码都处于加锁受保护状态,千万不要在其间写sleep之类的函数,否则可能出现死锁
初始化临界区
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
该函数用于对结构的各个成员进行初始化. 注意:当不使用临界区时,需要删除临界区
删除临界区
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
该函数用于对结构中的成员变量进行删除做清理工作。如果有任何线程仍然使用关键代码段,那么不应该删除该代码段
进入临界区
VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
这个函数功能与WaitForSingleObject类相似,它会检测CRITICAL_SECTION中成员根据不种情况做不同操作
1. 如果没有线程拥有访问权,就会修改成员使之指向当前线程,访问计数为1,使当前线程拥有对CRITICAL_SECTION的所有权并且立即返回
2. 如果拥有访问权的是当前线程,就会把访问计数+1. 如果出现这种情况,说明当前线程在某一时刻进入了临界区,但是没有退出临界区,又重新进入了临界区,明显是个错误的操作
3.如果拥有访问权的是其它线程,就会进入等待状态,直到得到拥有权变有可调度状态才会苏醒
注意:当线程进入临界区,不再访问共享资源时,需要退出临界区,否则将出现第2种错误的操作发生
退出离界区
VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);
1.PCRITICAL_SECTION
它会检测CRITICAL_SECTION中成员,如果为0,将不做任何操作直接返回. 如果计数大于0,就-1,如果结果为0,查找等待的线程数量是否大于0?
如果大于1,就从所有等待线中挑选一个线程成为新的拥有者,修改计数器为1,然后返回. 反之如果等待线程数量为0,直接返回
编写一个Demo用于演示Critical Section临界区基本操作,功能与上一篇相同
1. 创建个基于对话框的工程CriticalSectionDemo
2. 添加一个编辑框用于显示售票信息,修改ID为IDC_EDIT_SHOWINFO, 修改属性为不可读.
3.添加一个按钮用于启动线程,修改ID为IDC_BTN_SELLTICKET
4. 先定义相关全局变量和线程函数前置声明
1 int g_nTickNum = 10; //总票数 2 CEdit* g_editShowInfo; //编辑框控件指针 3 CRITICAL_SECTION g_cs; //临界区对象 4 5 //线程函数前置声明 6 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam); 7 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam); 8 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam);
5.OnInitDialog中添加相应代码
1 BOOL CCriticalSectionDemoDlg::OnInitDialog() 2 { 3 CDialogEx::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 //初始化临界区 11 InitializeCriticalSection(&g_cs); 12 //获取EDIT控件指针,供线程内部访问 13 g_editShowInfo = (CEdit*)GetDlgItem(IDC_EDIT_SHOWINFO); 14 15 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 16 }
6.按钮_启动线程事件
1 void CCriticalSectionDemoDlg::OnBnClickedBtnSellticket() 2 { 3 //创建三个售票窗口线程,创建后直接关闭句柄 4 CloseHandle(CreateThread(NULL,0,ThreadSellTicket_One,NULL,0,NULL)); 5 CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Two,NULL,0,NULL)); 6 CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Three,NULL,0,NULL)); 7 }
7.三个售票窗口线程代码
1 //线程_售票窗口1 2 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam) 3 { 4 CString strEdit; 5 CString strNew; 6 while (true) 7 { 8 Sleep(1000); 9 EnterCriticalSection(&g_cs); 10 g_editShowInfo->GetWindowText(strEdit); 11 if(g_nTickNum > 0) 12 { 13 strNew.Format(_T("线程1:剩余票数:%d,售出1张票\r\n"),g_nTickNum--); 14 strEdit += strNew; 15 if(g_nTickNum == 0) 16 { 17 strEdit += _T("线程1:票己售空,关闭售票窗口\r\n"); 18 g_editShowInfo->SetWindowText(strEdit); 19 LeaveCriticalSection(&g_cs); 20 break; 21 } 22 else 23 { 24 g_editShowInfo->SetWindowText(strEdit); 25 LeaveCriticalSection(&g_cs); 26 } 27 } 28 else 29 { 30 strEdit += _T("线程1:票己售空,关闭售票窗口\r\n"); 31 g_editShowInfo->SetWindowTextW(strEdit); 32 LeaveCriticalSection(&g_cs); 33 break; 34 } 35 } 36 return true; 37 }
1 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam) 2 { 3 CString strEdit; 4 CString strNew; 5 while (true) 6 { 7 Sleep(1000); 8 EnterCriticalSection(&g_cs); 9 g_editShowInfo->GetWindowText(strEdit); 10 if(g_nTickNum > 0) 11 { 12 strNew.Format(_T("线程2:剩余票数:%d,售出1张票\r\n"),g_nTickNum--); 13 strEdit += strNew; 14 if(g_nTickNum == 0) 15 { 16 strEdit += _T("线程2:票己售空,关闭售票窗口\r\n"); 17 g_editShowInfo->SetWindowText(strEdit); 18 LeaveCriticalSection(&g_cs); 19 break; 20 } 21 else 22 { 23 g_editShowInfo->SetWindowText(strEdit); 24 LeaveCriticalSection(&g_cs); 25 } 26 } 27 else 28 { 29 strEdit += _T("线程2:票己售空,关闭售票窗口\r\n"); 30 g_editShowInfo->SetWindowTextW(strEdit); 31 LeaveCriticalSection(&g_cs); 32 break; 33 } 34 } 35 return true; 36 }
1 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam) 2 { 3 CString strEdit; 4 CString strNew; 5 while (true) 6 { 7 Sleep(1000); 8 EnterCriticalSection(&g_cs); 9 g_editShowInfo->GetWindowText(strEdit); 10 if(g_nTickNum > 0) 11 { 12 strNew.Format(_T("线程3:剩余票数:%d,售出1张票\r\n"),g_nTickNum--); 13 strEdit += strNew; 14 if(g_nTickNum == 0) 15 { 16 strEdit += _T("线程3:票己售空,关闭售票窗口\r\n"); 17 g_editShowInfo->SetWindowText(strEdit); 18 LeaveCriticalSection(&g_cs); 19 break; 20 } 21 else 22 { 23 g_editShowInfo->SetWindowText(strEdit); 24 LeaveCriticalSection(&g_cs); 25 } 26 } 27 else 28 { 29 strEdit += _T("线程3:票己售空,关闭售票窗口\r\n"); 30 g_editShowInfo->SetWindowTextW(strEdit); 31 LeaveCriticalSection(&g_cs); 32 break; 33 } 34 } 35 return true; 36 }
8.DestroyWindow添加相应代码
1 BOOL CCriticalSectionDemoDlg::DestroyWindow() 2 { 3 //删除临界区 4 DeleteCriticalSection(&g_cs); 5 return CDialogEx::DestroyWindow(); 6 }