zoukankan      html  css  js  c++  java
  • 多线程(三)多线程同步_基本介绍及mutex互斥体

    同步进制的引入为了解决以下三个主要问题:
    1.控制多个线程之间对共享资源访问,保证共享资源的完整性
    例如:线程A对共享资源进行写入,线程B读取共享资源
    2.确保多个线程之间的动作以指定的次序发生
    例如:线程B以线程A结束为条件进行触发运行
    3.控制共享资源的最大访问数量
    例如:有10个线程需要访问共享资源,同时只允许5个线程访问,那剩余线程就需要放入队列中进行等待

    同步对象分类:
    用户模式下的同步对象:例如关键段等
    优点:速度快
    缺点:不能跨进程,容易引起死锁
    内核模式下的同步对象:例如互斥量,信号量,事件等
    优点:跨进程,安全性高
    缺点:速度慢(需要从当前用户模式下切换到内核模式,需要消耗时间)

    等待函数(同步函数)
    DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
    1.hHandle 对象句柄,如Event(事件)、Mutex(互斥锁)、Semaphore(信号)、Thread(线程)、process(进程)等等内核对象句柄
    2.dwMilliseconds 等待时间间隔,毫秒
    如果dwMilliseconds为非零值,函数会处于等待状态直到hHandle标志的对象成为触发状态,或者时间超时,函数才会返回
    如果dwMilliseconds为0值,不论hHandle标志的对象是否是触发状态,都会立即返回
    如果dwMilliseconds为INFINITE,函数会无限等待直到hHandle标志的对象成为触发状态后才会返回
    返回值:
    WAIT_OBJECT_0 对象是触发状态
    WAIT_TIMEOUT 等待超时,等待时间内对象一直处于未触发状态
    WAIT_ABANDONED 拥有mutex的线程在结束时没有释放对象
    WAIT_FAILED  传递了一个无效句柄,出现错误,可通过GetLastError得到错误代码

    注意:
    1.调用等待函数时,会挂起当前代码所处的线程,直到函数返回时线程才会恢复执行.避免在窗口线程中调用此函数,因为一旦调用,窗口线程被挂起,函数没有返回前整个窗口都是处于假死状态。
    2.当第一个参数是一个mutex对象时,有以下三种情况:
    一.如果mutex对象存在拥有者并且不是当前线程,就会根据第二个参数进行等待.根据返回值如果为WAIT_OBJECT_0代表当前线程获得对象拥有权。如果为WAIT_TIMEOUT代表超时未获得对象拥有权。如果为WAIT_ABANDONED代表拥有mutex的线程在结束时没有释放对象,此时无法获知通过mutex所保护的共享资源是否被破坏.
    二.如果mutex对象不存在拥有者,当前线程将会自动成为新的拥有者,返回WAIT_OBJECT_0
    三.如果mutex对象拥有者是当前线程,也是直接返回WAIT_OBJECT_0
    每次调用WaitForSingleObject,如果返回值为WAIT_OBJECT_0,mutex对象内部的递归计数器会+1,后面一定要使用ReleaseMutex进行释放使递归计数器-1.

    演示:检测某个进程是否结束

     1     //不进行等待,只获取进程是否有信号
     2     //注意:进程启动后是无信号状态,只有当点击关闭按钮进程退出时才会变为有信号状态
     3     DWORD dwCode = WaitForSingleObject(hProcess,0);
     4     switch (dwCode)
     5     {
     6     case WAIT_OBJECT_0: //有信号状态,表示进程己退出
     7         printf("Process is Exit!
    ");
     8         break;
     9     case WAIT_TIMEOUT: //无信号状态,表示进程还在运行中
    10         printf("Process is Running
    ");
    11         break;
    12     case WAIT_FAILED: //错误
    13         {
    14             DWORD dwError = GetLastError();
    15             printf("Fail:%d
    ",dwError);
    16         }
    17     default:
    18         break;
    19     }
    20     CloseHandle(hProcess);
    21 
    22     getchar();
    23     return 0;
    演示1

    mutex互斥对象:
    它是系统内核中的一种数据结构,用于确保一个线程独占一个资源的访问.
    包含三个部分:
    使用计数器    统计有多少个线程在调用用该对象
    线程ID   当前占用这个互斥对象的线程ID.线程ID为0,代表没有被任何线程占用,处于触发状态.反之线程ID不为0,代表被一个线程占用,处于未触发状态
    递归计数器   统计当前占用这个互斥对象的线程占用了该互斥对象的次数

    创建互斥体对象
    HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName)
    1.lpMutexAttributes 指向安全属性指针,通常为NULL
    2.bInitialOwner 初始化互斥对象所有者,通常为FASLE,也就是没有所有者
    3.lpName 指定互斥体对象的名字
    返回值:
    成功返回新互斥体对象句柄,失败返回0
    注意:如果己经有同名的互斥体对象存在,函数将返回己有的互体斥对象句柄,不会再创建新的互斥体对象

    释放互斥体对象
    BOOL ReleaseMutex(HANDLE hMutex)
    1.hMutex 互斥体对象句柄
    返回值:
    成功返回真,失败返回假

    当线程中调用ReleaseMutex时,该函数会判断当前线程ID与互斥体对象的线程ID是否相同?如果相同,互斥体对象的递归计数器会-1,如果不相同不进行任何操作,直接返回FASLE,此时调用GetLastErro,将返回ERROR_NOT_OWNER(试图释放不是调用者拥有的互斥对象)
    每调用一次该函数会使对象的占用计数器-1.如果线程调用等待函数返回WAIT_OBJECT_0不止一次,那ReleaseMutex就得调用相同次数,才能使占用计数器变为0.当占用计数器变为0时,系统内部会把该互斥体对象的线程ID设为0,也就是失去拥有者,此时该互斥对象成为可触发状态.
    注意:如果一个线程是互斥体对象的拥有者,但是没有释放互斥对象就结束了线程,此后系统在检则互斥体对象的线程ID时发现此线程己结束,系统将自动把互斥对象的ID复置为0,并将它的递归计数器复置为0.然后从等待线程中寻找一个线程做为它的新的拥有者,并将递归计数器设置为1,同时这个等待线程变为可调度状态

    使用互斥体对象配合等待函数实现同步的流程:

    假设有一个全局的共享资源,同时有多个线程进行读写。为了保证共享资源的完整性,同一时刻只允许一个线程读写操作

    1.创建一个互斥体对象,第二个参数为0,代表该互斥体对象没有所有者,保存返回的对象句柄供多个线程访问
    2.多个线程内部在访问共享资源前,先调用WaitForSingleObject传入保存的互斥体对象句柄,然后再访问共享资源,最后再ReleaseMutex.

    这样当有一个线程A执行WaitForSingleObject时,会获得互斥体对象所有权,此时互斥体对象的线程ID将被修改为线程A的ID,然后递归计数器为1,使用计数器为1,然后线程A独占访问共享资源。在没有调用ReleaseMutex前,后面调用WaitForSingleObject的线程由于对象是未触发状态,最终都将进入等待状态.直到线程A调用ReleaseMutex使递归计数器-1后变为0,此时系统会把线程ID设为0,交出所有权,该对象又成为触发状态,下一个线程才能获得所有权,独占访问共享资源。

    编写一个Demo用于演示mutex互斥体基本操作

    功能介绍:

    模拟售票系统,有三个售票窗口(三个线程),每隔1秒售出1张票,售票前提示剩余总票数,以及提示售出1张票。当剩余总票售为0时,提示售票窗口关闭(线程结束)

    开始编写代码:

    1. 创建个基于对话框的工程MutexDemo

    2. 添加一个编辑框用于显示售票信息,修改ID为IDC_EDIT_SHOWINFO, 修改属性为不可读.

    3.添加一个按钮用于启动线程,修改ID为IDC_BTN_SELLTICKET

    4. 先定义相关全局变量和线程函数前置声明

    1 int g_nTickNum = 10; //总票数
    2 CEdit* g_editShowInfo; //编辑框控件指针
    3 HANDLE g_hMutex; //互斥体对象句柄
    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 CMutexDemoDlg::OnInitDialog()
     2 {
     3     CDialogEx::OnInitDialog();
     4 
     5     // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
     6     //  执行此操作
     7     SetIcon(m_hIcon, TRUE);            // 设置大图标
     8     SetIcon(m_hIcon, FALSE);        // 设置小图标
     9 
    10     //创建mutex互斥体对象
    11     g_hMutex = CreateMutex(NULL,FALSE,0);
    12     //获取EDIT控件指针,供线程内部访问
    13     g_editShowInfo = (CEdit*)GetDlgItem(IDC_EDIT_SHOWINFO);
    14 
    15 
    16     return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
    17 }
    OnInitDialog

    6.按钮_启动线程事件

    1 //按钮_开始售票
    2 void CMutexDemoDlg::OnBnClickedBtnSellticket()
    3 {
    4     //创建三个售票窗口线程,创建后直接关闭句柄
    5     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_One,NULL,0,NULL));
    6     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Two,NULL,0,NULL));
    7     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Three,NULL,0,NULL));
    8 }
    按钮_启动线程事件

    7.三个售票窗口线程代码

     1 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam)
     2 {
     3     CString strEdit;
     4     CString strNew;
     5     while (true)
     6     {
     7         Sleep(1000);
     8         WaitForSingleObject(g_hMutex,INFINITE);
     9         g_editShowInfo->GetWindowText(strEdit);
    10         if(g_nTickNum > 0)
    11         {
    12             strNew.Format(_T("线程1:剩余票数:%d,售出1张票
    "),g_nTickNum--);        
    13             strEdit += strNew;
    14             if(g_nTickNum == 0)
    15             {
    16                 strEdit += _T("线程1:票己售空,关闭售票窗口
    ");
    17                 g_editShowInfo->SetWindowText(strEdit);
    18                 ReleaseMutex(g_hMutex);
    19                 break;
    20             }
    21             else
    22             {
    23                 g_editShowInfo->SetWindowText(strEdit);
    24                 ReleaseMutex(g_hMutex);
    25             }
    26         }
    27         else
    28         {
    29             strEdit += _T("线程1:票己售空,关闭售票窗口
    ");
    30             g_editShowInfo->SetWindowTextW(strEdit);
    31             ReleaseMutex(g_hMutex);
    32             break;
    33         }
    34     }
    35     return true;
    36 }
    线程_售票窗口1
     1 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam)
     2 {
     3     CString strEdit;
     4     CString strNew;
     5     while (true)
     6     {
     7         Sleep(1000);
     8         WaitForSingleObject(g_hMutex,INFINITE);
     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                 ReleaseMutex(g_hMutex);
    19                 break;
    20             }
    21             else
    22             {
    23                 g_editShowInfo->SetWindowText(strEdit);
    24                 ReleaseMutex(g_hMutex);
    25             }
    26         }
    27         else
    28         {
    29             strEdit += _T("线程2:票己售空,关闭售票窗口
    ");
    30             g_editShowInfo->SetWindowTextW(strEdit);
    31             ReleaseMutex(g_hMutex);
    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         WaitForSingleObject(g_hMutex,INFINITE);
     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                 ReleaseMutex(g_hMutex);
    19                 break;
    20             }
    21             else
    22             {
    23                 g_editShowInfo->SetWindowText(strEdit);
    24                 ReleaseMutex(g_hMutex);
    25             }
    26         }
    27         else
    28         {
    29             strEdit += _T("线程3:票己售空,关闭售票窗口
    ");
    30             g_editShowInfo->SetWindowTextW(strEdit);
    31             ReleaseMutex(g_hMutex);
    32             break;
    33         }
    34     }
    35     return true;
    36 }
    线程_售票窗口3

     8.DestroyWindow添加相应代码

    1 BOOL CMutexDemoDlg::DestroyWindow()
    2 {
    3     //关闭互斥体对象句柄
    4     CloseHandle(g_hMutex);
    5 
    6     return CDialogEx::DestroyWindow();
    7 }
    DestroyWindow

    最终演示效果如下:

  • 相关阅读:
    linux安装nodejs
    Ubuntu下配置TFTP服务以及 android下使用TFTP
    笔记-《数据通信与网络教程》-第一章
    X86汇编基础-《Linux内核分析》云课堂笔记
    文章点击量排行TOP100-IBM power8算法挑战赛第三期
    LeetCode:Climbing Stairs
    LeetCode:Search for a Range
    LeetCode:Longest Substring Without Repeating Characters
    LeetCode:Linked List Cycle II
    LeetCode:Merge Sorted Array
  • 原文地址:https://www.cnblogs.com/fzxiaoyi/p/12068465.html
Copyright © 2011-2022 走看看