• 【转】windows平台多线程同步之Mutex的应用


    • 线程组成: 
      1. 线程的内核对象,操作系统用来管理该线程的数据结构。
      2. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

      操作系统为每一个运行线程安排一定的CPU时间 —— 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,多个线程不断地切换运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。 
      单cpu计算机一个时间只能运行一个线程,如果计算机拥有多个CPU,线程就能真正意义上同时运行了。 
      windows平台下,创建线程可以使用windows api 函数CreateThread来实现,函数声明是:

    WINBASEAPI
    HANDLE
    WINAPI
    CreateThread(
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        DWORD dwStackSize,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        DWORD dwCreationFlags,
        LPDWORD lpThreadId
        );

    参数说明:

    lpThreadAttributes 线程安全性,使用缺省安全性,一般缺省null
    dwStackSize 堆栈大小,0为缺省大小
    lpStartAddress 线程要执行的函数指针,即入口函数
    lpParameter 线程参数
    dwCreationFlags 线程标记,如为0,则创建后立即运行
    lpThreadId LPDWORD为返回值类型,一般传递地址去接收线程的标识符,一般设为null

    因为要使用windows api函数,所以包含:

    #include <windows.h>

    另外需要标准输入输出函数,所以包含:

    #include <iostream.h>

    - 问题引出   以多个售票窗口卖同一张火车票为例,定义一个全局的票数tickets,用两个线程来执行卖票,两个线程访问同一个变量tickets,先看一个不正确的写法:

    //问题程序
    
    #include <windows.h>
    #include <iostream.h>
    
    DWORD WINAPI Fun1Proc(
      LPVOID lpParameter   
    );
    
    DWORD WINAPI Fun2Proc(
      LPVOID lpParameter   
    );
    
    
    int tickets=100;
    
    void main()
    {
        HANDLE hThread1;
        HANDLE hThread2;
        hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
        hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
    
        CloseHandle(hThread1);
        CloseHandle(hThread2);
    
        system("pause");
    }
    
    DWORD WINAPI Fun1Proc(
      LPVOID lpParameter   
    )
    {
        while(TRUE)
        {
            if(tickets>0)
            {
                Sleep(1);//假定为卖票需要花费的时间
                cout<<"thread1 sell ticket : "<<tickets--<<endl;
            }
            else
                break;  
        }
        return 0;
    }
    
    DWORD WINAPI Fun2Proc(
      LPVOID lpParameter   // thread data
    )
    {   
        while(TRUE)
        {
            if(tickets>0)
            {
                Sleep(1);
                cout<<"thread2 sell ticket : "<<tickets--<<endl;
            }
            else
                break;  
        }
        return 0;
    }

      线程中sleep(1);表名该线程放弃执行的权利,操作系统会选择另外的线程进行执行。所以执行结果是:

    执行结果


      可以看见程序不是按照预期的效果执行的,tickets的改变是混乱的。所以两个线程访问同一块资源时,需要考虑线程同步问题,即其中一个线程操作改资源时,其他线程不能访问该资源,只能等待,该线程执行结束之后,其他线程才能对该资源进行访问。 
      一般采用互斥对象来实现线程的同步。

    • 互斥对象 
      特征: 
        互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 
        互斥对象包含一个使用数量,一个线程ID和一个计数器。 
        ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。 
        采用互斥对象进行多线程同步的正确例子如下:
    #include <windows.h>
    #include <iostream.h>
    
    DWORD WINAPI Fun1Proc(
      LPVOID lpParameter   // thread data
    );
    
    DWORD WINAPI Fun2Proc(
      LPVOID lpParameter   // thread data
    );
    int index=0;
    int tickets=100;
    HANDLE hMutex;
    void main()
    {
        HANDLE hThread1;
        HANDLE hThread2;
        hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
        hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
        CloseHandle(hThread1);
        CloseHandle(hThread2);
    
        //创建一个匿名的互斥对象,且为有信号状态,
        hMutex=CreateMutex(NULL,FALSE,NULL);
    
        system("pause");
    
    }
    
    DWORD WINAPI Fun1Proc(
      LPVOID lpParameter   // thread data
    )
    {
        while(TRUE)
        {
            //等待互斥对象的信号,INFINITE表示一直等待,对之后的代码进行保护
            WaitForSingleObject(hMutex,INFINITE);
            if(tickets>0)
            {
                Sleep(1);
                cout<<"thread1 sell ticket : "<<tickets--<<endl;
            }
            else
                break;
            //释放指定互斥对象的所有权,操作系统将互斥对象的线程id被置为0,互斥对象变为已通知状态,线程2就能请求到互斥对象
            ReleaseMutex(hMutex);
        }
        return 0;
    }
    
    DWORD WINAPI Fun2Proc(
      LPVOID lpParameter   // thread data
    )
    {
        while(TRUE)
        {
            WaitForSingleObject(hMutex,INFINITE);
            if(tickets>0)
            {
                Sleep(1);
                cout<<"thread2 sell ticket : "<<tickets--<<endl;
            }
            else
                break;
            ReleaseMutex(hMutex);
        }
        return 0;
    }
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    执行结果


      通过测试可知,以上互斥对象的引入可以很好的解决线程间访问资源同步的问题。关于互斥对象,还有以下几个问题需要说明。

    • 互斥对象的释放问题 
        如果main中
        hMutex=CreateMutex(NULL,TRUE,NULL);

      子线程中:

        while(TRUE)
        {
            ReleaseMutex(hMutex);//无效
            //等待互斥对象的信号,INFINITE表示一直等待,对之后的代码进行保护
            WaitForSingleObject(hMutex,INFINITE);
        }

      如果CreateMutex第二个参数为true,则表示主线程拥有该互斥对象,操作系统将互斥对象的线程id设为主线程的线程id,如果主线程不释放,则子线程会一直等待,此时子线程也没有权利进行释放,所以使用互斥对象的原则是:谁拥有互斥对象,谁释放互斥对象。

      另外,如果main中

        hMutex=CreateMutex(NULL,TRUE,NULL);

      并且再次请求互斥对象:

        WaitForSingleObject(hMutex,INFINITE);

      并调用一次释放互斥对象:

        ReleaseMutex(hMutex);

      此时子线程依然是等待状态,得不到互斥对象的使用权,原因是: 
      CreateMutex(NULL,TRUE,NULL)由于第二个参数为true,主线程拥有互斥对象的使用权,互斥对象内部计数器加1,再次调用WaitForSingleObject请求互斥对象时,内部计数器又加1,计数器是记录线程拥有互斥对象的次数,而只释放ReleaseMutex了一次,互斥对象依然被占用,所以子线程得不到使用权。 
      因此正确的写法是:

        hMutex=CreateMutex(NULL,TRUE,NULL);
        WaitForSingleObject(hMutex,INFINITE);
        ReleaseMutex(hMutex);
        ReleaseMutex(hMutex);

      如果多次请求互斥对象,就应该多次释放互斥对象。

      再看这样一种情况,线程中没有释放互斥对象的拥有权:

        DWORD WINAPI Fun1Proc(LPVOID lpParameter)
        {
            waitforsingleobject(hmutex,infinite);
            cout<<"thread1 is running"<<endl;
            return 0;
        }
        DWORD WINAPI Fun2Proc(LPVOID lpParameter)
        {
            waitforsingleobject(hmutex,infinite);
            cout<<"thread2 is running"<<endl;
            return 0;
        }

      此时执行依然能够得到输出:

        "thread1 is running
        "thread2 is running

      这是因为:如果线程退出时没有释放互斥对象,操作系统在销毁线程时会自动将线程占用的互斥对象的信息清除,计数器归零,这样其他线程(thread2 )就能申请到互斥对象使用权。

    • 创建命名互斥对象
        hMutex=CreateMutex(NULL,TRUE,"myApp");
        if(hMutex)
        {
            if(ERROR_ALREADY_EXISTS==GetLastError())
            {
                cout<<"已经有一个相同应用程序在运行!"<<endl;
                return;
            }
        }

      命名互斥对象的一种应用是:通过命名互斥对象,可以保证当前只有一个应用程序实例在运行。

      以上是关于windows平台下多线程同步相关的互斥对象的使用问题,之后将对线程同步的事件对象Event进行介绍和解析,敬请关注。文中如有谬误,还望不吝赐教。

  • 相关阅读:
    C#撸了个批量转换Word、Excel、PPT为PDF的软件 pdfcvt.com
    关于《图解国富论》若干问题的思考《六》
    关于《图解国富论》若干问题的思考《五》
    关于《图解国富论》若干问题的思考《四》
    关于《图解国富论》若干问题的思考《三》
    关于《图解国富论》若干问题的思考《二》
    关于《图解国富论》若干问题的思考《一》
    忆秦娥·娄山关
    《动物精神》笔记
    《非理性繁荣》笔记
  • 原文地址:https://www.cnblogs.com/renyuan/p/6000465.html
Copyright © 2020-2023  润新知