多线程(四)多线程同步_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 }
OnInitDialog

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
 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 }
线程_售票窗口2
 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 }
线程_售票窗口3

8.DestroyWindow添加相应代码

1 BOOL CCriticalSectionDemoDlg::DestroyWindow()
2 {
3     //删除临界区
4     DeleteCriticalSection(&g_cs);
5     return CDialogEx::DestroyWindow();
6 }
DestroyWindow

最终演示效果如下:

posted @ 2019-12-20 14:55  SmallOverFllow  阅读(685)  评论(0编辑  收藏  举报