• Windows线程控制


    多线程无疑带来了很多方便,提高了很多开发效率,但是同时也带来了很多问题。

    举个栗子:

    DWORD WINAPI ThreadProc(LPVOID lPParameter);
    
    int m = 0;
    int n = 0;
    
    int main(int argc, TCHAR* argv[], TCHAR* envp[])  
    {
        
        HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
        
        int i = 0;
        for (i=0;i<100000;i++)
        {
            m++;
            n++;
        }
        Sleep(1);
    
        cout<<"M:"<<m<<endl;
        cout<<"N:"<<n<<endl;    
    
    
        return 0;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lPParameter)
    {
        int j = 0;
        for (j=0;j<100000;j++)
        {
            m++;
            n++;
        }
    return 0; }

     这段程序的输出结果是多少呢?

     按照常理来说不难得出结论:m与n都是20000

     可是真正运行结果呢?

     

     结果令人大吃一惊,而且每次结果都不相同!为什么会出现这种情况呢?

     A线程在访问全局资源的时候并不能控制B线程对全局资源的访问,A取出内存数据放到寄存器运算时,B也取出内存数据放到寄存  器运算,那么有可能造成重复运算!也就是说A正在运算的时候,还没把运算结果放回来,B就也行运算了。所以最终结果比  200000小并且m与n都不相同。

     为了控制线程不要紊乱要按照我们本意的协调进行,不可避免的就要用到线程控制(互斥/同步)技术!

     下面说说线程控制的基本方法

     1.EVENT(事件)

     2.Critical Section(临界区)

     3.Mutex (互斥体)

     4.Semaphore (信号量)

     0X01

     EVENT(事件)

     用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状  态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一  直保持  为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线  程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为 CEvent.。CEvent的构  造函数默认创  建一个自动重置的事件,而且处于未激发状态。改变事件的状态:SetEvent,ResetEvent。

     我们看看如何运用事件来解决我们的实际问题:

    DWORD WINAPI ThreadProc(LPVOID lPParameter);
    
    int m = 0;
    int n = 0;
    
    int main(int argc, TCHAR* argv[], TCHAR* envp[])  
    {
    
        HANDLE hEvent = NULL;
    
        hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);  //FALSE    ×Ô¶¯   TRUE  ÊÖ¶¯   FALSEÊÇ×Ô¶¯    
        
        HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,hEvent,0,NULL);
    
        WaitForSingleObject(hEvent,INFINITE); 
        
        int i = 0;
        for (i=0;i<100000;i++)
        {
            m++;
            n++;
        }
        Sleep(1);
    
        cout<<"M:"<<m<<endl;
        cout<<"N:"<<n<<endl;    
    
    
        return 0;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lPParameter)
    {
        HANDLE hEvent = NULL;
        
        hEvent = (HANDLE)lPParameter;
        
    
        int j = 0;
        for (j=0;j<100000;j++)
        {
            m++;
            n++;
        }
    
        SetEvent(hEvent);
        
        return 0;
    }

     我们看看结果对不对

     

      结果正确!

      0X02 

      Critical Section(临界区)

      CRITICAL_SECTION是最快的。其他内核锁(事件、互斥体),每进一次内核,都需要上千个CPU周期。
      使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可     能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如     何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就   是:Critical Section不是一个核心对象无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放     临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。

      

    DWORD WINAPI ThreadProc(LPVOID lPParameter);
    
    int m = 0;
    int n = 0;
    
    CRITICAL_SECTION cs;  
    int main(int argc, TCHAR* argv[], TCHAR* envp[])  
    {
        InitializeCriticalSection(&cs);   
        
        HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
        
        int i = 0;
        EnterCriticalSection(&cs);
        for (i=0;i<100000;i++)
        {
            m++;
            n++;
        }
        LeaveCriticalSection(&cs);
    
        Sleep(1);
    
        cout<<"M:"<<m<<endl;
        cout<<"N:"<<n<<endl;    
    
        DeleteCriticalSection(&cs);  //死锁  
    
    
        return 0;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lPParameter)
    {
    
        int j = 0;
        EnterCriticalSection(&cs);
        for (j=0;j<100000;j++)
        {
            m++;
            n++;
        }
        LeaveCriticalSection(&cs);
    
        return 0;
    }

     同样这样也能获得正确结果!

     关于临界区的使用也就是四步走的问题:初始化临界区-->进入临界区-->离开临界区-->销毁临界区。牢记这四部就不会出现  问题了。需要注意的一点是:临界区与递归要小心使用,在临界区递归会造成死锁

     0X03  

     Mutex (互斥体)

     互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象 (Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定 TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此 Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个 Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

    HANDLE hMutex = NULL;
      
    int main(int argc, TCHAR* argv[], TCHAR* envp[])  
    {
        hMutex = CreateMutex(NULL,TRUE,NULL);
        
        HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
        
        int i = 0;
    
        for (i=0;i<100000;i++)
        {
            m++;
            n++;
        }
        
        ReleaseMutex(hMutex);
        
        Sleep(1);
    
        cout<<"M:"<<m<<endl;
        cout<<"N:"<<n<<endl;    
    
        return 0;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lPParameter)
    {
    
        WaitForSingleObject(hMutex,INFINITE);
    
        int j = 0;
        for (j=0;j<100000;j++)
        {
            m++;
            n++;
        }
    
        return 0;
    }

      0X04

      Semaphore (信号量)

      信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。Win32函数 CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数   加1,当时不会超过初始设定的资源总数。 

    HANDLE hSemaphore = NULL;
    
    int main(int argc, TCHAR* argv[], TCHAR* envp[])  
    {
        hSemaphore = CreateSemaphore(NULL,0,1,NULL);
        
        HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);
        
    
        int i = 0;
        
        for (i=0;i<100000;i++)
        {
            m++;
            n++;
        }
    
        ReleaseSemaphore(hSemaphore,1,NULL);
        
        Sleep(1);
        
        cout<<"M:"<<m<<endl;
        cout<<"N:"<<n<<endl;    
    
        CloseHandle(hSemaphore);
        
        return 0;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lPParameter)
    {
    
        WaitForSingleObject(hSemaphore,INFINITE);
        
        int j = 0;
        for (j=0;j<100000;j++)
        {
            m++;
            n++;
        }
    
        
        return 0;
    }
  • 相关阅读:
    bootstrap添加多个模态对话框支持
    突然想写书
    几个常用的内存、CPU飙高 分析工具
    steeltoe学习
    thrift简单使用
    记一次线上Mysql数据库 宕机
    .NetCore 开发生产环境项目前的思考&&踩坑
    SOA(面向服务架构)——踩坑后反思:这样值得吗?
    ProtoBuf与Newtonsoft序列化反序列化性能对比
    SimpleInjector 简单使用
  • 原文地址:https://www.cnblogs.com/zibility/p/5658542.html
Copyright © 2020-2023  润新知