zoukankan      html  css  js  c++  java
  • 【转】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进行介绍和解析,敬请关注。文中如有谬误,还望不吝赐教。

  • 相关阅读:
    noi 2011 noi嘉年华 动态规划
    最小乘积生成树
    noi 2009 二叉查找树 动态规划
    noi 2010 超级钢琴 划分树
    noi 2011 阿狸的打字机 AC自动机
    noi 2009 变换序列 贪心
    poj 3659 Cell Phone Network 动态规划
    noi 2010 航空管制 贪心
    IDEA14下配置SVN
    在SpringMVC框架下建立Web项目时web.xml到底该写些什么呢?
  • 原文地址:https://www.cnblogs.com/renyuan/p/6000465.html
Copyright © 2011-2022 走看看