zoukankan      html  css  js  c++  java
  • 利用互斥对象实现线程同步的实例说明

    多线程编程中,如果我们稍有不注意,很容易出现一些意想不到的结果,主要原因就是多线程的同步处理;我们需要保证多个线程在共同运行时,进行对应资源的同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源,这里将使用互斥对象实现线程同步;


    “认识多线程”这篇文章中有个实例2,该实例给出了两个线程共同完成g_Index的计数,并打印对应的线程标识;最终的运行结果存在不符合预期的地方;原因就是没有对g_Index这个共享资源进行保护;本节将利用互斥对象实现线程同步,解决这个问题;


    互斥对象


    互斥对象(Mutex)属于内核对象,它能确保线程拥有对单个资源的互斥范围权利,即线程A正在拥有资源Z,线程B恰好也要使用资源Z,则线程B会等到线程A使用完资源后,才去使用资源Z;

    互斥对象包含一个使用数量,一个线程ID,一个计数器。其中ID用于标识哪个线程当前拥有这个互斥对象,计数器用于标识该线程拥有互斥对象的次数

    这里会用到CreateMutex、ReleaseMutex、WaitForSingleObject这三个函数,利用这三个函数可以完成线程同步,下面介绍这几个函数;


    CreateMutex


    HANDLE WINAPI CreateMutex(
      __in_opt  LPSECURITY_ATTRIBUTES lpMutexAttributes,
      __in      BOOL bInitialOwner,
      __in_opt  LPCTSTR lpName
    );
    功能:

    创建或者打开一个已经命名的或者匿名的互斥对象

    参数:

    lpMutexAttributes

    一个指向LPSECURITY_ATTRIBUTES结构的指针,我们一般设置该值为NULL,表示让互斥对象使用默认的安全性;

    bInitialOwner

    指定互斥对象初始的拥有者,TRUE:创建者对象的线程获得该对象的使用权,内部计数器加1,否则,该线程不获得互斥对象的所有权;

    lpName

    指定互斥对象的名称,如果此参数为NULL,表示创建一个匿名的互斥对象;


    ReleaseMutex


    当我们对共享资源访问后,我们需要释放该对象的所有权,让这个互斥对象处于已通知状态;此时可以调用ReleaseMutex函数,调用该函数相当于互斥对象的计数器减1,其函数声明如下:

    BOOL WINAPI ReleaseMutex(__in  HANDLE hMutex);

    参数:

    hMutex 需要释放的互斥对象的句柄

    当前线程调用了该函数,则其他线程就有机会获得该对象的所有权,从而获得共享资源的访问;

    WaitForSingleObject


    线程若要获得互斥对象的所有权,则必须主动发出请求才能获得该互斥对象的所有权;获得对象的所有权,可以调用WaitForSingleObject函数来实现,若成功获得互斥对象的执行权,该互斥对象的计数器则增加1,其函数声明如下:

    DWORD WINAPI WaitForSingleObject(
      __in  HANDLE hHandle,
      __in  DWORD dwMilliseconds
    );

    参数:

    hHandle

    所请求互斥对象的句柄;一旦互斥对象处于有信号状态,该函数就返回。如果该互斥对象一致处于无状态对象,函数会一致等待,该线程暂停执行

    dwMilliseconds

    指定等待的时间间隔,以毫秒为单位。

    如果该参数为0表示测试该对象的状态就立即返回,不关心线程是否获得所有权,相当于没有进行线程同步处理

    如果该参数为INFINITE表示函数会一直等待,直到等待的对象处于有信号状态才会返回,这里能保证资源的独占性;


    WaitForSingleObject函数返回的两种情况,其返回值有三个WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED:

    1)指定的对象变成有信号状态

    2)指定的等待时间间隔已过;


    实例1


    现在利用互斥对象实现符合线程安全的计数器打印,其具体代码如下:

    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    //#include <afxmt.h>
    using namespace std;
    
    int g_nIndex = 0;
    const int nMaxCnt = 50;
    
    //CCriticalSection csIndexLock;
    HANDLE hMetex = NULL;
    
    //新线程的起始地址
    DWORD WINAPI  Thread1Proc(LPVOID lpParameter)
    {  
        while (TRUE)
        {
            //等待线程的执行权,无限期等待
            //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行
            WaitForSingleObject(hMetex,INFINITE);
            if (g_nIndex++ < nMaxCnt)
            {
                cout << "Index = "<< g_nIndex << " ";
                cout << "Thread1 is ruuning" << endl;
            }
            else
            {
                break;
            }
            //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体
            ReleaseMutex(hMetex);
        }
        return 0;
    }
    
    //新线程的起始地址
    DWORD WINAPI  Thread2Proc(LPVOID lpParameter)
    {  
        while (TRUE)
        {
            //等待线程的执行权,无限期等待
            //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行,否则不执行
            WaitForSingleObject(hMetex,INFINITE);
            if (g_nIndex++ < nMaxCnt)
            {
                cout << "Index = " << g_nIndex << " ";
                cout << "Thread2 is ruuning" << endl;
            }
            else
            {
                break;
            }
            //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体
            ReleaseMutex(hMetex);
        }
        return 0;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE hThread1 = NULL;
        HANDLE hThread2 = NULL;
        //创建互斥对象,主线程没有执行权
        hMetex = CreateMutex(NULL, FALSE, NULL);
        //创建新的线程
        hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
        hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
    
        //无须对新线程设置优先级等操作,关闭之
        //良好的编码习惯
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        //主线程放弃执行全力
        Sleep(5000);
        return 0;
    }
    运行结果:


    实例2


    如果在我们在创建互斥对象时,将CreateMetux的第二个对象设置为TURE,即

    hMetex = CreateMutex(NULL, TRUE, NULL);
    运行结果:



    分析:

    wMain主线程获得这个互斥对象的执行权,并且没有释放这个互斥对象,其他线程在WaitForSingleObject时,该互斥对象都属于未通知状态,因此Thread1和Thread2都不会执行;


    实例3


    在思路2的基础上,若我们在线程1和线程2的WaitForSingleObject函数前面,增加ReleaseMutex(hMutex)语句,执行情况和实例2一致;

    第一点说明:

    对互斥对象来说,它是唯一与线程相关的内核对象;当主线程拥有互斥对象时,操作系统将互斥对象的线程ID设置为主线程的ID

    线程1在调用ReleaseMutex函数时,操作系统会判断当前线程的线程ID和互斥对象中的线程ID是否相等,只有相等才能正在释放;由于主线没有释放,互斥对象的线程ID是主线程的ID,导致其他线程没有执行的机会。对于互斥对象来说,必须是谁拥有谁完成释放


    实例4


    对于如下的主线程函数来说,线程1和线程2想获得执行权,需要释放两次,代码如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE hThread1 = NULL;
        HANDLE hThread2 = NULL;
        //创建互斥对象,主线程有执行权
        hMetex = CreateMutex(NULL, TRUE, NULL);
        WaitForSingleObject(hMetex,INFINITE);
        //创建新的线程
        hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
        hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
    
        //无须对新线程设置优先级等操作,关闭之
        //良好的编码习惯
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        //CreateMutex创建时,主线程获得所有权,计数器+1
        ReleaseMutex(hMetex);
        //同一个线程内调用WaitForSingleObject,即此刻互斥对象处于未通知状态
        //调用线程也会获得执行权,计数器再+1
        ReleaseMutex(hMetex);
        //主线程放弃执行全力
        Sleep(5000);
        return 0;
    }

    第二点说明:

    我们在调用WaitForSingleObject函数时,若请求的线程ID和互斥对象内部的线程ID是相同的,即使此时此互斥对象处于未通知状态,该线程还可以请求到该回车对象的所有权,计数器则会再加1;所以实例4中需要释放互斥对象两次才行;


    实例5:


    第三点说明:

    当一个线程已经终止时(比如线程return),操作系统自动会帮助我们将互斥对象的线程ID设置为0,并且将引用计数器设置为0;

    //新线程的起始地址
    DWORD WINAPI  Thread1Proc(LPVOID lpParameter)
    {  
        //等待线程的执行权,无限期等待
        //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行
        WaitForSingleObject(hMetex,INFINITE);
        cout << "Thread1 is ruuning" << endl;
        //线程结束,自动释放互斥变量,使其处于已通知状态
        return 0;
    }
    
    //新线程的起始地址
    DWORD WINAPI  Thread2Proc(LPVOID lpParameter)
    {  
        WaitForSingleObject(hMetex,INFINITE);
        cout << "Thread2 is ruuning" << endl;
        //线程结束,自动释放互斥变量,使其处于已通知状态
        return 0;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE hThread1 = NULL;
        HANDLE hThread2 = NULL;
        //创建互斥对象,主线程没有执行权
        hMetex = CreateMutex(NULL, FALSE, NULL);
        //创建新的线程
        hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);
        hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);
    
        //无须对新线程设置优先级等操作,关闭之
        //良好的编码习惯
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        //主线程放弃执行权力
        Sleep(5000);
        return 0;
    }

  • 相关阅读:
    如何在服务器上添加本地驱动器
    JQ查找到带有某个字符,并起类名,然后替换这个某个字符
    使用IWMS的网站打开显示“未能加载文件或程序集”,解决方案
    表头固顶结构
    在页面中有overflow-y:auto属性的div,当出现滚动条,点击返回顶部按钮,内容回这个div最顶部
    在文档页面整个区域出现导航随内容滚动高亮显示效果
    在文档页面局部出现导航随内容滚动高亮显示效果
    在Hmtl页面中只让其中单独的一个div隐藏滚动条但是仍可滚动浏览下边的内容
    GC参考手册 —— GC 调优(工具篇)
    GC参考手册 —— GC 算法(实现篇)
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468339.html
Copyright © 2011-2022 走看看