zoukankan      html  css  js  c++  java
  • 多线程(四)多线程同步_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张票
    "),g_nTickNum--);        
    14             strEdit += strNew;
    15             if(g_nTickNum == 0)
    16             {
    17                 strEdit += _T("线程1:票己售空,关闭售票窗口
    ");
    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:票己售空,关闭售票窗口
    ");
    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张票
    "),g_nTickNum--);        
    13             strEdit += strNew;
    14             if(g_nTickNum == 0)
    15             {
    16                 strEdit += _T("线程2:票己售空,关闭售票窗口
    ");
    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:票己售空,关闭售票窗口
    ");
    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张票
    "),g_nTickNum--);        
    13             strEdit += strNew;
    14             if(g_nTickNum == 0)
    15             {
    16                 strEdit += _T("线程3:票己售空,关闭售票窗口
    ");
    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:票己售空,关闭售票窗口
    ");
    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

    最终演示效果如下:

  • 相关阅读:
    拖拽模块move2
    拖拽模块move1
    String类和StringBuilder
    你真的会二分查找吗
    C++中关于new及动态内存分配的思考
    【转】Github 上传代码
    HDU4801·二阶魔方
    POJ2676,HDU4069解决数独的两种实现:DFS、DLX
    读书笔记
    SpringBoot-------实现多数据源Demo
  • 原文地址:https://www.cnblogs.com/fzxiaoyi/p/12072551.html
Copyright © 2011-2022 走看看