• Windows程序设计6(内存、线程、进程)


     

    一、 Windows 内存管理

    1. 地址空间:32位操作系统空间0~ 2^32-14G),地址空间越大程序越易于编写。
    2. 地址空间的划分:

    2.1 用户地址空间 0 - 2G0x7FFFFFFF )存放用户的程序和数据。用户空间的代码是不能访问内核空间的数据和代码。

    2.1.1 空指针区(NULL区,0-64K)系统将地址小于64K指针,都认为是空指针。

    2.1.2 用户区 64K~ 0x7FFEFFFF,存放用户程序的代码和数据

    2.1.3 64K禁入区(0x7FFEFFFF - 0x7FFFFFFF )

    2.2 内核地址空间 2G - 4G

    存放内核的代码和数据,例如系统驱动。

    内核空间代码是可以访问用户空间。

    1. 区域:连续的一块内存。区域的大小一般为64K或者64K倍数。每个区域都有自己的状态

    1)空闲:没有被使用

    2)私有:被预定的区域

    3)映像:存放代码

    4)映射:存放数据

    1. 物理内存(半导体,内存条):系统可以使用的实际内存。CPU可以直接访问的内存。
    2. 虚拟内存(磁盘交换文件):将硬盘文件虚拟成内存使用。(pagefile.sys 文件)CPU如果要访问虚拟内存数据,必须将虚拟内存数据放到物理内存。经常使用的数据存放在物理内存中,不经常使用的数据存放在虚拟内存中。
    3. 内存页:系统管理内存的最小单位。内存页大小为4K,每个内存页有自己的权限。
    4. 页目表:
    5. 指针地址

     

    31        22 21        12 11           0

    |------------------|------------------|-------------------|

        10位          10位         12位  

        

    2^10=1024         1024           4K

      页目             页表          

    包含1K个页表     包含1K个页   包含4K个字节

    页目中包含1K项,每项对应一个页表,页表包含1K个项,每项对应一个页,页中包含4k字节  ----   1K*1K*4K=4 G

    1. 内存获取数据,访问过程

    a) CPU根据地址在物理内存中查找相应的位置。如果找到物理内存,取回数据。如果未找到,执行b)

    b) 根据地址去虚拟内存中查找相应的位置。如果未找到,那么该地址没有内存空间,返回错误(野指针)。如果找到,执行c)

    c) 将该地址所在内存页,置换到物理内存中,同时将原物理内存数据,存入到虚拟内存中。

    d) 将物理内存中的数据返回给使用者。

    1. 内存分配

    a) 虚拟内存分配-适合大内存分配,一般是1M之上的内存。

    b) 堆内存分配-适合小内存分配,一般是1M以下的内存。malloc/new

    c) 栈内存分配-适合小内存分配,一般是1M以下的内存。

    d) 

    1. 虚拟内存:

    a) 虚拟内存分配:速度快,大内存效率高。将内存和地址分配分别执行,可以先分配内存地址,在需要的时候再将地址绑定(提交)到内存。常用字大型电子表格等处理。

    b) 虚拟内存的分配:

    LPVOID   VirtualAlloc(

    LPVOID   lpAddress,// NULL或提交地址

    SIZE_T   dwSize, //分配的大小

    DWORD   flAllocationType, //分配方式

    DWORD   flProtect //内存访问方式

    );  //分配成功返回虚拟内存地址,失败返回NULL

    flAllocationType分配方式:

    MEM_COMMIT - 提交内存分配之后返回地址和内存空间.(内存和地址同时分配)

    MEM_RESERVE- 保留地址,分配之后只返回地址,内存空间不生成(不绑定到内存)。要使用内存必须再次提交执行,即再次执行VirtualAlloc(第一次返回的提交地址, ** , MEM_COMMIT , ** )

    不足一页分配或是跨页地址分配,则操作系统会将从跨页的低位地址开始分配的。即按照页边界对齐的原则。内存绑定(提交)以页(4096字节)为单位。

    获取内存状态:

    VOID    GlobalMemoryStatus(

           LPMEMORYSTATUS lpBuffer   // 内存状态结构

    );

    参数说明:内存状态结构:

    typedef struct _MEMORYSTATUS { // mst

        DWORD dwLength;        // 结构体字节数 

        DWORD dwMemoryLoad;    // 内存使用率,百分之

        DWORD dwTotalPhys;     // 物理内存总字节数

        DWORD dwAvailPhys;     // 空闲物理内存字节数 

        DWORD dwTotalPageFile; // 分页文件总字节数 

        DWORD dwAvailPageFile; // 空闲分页文件字节数 

        DWORD dwTotalVirtual;  // 虚拟内存总字节数 

        DWORD dwAvailVirtual;  // 空闲虚拟内存字节数 

    } MEMORYSTATUS, *LPMEMORYSTATUS;

    c) 虚拟内存的释放:

    BOOL    VirtualFree(

    LPVOID   lpAddress,//释放地址

    SIZE_T    dwSize, //释放的大小,字节数

    DWORD   dwFreeType //释放方式

    ); //成功返回true,失败返回false

    dwSize 0, 表示全部释放

    释放方式:

    MEM_DECOMMIT - 只释放内存,不释放地址。

    MEM_RELEASE - 地址和内存都释放。

    1. 堆内存:

    a) 堆内存分配:适合分配小内存,一般是小于1M的内存。一般每个程序都有自己的堆,默认大小为1M,会根据使用情况需要进行动态调整。

    b) 堆的使用

    1. 堆的信息:
      1. 获取调用进程的首个堆:

    HANDLE  GetProcessHeap (void);

     //成功返回调用进程首个堆的句柄,失败返回NULL

    1. 获取调用进程的所有堆

    DWORD   GetProcessHeaps(

         DWORD  NumberOfHeaps,  //堆句柄数组的容量                       

         PHANDLE  ProcessHeaps  // 保存返回堆句柄数组

    );//成功返回进程堆数目,失败返回0

    1. 创建堆:

    HANDLE    HeapCreate(

     DWORD    flOptions,//创建选项

     SIZE_T    dwInitialSize, //初始字节数,以后可调整

     SIZE_T    dwMaximumSize //最大字节数,0表示无穷大

    ); 成功返回堆句柄,失败返回NULL

    参数说明:

    flOptions

    HEAP_GENERATE_EXCEPTIONS   堆内存分配失败则引发异常

    HEAP_NO_SERIALIZE      支持不连续存取

    1. 从堆中分配内存

       LPVOID     HeapAlloc(

    HANDLE   hHeap,   //堆句柄

    DWORD    dwFlags,  //分配方式

    SIZE_T     dwBytes  //分配内存大小

    ); 成功返回地址,失败返回NULL

    参数说明:

    HEAP_GENERATE_EXCEPTIONS   堆内存分配失败则引发异常

    HEAP_NO_SERIALIZE      支持不连续存取,

    (若在创建堆的时候已经指定此参数,则此参数将忽略.)

    HEAP_ZERO_MEMORY   初始化清零

    1. 释放堆内存:

    BOOL   HeapFree(

    HANDLE  hHeap,  // 堆句柄

    DWORD  dwFlags, // 释放方式,只能取HEAP_NO_SERIALIZE

    LPVOID   lpMem   // 释放堆内存地址

    ); //成功返回true,失败返回false

    1. 销毁堆:

    BOOL   HeapDestroy(

    HANDLE   hHeap   //堆句柄

    ); //成功返回true ,失败返回false

    当堆被销毁后,其中该堆分配的内存均将释放。

    c) Win32 malloc / new 实现实际是调用了上述堆函数。即:

    VirtualAlloc/HeapAlloc/malloc/newWindows平台上,函数调用关系:

    new/malloc -> HeapAlloc ->VirtualAlloc

    1. 栈内存

    a) 栈内存-每个线程都具有自己的栈,默认大小1M,一般是系统维护栈。

    b) Windows提供了 _alloca 函数, 用于在栈上分配内存。

    c) 操作系统负责自动维护栈内存的分配与释放。

    1. 内存映射文件:

    a) 本质:将文件映射成内存来使用。当使用内存时,就是在使用文件。

      常用于实现进程间通信,比直接通过文件I/O效率更高。

    b) 内存映射的使用:

    1. 创建/ 打开文件CreateFile。设置可读可写属性.
    2. 创建映射:

    HANDLE   CreateFileMapping(

    HANDLE   hFile, //文件句柄

    LPSECURITY_ATTRIBUTES lpAttributes, //安全属性设为NULL

    DWORD   flProtect,//访问方式可读写,PAGE_READWRITE

    DWORD   dwMaximumSizeHigh,//内存映射文件大小的高32

    DWORD   dwMaximumSizeLow, //内存映射文件大小的低32

    LPCTSTR lpName //映射名,NULL表示匿名映射,其他进程不可访问

    );   // 创建成功返回映射文件句柄,失败返回NULL

    1. 加载映射文件:

    LPVOID    MapViewOfFile(

    HANDLE   hFileMappingObject,//内存映射文件句柄

    DWORD dwDesiredAccess,//访问模式,FILE_MAP_ALL_ACCESS

    DWORD   dwFileOffsetHigh,  //偏移量的高32

    DWORD   dwFileOffsetLow,       //偏移量的低32

    SIZE_T    dwNumberOfBytesToMap  //映射的字节数量

    ); 成功返回地址,失败返回NULL

    参数说明:

    dwFileOffsetHighdwFileOffsetLow合成的偏移量,必须是区域粒度的整数倍(64K的整数倍)

    使用映射,即对该返回地址的操作即可。

    1. 使用映射(内存):以内存的方式使用该映射
    2. 卸载内存映射:

    BOOL   UnmapViewOfFile(

    LPCVOID   lpBaseAddress //映射的地址

    ); .//成功返回true,失败返回false

    1. 关闭(销毁)映射:

    BOOL    CloseHandle(

      HANDLE   hObject   // 句柄

    );

    1. 关闭文件:

    CloseHandle,同上

    1. 对于已建好的映射,打开映射:

    HANDLE   OpenFileMapping(

      DWORD   dwDesiredAccess,  // 访问方式,FILE_MAP_ALL_ACCESS

      BOOL     bInheritHandle,    // 子进程是否继承此函数所返回的句柄

      LPCTSTR   lpName          //映射名

    );// 成功返回映射句柄,失败返回NULL

    c) 基于内存映射文件的进程间的通信

    1. 写进程:创建文件- 创建映射- 加载映射- 写入映射- 卸载映射 – 

            销毁映射- 关闭文件

    1. 读进程:打开映射- 加载映射- 读取映射- 卸载映射- 销毁映射
    2. 关于句柄:

    句柄就是内存对象地址在句柄表中索引。通过句柄不能直接访问内存,但是可以通过APIs函数操作其标识的对象。

    二、 Windows 进程

    1. 基本概念

    1) 进程是一个容器,包含程序执行所需要的代码,数据。资源、等信息。

       Windows 是一个多任务操作系统,可以同时执行多个进程。

    2. 进程特点

    1)每个进程都有自己的ID

    2)每个进程都有自己的地址空间,进程之间无法访问对方的地址空间。

    3)每个进程都有自己的安全属性

    4)每个进程当中至少包含一个线程

       3. 进程环境信息

    获取和释放环境信息

    获取

       LPVOID  GetEnvironmentStrings(VOID);//返回当前进程环境变量地址

    释放

       BOOL    FreeEnvironmentStrings(

        LPTSTR    lpszEnvironmentBlock  // 进程环境块指针

    );//成功返回true,失败返回false

       4. 获取和设置环境变量

    设置环境变量:

    BOOL  SetEnvironmentVariable(

       LPCTSTR   lpName,  //变量名

       LPCTSTR   lpValue  // 变量值

    );//成功返回true,失败返回false

    获取环境变量:

    DWORD  GetEnvironmentVariable(

    LPCTSTR  lpName,  // 变量名

    LPTSTR   Buffer,   //变量值缓冲区

    DWORD  Size // 变量值缓冲区大小(字符为单位含尾空字符)

    );//返回存在变量缓冲区的字符数,不含尾空字符

    若没有找到缓冲区变量名,则返回0

    5. 进程信息:

    1) 获取进程ID

    DWORD  GetCurrentProcessId(VOID)//返回调用进程的ID

    另外,int  _getpid( void );也可以返回进程的ID

    2) 获取进程句柄:

    HANDLE  GetCurrentProcess(VOID)//返回调用进程的伪句柄(-1),可以使用该句柄访问该进程的所用操作。但其子进程不继承该句柄

    6. 进程的使用:

    1)创建进程:

    WinExec  - Win6 遗留,现在基本不用。

    ShellExecute  - Shell 操作 ,速度慢。

    Createprocess   目前使用最多

    BOOL  CreateProcess(

    LPCTSTR   lpApplicationName,//应用程序名称路径

    LPTSTR    lpCommandLine, //命令行参数

    LPSECURITY_ATTRIBUTES  lpProcessAttributes, //进程安全属性

    LPSECURITY_ATTRIBUTES  lpThreadAttributes,

    //线程安全属性,NULL为缺省属性,同上。

    BOOL   bInheritHandles, //子进程是否可以继承父进程的句柄

    DWORD   dwCreationFlags, //创建方式,0表示立即启动

    LPVOID   lpEnvironment, //子进程环境,NULL表示继承父进程环境

    如:”book=c++pen=color0”其中的,表示环境变量分隔符,表示结束分号。

    LPCTSTR   lpCurrentDirectory,//子进程工作目录,NULL则继承父进程工作目录

    LPSTARTUPINFO   lpStartupInfo, //启动信息

    LPPROCESS_INFORMATION   lpProcessInformation

    //进程信息,返回进程和线程的句柄ID等。

    );//成功返回true,失败返回false

    参数说明:

    LPPROCESS_INFORMATION

    typedef  struct  _PROCESS_INFORMATION { // pi

        HANDLE hProcess;  ///子进程句柄

        HANDLE hThread;   ///子进程的主线程句柄

        DWORD dwProcessId; ///子进程ID

        DWORD dwThreadId; ////子进程的主线程ID

    } PROCESS_INFORMATION;

    7. 等候进程

    等候函数:

    等候单个:WaitForSingleObject

    DWORD   WaitForSingleObject(

    HANDLE   hHandle,        //等待的进程、线程句柄

    DWORD   dwMilliseconds // 等候时间(毫秒),INFINITE永远等候

    );// 成功返回引起该函数返回的事件码,失败返回WAIT_FAILED(-1).

    引起该函数返回的事件码:

    WAIT_OBJECT_0    句柄有信号,进线程程已结束

    WAIT_ WAIT_TIMEOUT   等候线、进程超时,进线程结束

    阻塞函数,等候句柄的信号,只在句柄有信号或超出等候时间,才会结束等候。

     等候多个:WaitForMultipleObjects -

    DWORD   WaitForMultipleObjects(

      DWORD  nCount, //句柄数量

      CONST HANDLE   *lpHandles,  //句柄BUFF的首地址

      BOOL    bWaitAll,//等候方式

      DWORD   dwMilliseconds      // 等候时间

    );

    bWaitAll - 等候方式

    TRUE - 表示所有句柄都有信号,才结束等候

    FASLE- 表示句柄中只要有1个有信号,就结束等候。

    8. 退出进程

    VOID ExitProcess(

    UINT uExitCode   // 退出码

    );

    终止进程(终止指定进程及其线程):

    BOOL  TerminateProcess(

     HANDLE  hProcess, //进程句柄

       UINT   uExitCode   // 退出码

    );//成功返回true,失败返回false

    9. 通过进程ID获取句柄

    HANDLE   OpenProcess(

      DWORD  dwDesiredAccess,// 访问权限,PROCESS_ALL_ACCESS

      BOOL   bInheritHandle,    // 子进程是否继承父进程句柄

      DWORD   dwProcessId       // 进程ID

    ); 返回打开进程句柄,失败返回NULL

    10. 关闭进程句柄

    CloseHandle(HANDLE  handle);

    三、 Windows 线程

    1. 线程概念:Windows线程是可以执行的代码的实例。系统是以线程为单位调度程序。一个程序当中可以有多个线程,实现多任务的处理。

       Windows 线程特点:

    1)线程都具有1个唯一标识  —— TID

    2)线程具有自己的安全属性

    3)每个线程都具有自己的内存栈

    4)每个线程都具有自己的寄存器信息,(用以保存自己的存储状态、现场等)

      进程多任务和线程多任务:

    进程多任务是每个进程都使用私有地址空间,相互独立,彼此交换数据困难。

    线程多任务是进程内的多个线程使用同一个地址空间即共享。彼此交换数据方便,容易产生冲突。

    线程的调度:

        CPU的执行时间划分成若干时间片,分给不同的线程,依次根据时间片轮流执行不同的线程。

       线程轮询:线程A -> 线程B -> 线程A......

    2. 线程过程函数

    DWORD    WINAPI ThreadProc(

       LPVOID   lpParameter //创建线程时,传递给线程的参数

    );

    //返回值代表线程执行的成功或失败,可由GetExitCodeThread函数获取。

    3. 创建线程

    HANDLE   CreateThread(

    LPSECURITY_ATTRIBUTES  lpThreadAttributes,//安全属性,NULL

    SIZE_T   dwStackSize,        //线程栈的初始大小,0表示与调用线程相同

    LPTHREAD_START_ROUTINE  lpStartAddress, //线程处理函数的函数地址

    LPVOID   lpParameter,                   //传递给线程处理函数的参数

    DWORD   dwCreationFlags,          //线程的创建方式,

    LPDWORD   lpThreadId                      //创建成功,返回线程的ID

    ); 创建成功,返回线程句柄,失败返回NULL

    参数说明:dwCreationFlags,

    0                        立即运行

    CREATE_SUSPENDED 创建后先挂起,直到调用ResumeThread函数再运行。

    4. 结束进程

    终止指定线程

    BOOL   TerminateThread(

       HANDLE   hThread,    // 线程句柄

       DWORD   dwExitCode   // 退出码

    );//成功返回true,失败返回false

    结束函数所在的线程

      VOID    ExitThread(

    DWORD   dwExitCode   // 线程退出码

    );

    5. 关闭线程句柄

    CloseHandle( HANDLE  hadle);

    6. 线程的挂起和执行

    挂起:

    DWORD   SuspendThread(

     HANDLE   hThread   // 线程句柄

    );//成功返回线程此前被挂起的次数,失败返回-1.

    执行:

    DWORD   ResumeThread(

      HANDLE   hThread   //线程句柄

      );//返回线程此前被恢复的次数,失败返回-1.

    7. 线程信息

    获取当前线程的ID:

    DWORD GetCurrentThreadId(VOID);

    获取当前线程的句柄:

    HANDLE GetCurrentThread(VOID);

    8. 打开指定ID的线程,获取其句柄

    HANDLE   OpenThread(

        DWORD dwDesiredAccess,  // 访问方式

        BOOL  bInheritHandle, // 子进程是否继承父进程句柄

        DWORD dwThreadId    //线程ID

    );//成功返回线程句柄,失败返回NULL。

    9. 线程问题:

    线程A -> 线程B -> 线程A 。。。。。

    当线程A执行printf输出时,如果线程A的执行时间结束,系统会将线程A的相关信息(栈、寄存器)压栈保护,同时将线程B相关信息恢复,然后执行线程B,线程B继续输出字符。由于线程A正输出字符,线程B会继续输出,画面字符会产生混乱。

    四、 线程同步技术

    同步机制:原子锁、临界区(段)、事件、互斥、信号量

    1. 原子锁:

    问题描述:

    多个线程对同一个数据进行原子操作,会产生结果丢失。比如执行++运算时,当线程A执行g_nValue1++时,如果线程切换时间正好是在线程A将值保存到g_nValue1之前,线程B继续执行g_nValue1++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_nValue1上,线程B进行的加法操作被覆盖。

    S1: g_nValue1  à寄存器

    S2:寄存器的值+1

    S3:寄存器的值—>g_nValue1

    多个线程分别执行此三个过程,使得值发生覆盖。

    原子锁的使用:

    对单条指令操作的API

    LONG  InterlockedIncrementLPLONG  lpAddend);//自增变量的共享指针

    //返回自增后的结果。

    LONG  InterlockedDecrementLPLONG  lpAddend);//自减变量的共享指针

    //返回自减后的结果。

    LONG InterlockedCompareExchangeLPLONG  lpAddend);

    LONG InterlockedExchangeLPLONG  lpAddend);

    LONG InterlockedExchangeAdd(LPLONG  lpAddend);

    原子锁的实现:

    直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问。

    2. 临界区

    问题描述:

    printf输出混乱,多线程情况下同时使用一段代码。临界区可以锁定一段代码,防止多个线程同时使用该段代码。

    临界区结构体:CRITICAL_SECTION  

    1) 初始化一个临界区

    VOID    InitializeCriticalSection(

      LPCRITICAL_SECTION   lpCriticalSection  //临界区结构体变量

    );

    2)进入临界区:

    添加到被锁定的代码之前

    VOID     EnterCriticalSection(

      LPCRITICAL_SECTION  lpCriticalSection  // 临界区结构体变量

    );

    阻塞函数,直到调用线程获取对指定临界区对象的所有权才返回,任何时候只有一个线程拥有临界区资源。

    3) 离开临界区:

    添加到被锁定的代码之后

    VOID     LeaveCriticalSection(

    LPCRITICAL_SECTION  pCriticalSection  // 临界区结构体变量

    );

    4) 删除临界区

    VOID   DeleteCriticalSection(

    LPCRITICAL_SECTION   lpCriticalSection  //临界区结构体变量

    );

    原子锁和临界区:

    原子锁 - 单条指令。

    临界区 - 单条或多行代码。

    3. 互斥体(Mutex)

    问题描述:多线程下代码或资源的共享使用。

    互斥体的使用:

    1) 创建互斥

    HANDLE  CreateMutex(

     LPSECURITY_ATTRIBUTES  lpMutexAttributes, //安全属性

        BOOL   bInitialOwner,//调用线程是否初始拥有该互斥体

       LPCTSTR   lpName    //互斥体名字

      ); 创建成功返回互斥句柄,失败返回NULL

    2)等待互斥体

    添加到资源之前

    WaitForSingleObject (hMutex , INFINITE);

    若其他线程拥有该互斥体,则hMutex无信号,函数阻塞,直到调用线程获得对互斥体的所有权,此时hMetex有信号,函数返回。

    3) 释放互斥体

    添加到资源之后

    BOOL   ReleaseMutex(

    HANDLE hMutex   // 互斥体句柄

    );//成功返回true,失败返回false

    4)关闭互斥体

    CloseHandle(hMutex);

    互斥体和临界区的区别:

    临界区 - 用户态,执行效率高,只能在同一个进程中使用。

    互斥体 - 内核态,执行效率低,可以通过命名的方式跨进程使用。

    4. 事件

    问题描述:程序之间的通知的问题。

    事件的使用:

    1)创建事件

    HANDLE   CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性

    BOOL    bManualReset,                       

    //事件重置方式,TRUE手动,FALSE自动

    BOOL bInitialState,       //事件初始状态,TRUE有信号

    LPCTSTR lpName  //事件命名

    ); 创建成功返回 事件句柄,失败返回NULL

    2)等待事件

    WaitForSingleObject/

    WaitForMultipleObjects

    3)触发事件

    将事件设置成有信号状态

    BOOL    SetEvent(

       HANDLE   hEvent   // 事件句柄

    );

    将事件设置成无信号状态

    BOOL   ResetEvent(

       HANDLE   hEvent   // 事件句柄

    );

    4)关闭事件

    CloseHandle HANDLE  handle);

    注意:防止事件造成死锁。

    5. 信号量

    问题描述:类似于事件,解决通知的相关问题。但是可以提供一个计数器,可以设置次数。解决多个进程共享有限的资源的问题。

    信号量的使用:

    1) 创建信号量

    HANDLE   CreateSemaphore(

      LPSECURITY_ATTRIBUTES  lpSemaphoreAttributes,

       //安全属性

      LONG    lInitialCount,        //初始化信号量资源数量

      LONG    lMaximumCount,   //信号量资源的最大值

      LPCTSTR  lpName           //信号量命名

    ); 创建成功返回信号量句柄,失败返回NULL

    2)等待信号量

    添加到资源之前。

    WaitForSingleObject hSemaphoreINFINITE);

    每等候通过一次,信号量的信号资源数减1,直到为0阻塞。

    若资源计数为0,则hSemaphore无信号,函数阻塞,直到资源计数大于0.此时有信号,函数返回同时资源数减1;。

    3)释放信号量

    添加到资源之后。

    BOOL  ReleaseSemaphore(

      HANDLE   hSemaphore, //信号量句柄

      LONG   lReleaseCount, //释放资源数量

      LPLONG   lpPreviousCount   

       //释放前信号量的数量,可以为NULL

    );//成功返回true,失败返回false

    4)关闭句柄

    CloseHandle( HANDLE  handle);

    6. 线程局部存储

    局部与线程的全局变量

    1) 分配线程局部存储

    DWORD  TlsAllocVOID);

    //成功返回线程局部存储索引,失败返回-1

    2) 保存数据到线程局部存储

    BOOL  TlsSetValue(

    DWORD  dwTlsIndex ,  //线程局部存储索引

    LPVOID  lpTlsValue      //数据

    )//成功返回true,失败返回false

    3) 线程局部储存中获取数据

    LPVOID   TlsGetValue(

      DWORD   dwTlsIndex   //线程局部存储索引

    );成功返回储存索引,失败返回NULL

    4) 释放线程局部储存索引

    BOOL     TlsFree(

      DWORD    dwTlsIndex   // 释放的线程局部存储索引

    );//成功返回true,失败返回false

    5) 静态线程局部存储

    --declspec(thread)  int  g_cn =0;

    --declspec(thread)  int  static  g_cn =0;

  • 相关阅读:
    选择高性能NoSQL数据库的5个步骤
    如何将 Redis 用于微服务通信的事件存储
    让你的AI模型尽可能的靠近数据源
    Collections.sort 给集合排序
    Bootstrap 文件上传插件File Input的使用
    select2 api参数的文档
    textarea 标签换行及靠左
    JSON
    JDK的get请求方式
    通讯录作业
  • 原文地址:https://www.cnblogs.com/haomiao/p/11646598.html
Copyright © 2020-2023  润新知