• Win32编程day15 学习笔记


    一 线程局部存储 Thread Local Storage

      1 由于多个线程使用同一个变量,各个线程
        都对变量进行操作,那么变量的值会被不同
        线程操作覆盖。
            
          通常   变量A   <-- 线程A
                         <-- 线程B
                    
          TLS    变量A   <-- 线程A
                 变量A   <-- 线程B
                
       2 TLS的使用
         2.1 使用关键字 __declspec(thread)
            __declspec(thread) CHAR * g_pszText2 = NULL;
         2.2 TLS相关API
           2.2.1 创建TLS索引
             DWORD TlsAlloc(VOID)
             返回一个TLS索引号
           2.2.2 设置值

             BOOL TlsSetValue(
             DWORD dwTlsIndex, //TLS索引
             LPVOID lpTlsValue //保存的值
             );

           2.2.3 获取值

             LPVOID TlsGetValue(
              DWORD dwTlsIndex  //TLS索引
             );

             返回存放在索引内的值
           2.2.4 释放

             BOOL TlsFree(
              DWORD dwTlsIndex   //TLS索引
             );
    View Code
    // ThreadTls.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "stdlib.h"
    #include "windows.h"
    
    CHAR * g_pszText   = NULL;
    DWORD  g_nTlsIndex = 0;
    
    void Print( )
    {
        printf( "g_pszText: %s\n", g_pszText );
        //从TLS索引中获取值
        CHAR * pszText = (CHAR *)
            TlsGetValue( g_nTlsIndex );
        printf( "TLS: %s\n", pszText );
    }
    
    DWORD WINAPI PrintProc( LPVOID pParam )
    {
        CHAR * pszText = (CHAR *)pParam;
        g_pszText = (CHAR *)malloc( 100 );
        strcpy( g_pszText, pszText );
        //将值保存到TLS索引当中
        TlsSetValue( g_nTlsIndex, g_pszText );
    
        while( 1 )
        {
            Print( );
            Sleep( 1000 );
        }
        return 0;
    }
    
    void Create( )
    {
        HANDLE hThread   = NULL;
        DWORD  nThreadID = 0;
    
        CHAR szText1[] = "ThreadProc 1----------";
        hThread = CreateThread( NULL, 0,
            PrintProc, szText1, 0, &nThreadID );
    
        CHAR szText2[] = "-----ThreadProc 2-----";
        hThread = CreateThread( NULL, 0,
            PrintProc, szText2, 0, &nThreadID );
    
        WaitForSingleObject( hThread, INFINITE );
    }
    
    int main(int argc, char* argv[])
    {    //创建TLS索引号
        g_nTlsIndex = TlsAlloc( );
        //创建线程
        Create( );
        //释放索引
        TlsFree( g_nTlsIndex );
        return 0;
    }

    二 线程同步

      1 多线程的问题
       
        A停止 -> B开始 -〉B停止 -> A开始
       
         当线程停止会保存寄存器的状态。
         当线程开始会恢复寄存器的状态。
        
        AB线程都使用printf的问题:
        A线程调用printf时,printf正在输出
        当中,A挂起,B执行,B线程也调用printf输出B的数据,画面会出现A的数据输出1部分,然后是B的数据;
        B挂起,A执行, A继续输出自己的数据.
        所以,由于多线程的切换,产生数据混乱.
        
      2 问题的解决 - 同步机制
        2.1 原子锁
        2.2 临界区
        2.3 事件
        2.4 互斥
        2.5 信号量
        2.6 可等候定时器
       
      3 等候多个内核对象事件

        DWORD WaitForMultipleObjects(
         DWORD nCount,//句柄的数量
         CONST HANDLE *lpHandles,//句柄数组
         BOOL fWaitAll, //等候方式
        DWORD dwMilliseconds );//等候时间

        等候方式fWaitAll:
           TRUE - 每个句柄都有事件,解除阻塞
           FALSE - 其中一个句柄有事件,解除阻塞

    三 原子锁
     
      1 g_nValue++执行
        线程A通过寄存器完成加法运算,假设g_nValue正在加到10000时,线程切换到B,A的寄存器中保存10000数字,B从10000开始加数据,当B加到15000时,线程切换到A,A恢复寄存器的值,A会继续从10000开始累加,就将B完成5000的加法覆盖.
       
      2 原子锁
        执行单个指令时,锁定操作,不允许其他线程访问.
       
      3 用法
        InterlockedIncrement ++运算
        InterlockedDecrement --运算
        InterlockedCompareExchange ?运算

    View Code
    // InterLock.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "windows.h"
    
    LONG g_nValue1 = 0;
    LONG g_nValue2 = 0;
    
    DWORD WINAPI InterProc1( LPVOID pParam )
    {
        for( int nIndex=0; nIndex<10000000; nIndex++ )
        {    //普通++
            g_nValue1++;
        }
        return 0;
    }
    DWORD WINAPI InterProc2( LPVOID pParam )
    {
        for( int nIndex=0; nIndex<10000000; nIndex++ )
        {    //原子锁++(lock)
            InterlockedIncrement( &g_nValue2 );
        }
        return 0;
    }
    
    void Create( )
    {
        DWORD  nThreadID  = 0;
        HANDLE hThread[4] = { NULL };
        hThread[0] = CreateThread( NULL, 0,
            InterProc1, NULL, 0, &nThreadID );
        
        hThread[1] = CreateThread( NULL, 0,
            InterProc1, NULL, 0, &nThreadID );
    
        hThread[2] = CreateThread( NULL, 0,
            InterProc2, NULL, 0, &nThreadID );
        
        hThread[3] = CreateThread( NULL, 0,
            InterProc2, NULL, 0, &nThreadID );
    
        WaitForMultipleObjects( 4, hThread, 
            TRUE, INFINITE );
    
        printf( "Value1=%d  Value2=%d\n", 
            g_nValue1, g_nValue2 );
    }
    
    int main(int argc, char* argv[])
    {
        Create( );
        return 0;
    }

    四 临界区
     
      1 临界区作用
        线程在执行代码时,将代码锁定,不允许其他线程执行,只有该线程离开后,其他线程才能使用这些代码
       
      2 临界区的使用
        2.1 初始化临界区

           VOID InitializeCriticalSection(
           LPCRITICAL_SECTION lpCriticalSection //临界区结构地址
           );

        2.2 临界区加锁

           VOID EnterCriticalSection(
                  LPCRITICAL_SECTION lpCriticalSection   // pointer to critical 
              //临界区
                );

        2.3 临界区解锁

           VOID LeaveCriticalSection(
           LPCRITICAL_SECTION lpCriticalSection 
              // 临界区 
           );

        2.4 释放临界区

           VOID DeleteCriticalSection(
           LPCRITICAL_SECTION lpCriticalSection   
                   //临界区
           );

      3 和原子锁相比
        原子锁是一条语句
        临界区可以完成多条语句的锁定.

    View Code
    // CriticalSection.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "conio.h"
    #include "windows.h"
    
    CRITICAL_SECTION g_cs = { 0 };
    LONG nValue = 0;
    
    void Print( )
    {    //进入临界区 - 加锁
        EnterCriticalSection( &g_cs );
    
        nValue++;
        printf( "Long long long.......%d\n", nValue );
        
        //离开临界区 - 解锁
        LeaveCriticalSection( &g_cs );
    }
    
    DWORD WINAPI PrintProc( LPVOID pParam )
    {
        while( 1 )
        {
            Print( );
            Sleep( 100 );
        }
        return 0;
    }
    
    void Create( )
    {
        DWORD nThreadID = 0;
        HANDLE hThread[2] = { 0 };
        hThread[0] = CreateThread( NULL, 0,
            PrintProc, NULL, 0, &nThreadID );
        hThread[1] = CreateThread( NULL, 0,
            PrintProc, NULL, 0, &nThreadID );
        
        getch( );
    }
    
    int main(int argc, char* argv[])
    {    //初始化临界区
        InitializeCriticalSection( &g_cs );
        Create( );
        //删除临界区
        DeleteCriticalSection( &g_cs );
        return 0;
    } 

    五 事件

      1 事件
        通知的作用,当收到事件时,线程可以执行. 否则,线程将等候事件发生.
       
      2 事件的用法
        2.1 创建事件

                HANDLE CreateEvent(
           LPSECURITY_ATTRIBUTES lpEventAttributes,//安全属性
           BOOL bManualReset,//重置方式
           BOOL bInitialState, //初始化状态
           LPCTSTR lpName //名称
                  );

        返回创建好的事件句柄
        bManualReset - 事件重置方式, TRUE手动和FALSE自动重置. 如果为FALSE,系统在等候到事件后,会自动将事件重置为无信号状态. 如果为TRUE,我们必须自己使用ResetEvent重置状态.
          bInitialState - 初始化状态, TRUE为有信号,FALSE无信号.
       2.2 等候事件
          WaitForSingleObject/
          WaitForMultipleObjects
        2.3 触发事件

          BOOL SetEvent(
          HANDLE hEvent //事件句柄
          );

        2.4 关闭事件
          CloseHandle
        2.5 重置事件

          BOOL ResetEvent(
           HANDLE hEvent //事件句柄
          );

        2.6 其他函数
          OpenEvent
          PulseEvent

    View Code
    // Event.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "conio.h"
    #include "windows.h"
    
    HANDLE g_hEvent = NULL;
    HANDLE g_hEvent2= NULL;
    
    DWORD WINAPI ThreadSend( LPVOID pParam )
    {
        while( 1 )
        {    //触发事件
            SetEvent( g_hEvent );
            Sleep( 500 );
            SetEvent( g_hEvent2 );
            Sleep( 500 );
        }
        return 0;
    }
    DWORD WINAPI ThreadRecv( LPVOID pParam )
    {
        while( 1 )
        {    //等候事件通知
            WaitForSingleObject( g_hEvent, 
                INFINITE );
            printf( "Hello Event: %p\n", 
                g_hEvent );
        }
        return 0;
    }
    
    DWORD WINAPI ThreadRecv2( LPVOID pParam )
    {
        while( 1 )
        {    //等候事件通知
            WaitForSingleObject( g_hEvent2, 
                INFINITE );
            printf( "Hello Event2: %p\n", 
                g_hEvent2 );
            ResetEvent( g_hEvent2 );
        }
        return 0;
    }
    
    void Create( )
    {
        DWORD  nThreadID  = 0;
        HANDLE hThread[3] = { NULL };
        hThread[0] = CreateThread( NULL, 0,
            ThreadSend, NULL, 0, &nThreadID );
    
        hThread[1] = CreateThread( NULL, 0,
            ThreadRecv, NULL, 0, &nThreadID );
    
        hThread[2] = CreateThread( NULL, 0,
            ThreadRecv2, NULL, 0, &nThreadID );
    }
    
    int main(int argc, char* argv[])
    {
        //创建自动重置事件
        g_hEvent = CreateEvent( NULL, 
            FALSE, FALSE, NULL );
        //创建手动重置事件
        g_hEvent2 = CreateEvent( NULL, 
            TRUE, FALSE, NULL );
    
        Create( );
        
        getch( );
    
        //关闭事件
        CloseHandle( g_hEvent );
        return 0;
    }

    六 互斥量

      1 互斥量
        多个线程同时只能有一个执行.
       
      2 互斥量使用
        2.1 创建互斥

          HANDLE CreateMutex(
            LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全属性
            BOOL bInitialOwner,  //初始化的拥有线程
                  LPCTSTR lpName ); //名称

       bInitialOwner - TRUE表示当前创建互斥
        量的线程拥有互斥, FALSE为不拥有.
        2.2 等候互斥
           WaitForSingleObject
           WaitForMultipleObjects
        2.3 重置互斥
           ReleaseMutex
        2.4 关闭互斥
           CloseHandle
        2.5 使用互斥线程,按照谁先等候谁先拥有互斥量的规则顺序执行.
        2.6 其他函数
          OpenMutex 打开互斥

    View Code
    // Mutex.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "conio.h"
    #include "windows.h"
    
    HANDLE g_hMutex = NULL;
    
    DWORD WINAPI ThreadProc1( LPVOID pParam )
    {
        while( 1 )
        {   //等候互斥量
            WaitForSingleObject( g_hMutex,
                INFINITE );
            printf( "ThreadProc1----------\n");
            Sleep( 500 );
            //释放互斥量
            ReleaseMutex( g_hMutex );
        }
        return 0;
    }
    
    DWORD WINAPI ThreadProc2( LPVOID pParam )
    {
        while( 1 )
        {    //等候互斥量
            WaitForSingleObject( g_hMutex, 
                INFINITE );
            printf( "------ThreadProc2------\n");
            Sleep( 500 );
            //释放互斥量
            ReleaseMutex( g_hMutex );
        }
        return 0;
    }
    
    DWORD WINAPI ThreadProc3( LPVOID pParam )
    {
        while( 1 )
        {    //等候互斥量
            WaitForSingleObject( g_hMutex, 
                INFINITE );
            printf( "------------ThreadProc3\n");
            Sleep( 500 );
            //释放互斥量
            ReleaseMutex( g_hMutex );
        }
        return 0;
    }
    
    void Create( )
    {
        DWORD  nThreadID  = 0;
        HANDLE hThread[3] = { NULL };
        hThread[0] = CreateThread( NULL, 0,
            ThreadProc1, NULL, 0, &nThreadID );
        hThread[1] = CreateThread( NULL, 0, 
            ThreadProc2, NULL, 0, &nThreadID );
        hThread[2] = CreateThread( NULL, 0, 
            ThreadProc3, NULL, 0, &nThreadID );
    }
    
    int main(int argc, char* argv[])
    {    //创建互斥量
        g_hMutex = CreateMutex( NULL,
            FALSE, NULL ); //FALSE表示当前创建互斥量的线程(main)不拥有互斥量
    
        Create( );
    
        getch( );
        //关闭
        CloseHandle( g_hMutex );
    
        return 0;
    }

    七 信号量
      
      1 信号量
        通知的作用,和事件类似.但是与事件不同.
        事件只维护一个值0或者1.
        信号量维护一个变量,0时无信号,大于0有信号.
       
      2 信号量的使用
        2.1 创建信号量

            HANDLE CreateSemaphore(
           LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全属性
           LONG lInitialCount,//初始信号量
           LONG lMaximumCount,//最大信号量
           LPCTSTR lpName //命名
            );

          返回创建好的信号量句柄.
        2.2 等候信号量
          WaitForSingleObject
          WaitForMultipleObjects
        2.3 释放信号

          BOOL ReleaseSemaphore(
           HANDLE hSemaphore, //信号量句柄
           LONG lReleaseCount,//释放信号的数量
           LPLONG lpPreviousCount //释放前的数量
          );

        2.4 关闭信号量
          CloseHandle
        2.5 打开信号量
          OpenSemaphore

    View Code
    // Semaphore.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "conio.h"
    #include "windows.h"
    
    HANDLE g_hSemaphore = NULL;
    
    DWORD WINAPI ThreadSend( LPVOID pParam )
    {
        while( 1 )
        {
            CHAR ch = getch( );
            switch( ch )
            {
            case '1':
                //释放信号 其实是增加可用空位
                ReleaseSemaphore( g_hSemaphore,
                    1, NULL );
                break;
            case '5':
                ReleaseSemaphore( g_hSemaphore,
                    5, NULL );
                break;
            }
        }
        return 0;
    }
    
    DWORD WINAPI ThreadRecv( LPVOID pParam )
    {
        while( 1 )
        {    //等候信号量的信号
            WaitForSingleObject( 
                g_hSemaphore, INFINITE );
            printf( "Hello Semaphore\n" );
            Sleep( 100 );
        }
        return 0;
    }
    
    void Create( )
    {
        DWORD  nThreadID  = 0;
        HANDLE hThread[2] = { NULL };
        hThread[0] = CreateThread( NULL, 0,
            ThreadSend, NULL, 0, &nThreadID );
        hThread[1] = CreateThread( NULL, 0,
            ThreadRecv, NULL, 0, &nThreadID );
    
        WaitForMultipleObjects( 2, hThread,
            TRUE, INFINITE );
    }
    
    int main(int argc, char* argv[])
    {    //创建信号量
        g_hSemaphore = CreateSemaphore( 
            NULL, 3, 10, NULL );  //3表示一开始有3个可用空位
        Create();
        //关闭信号量
        CloseHandle( g_hSemaphore );
        return 0;
    }

    八 可等候定时器

      1 可等候定时器
        是一个更加精确系统提供的定时器.能够达到100ns级别.
       
      2 定时器的使用
        2.1 创建定时器

            HANDLE CreateWaitableTimer(
          LPSECURITY_ATTRIBUTES lpTimerAttributes,//安全属性
          BOOL bManualReset,//重置方式
          LPCTSTR lpTimerName //命名
          );

          返回创建好的定时器的句柄
        2.2 设置定时器

          BOOL SetWaitableTimer(
           HANDLE hTimer, //定时器句柄
           const LARGE_INTEGER *pDueTime,//定时器第一次触发的时间,100ns级别
           LONG lPeriod, //后续每次触发的间隔,毫秒级别
           PTIMERAPCROUTINE pfnCompletionRoutine,  //APC处理函数
           LPVOID lpArgToCompletionRoutine,//APC参数
           BOOL fResume ); //休眠标识

           pDueTime - 正值,表示绝对时间
              负值,表示相对于现在的时间间隔
           lPeriod  - 0  定时器不再有后续触发
              大于0 按照间隔触发
                
           pDueTime | lPeriod | lPeriod ....
            
        2.3 等候定时器
          WaitForSingleObject
          WaitForMultipleObjects
        2.4 关闭定时器
          CloseHandle
        2.5 APC定时器 (异步调用处理)

          VOID CALLBACK TimerAPCProc(
          LPVOID lpArgToCompletionRoutine   // data value
          DWORD dwTimerLowValue // timer low value
          DWORD dwTimerHighValue   // timer high value
              );

      2.6 其他
        OpenWaitableTimer 打开
        CancelWaitableTimer 取消

    View Code
    // WaitableTimer.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    
    #define _WIN32_WINNT 0x0400
    
    #include "windows.h"
    
    HANDLE g_hTimer = NULL;
    
    DWORD WINAPI TimerThread( LPVOID pParam )
    {
        while( 1 )
        {
            WaitForSingleObject( g_hTimer, 
                INFINITE );
            printf( "Hello Timer\n" );
        }
    
        return 0;
    }
    
    
    void Create( )
    {    //创建定时器
        g_hTimer = CreateWaitableTimer(
            NULL, FALSE, NULL );
        //设置定时器
        UINT64 nDueTime = -100000000;
        SetWaitableTimer( g_hTimer, 
            (PLARGE_INTEGER)&nDueTime, 1000, //nDueTime是第一次触发的时间(纳秒),1000是以后触发间隔(毫秒)
            NULL, NULL, FALSE );
        //创建等候线程
        DWORD dwThreadID = 0;
        HANDLE hThread = CreateThread( NULL, 0,
            TimerThread, NULL, 0, &dwThreadID );
        WaitForSingleObject( hThread, INFINITE );
        //关闭定时器
        CloseHandle( g_hTimer );
    }
    
    VOID CALLBACK TimerProc(
      LPVOID lpArgToCompletionRoutine,
      DWORD  dwTimerLowValue,
      DWORD  dwTimerHighValue )       
    {
        printf( "------APC TimerProc--------\n" );
    }
    
    void APCTimer( )
    {    //创建定时器
        HANDLE hTimer = CreateWaitableTimer(
            NULL, FALSE, NULL );
        //设置定时器
        UINT64 nDueTime = -10000000;
        SetWaitableTimer( hTimer, 
            (PLARGE_INTEGER)&nDueTime, 1000,
            TimerProc, NULL, FALSE );
        //
        while( 1 )
        {
            SleepEx( -1, TRUE ); //阻塞main()函数,但是waitabletimer的消息还是能执行。说明这是另外一个消息队列
        }    
    
        //关闭句柄
        CloseHandle( hTimer );
    }
    
    
    int main(int argc, char* argv[])
    {
        Create( );
        //APCTimer( );
    
        return 0;
    }
  • 相关阅读:
    一周信创舆情观察(11.2~11.8)
    一周信创舆情观察(10.26~11.1)
    一周信创舆情观察(10.19~10.25)
    一周信创舆情观察(10.12~10.18)
    Python脚本导出AWS EC2资源清单
    C++typename的由来和用法
    百篇已过,又是一个新篇章,谈谈感受吧
    【硬件篇之电源纹波噪声测试】
    C++的转换手段并与explicit关键词配合使用
    shell脚本的使用该熟练起来了,你说呢?(篇二)
  • 原文地址:https://www.cnblogs.com/tangzhengyue/p/2648016.html
Copyright © 2020-2023  润新知