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;
    }

  • 相关阅读:
    ThinkPHP 3.2.2 实现持久登录 ( 记住我 )
    Java实现 LeetCode 20 有效的括号
    Java实现 LeetCode 20 有效的括号
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 19删除链表的倒数第N个节点
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 18 四数之和
    Java实现 LeetCode 17 电话号码的字母组合
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468339.html
Copyright © 2011-2022 走看看