精华内容
下载资源
问答
  • 最近查系统的内存泄露问题时,发现VIRT内存涨的特别厉害,

    最近查项目linux工程的内存泄露问题时,发现VIRT内存涨的特别厉害,开20线运行12个多小时,内存涨了70多个G,吓死个人,最后通过查阅资料和博客,发现是下面的情况导致的。


    情况说明:

     main

     {

                     for(i=0;i<num;i++)

                       {

                                pthread_attr_t threadAttr;
                                 pthread_attr_init(&threadAttr);
                                 // Set the stack size of the thread
                                 pthread_attr_setstacksize(&threadAttr, 120*1024);
                               //The default stack size is 2M !
                              pthread_create(&(Threadhandle[i]), NULL, InstanceThreadProc, intancePara+i);
                              pthread_attr_destroy(&threadAttr);     

                       } 

                 ........

                   for(i=0;i<nThread;i++)
               {
                      printf("thread %d - ok\n",i);
                      pthread_join(Threadhandle[i], NULL);
               }

      }

    VOID InstanceThreadProc(PVOID pvoid)

    {

         while(1)

           {

                 sem_init(&(pollpara.hStop),0,0);

                sem_init(&(pollpara.pollStoped),0,0);  

                pthread_attr_t threadAttr;

                 pthread_attr_init(&threadAttr);
                 pthread_attr_setstacksize(&threadAttr, 120*1024);
                 pthread_create(&Threadhandle, NULL, PollingThread,&pollpara);
                pthread_attr_destroy(&threadAttr);

          }

       ......

            sem_wait(&pollpara.hStop);

    }


    VOID PollingThread (PVOID pvoid)

    {

             //其他操作和处理

           。。。。。

            sem_post(&(pPara->hStop));

    }


    出问题的地方:

    1. InstanceThreadProc线程中不断的创建线程PollingThread 

    2.线程同步机制sem只是负责同步或通知,并不负责线程资源的释放

    也即是说即使线程InstanceThreadProc能够等待(使用sem)pollingThread线程正确退出,但是由于Linux下,使用pthread_create建立的线程默认是joinable属性的,如果不调用pthread_join等待该线程结束的话,系统为pollingThread开辟的内存资源无法回收,导致内存泄露。


    修改方案

    1.在InstanceThreadProc中使用pthread_join等待线程结束

    2.在创建线程pollingThread的时候,将其属性设置为PTHREAD_CREATE_DETACHED,这样线程结束后,系统会自动回线程资源,但是此种情况下,不能使用pthread_join来等待线程结束

    3.在线程pollingThread函数的开头,调用pthread_detach(pthread_self()),表示在线程结束后会自行释放 自身所占用的资源。


    总结

        Linux中,默认情况下是在一个线程被创建后,必须使用pthread_join函数对创建的线程进行资源回收,但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源

      Linux下,使用pthread_create建立的线程默认是joinable属性的,如果不调用pthread_join等待该线程结束,那么即便该线程调用pthread_exit使线程函数退出后, 线程本身的资源仍不会释放,这样线程资源就不会释放,导致内存泄露出现。






    展开全文
  • C++:多线程内存管理的思考

    千次阅读 2019-08-07 16:55:33
    用 C++ 写多线程算法,如果程序设计过程中完全不用 new、delete、malloc、free 等动态申请、释放内存,理论上讲,变量内存会完全分配在栈上。如果不用全局变量和指针引用,理论上讲,算法可以实现无锁设计。 但问题...

    用 C++ 写多线程算法,如果程序设计过程中完全不用 new、delete、malloc、free 等动态申请、释放内存,理论上讲,变量内存会完全分配在栈上。如果不用全局变量和指针引用,理论上讲,算法可以实现无锁设计。

    但问题来了,如果算法用 OOP 方法设计,数据能保证是完全分配在栈上吗?这里我们梳理一下这个问题,研究一下如何写出高效率的多线程无锁算法。

    1. C++:在堆上创建对象,还是在栈上?

    假设你已经清楚什么是堆,什么是栈。如果需要在堆上创建对象,要么使用new运算符,要么使用malloc系列函数。这点没有异议。真正有异议的是下面的代码:

    Object obj; 
    

    此时,obj是在栈上分配的吗?要回答这个问题,我们首先要理解这个语句是什么意思。这个语句就是代表着,在栈上创建对象吗?其实,这行语句的含义是,使对象obj具有“自动存储(automatic storage)”的性质。所谓“自动存储”,意思是这个对象的存储位置取决于其声明所在的上下文。

    • 如果这个语句出现在函数内部,那么它就在栈上创建对象。
    • 如果这个语句不是在函数内部,而是作为一个类的成员变量,则取决于这个类的对象是如何分配的。考虑下面的代码:
    class Class
    {
    	Object obj;
    };
         
    Class *pClass = new Class;
    

    指针pClass所指向的对象在堆上分配空间。因为Object obj;语句的含义是“自动存储”,所以,pClass->obj也是在堆上创建的。
    理解了这一点,再来看下面的语句:

    Object *pObj;
    pObj = new Object;
    

    Object *pObj;代表,指针pObj是自动存储的,仅此而已,没有任何其它含义。而下面一行语句则指出,这个指针所指向的对象是在堆上面分配的。如果这两行语句出现在一个函数内部,意味着当函数结束时,pObj会被销毁,但是它指向的对象不会。因此,为了继续使用这个对象,通常我们会在函数最后添加一个return语句,或者使用一个传出参数。否则的话,这个在堆上创建的对象就没有指针指向它,也就是说,这个对象造成了内存泄露。

    并不是说指针指向的对象都是在堆上创建的。下面的代码则使用指针指向一个在栈上创建的对象:

    Object obj;
    Object *pObj = &obj;
    

    至此,我们解释了函数内部的变量和成员变量。还有两类变量:全局变量和static变量。它们即不在堆上创建,也不在栈上创建。它们有自己的内存空间,是除堆和栈以外的数据区。也就是说,当Object obj即不在函数内部,又不是类的成员变量时,这个对象会在全局数据段创建,同理适用于static变量。对于指针Object *pObj;,如果这个语句出现在函数内部或类的成员变量,正如我们前面所说的,这个指针是自动存储的。但是,如果这个语句是在类的外部,它就是在全局数据段创建的。虽然它指向的对象可能在堆上创建,也可能在栈上创建。

    2. 堆和栈的区别

    • 生命周期
    • 性能

    2.1 生命周期

    第一点才是我们需要着重考虑的。由于栈的特性,如果你需要一个具有比其所在的上下文更长的生命周期的变量,只能在堆上创建它。所以,我们的推荐是:只要能在栈上创建对象,就在栈上创建;否则的话,如果你不得不需要更长的生命周期,只能选择堆上创建。这是由于在栈上的对象不需要我们手动管理内存。有经验的开发人员都会对内存管理感到头疼,我们就是要避免这种情况的发生。总的来说,我们更多推荐选择在栈上创建对象。

    但是,有些情况,即便你在栈上创建了对象,它还是会占用堆的空间。考虑如下代码:

    void func
    {
        std::vector v;
    } 
    

    对象v是在栈上创建的。但是,STL 的vector类其实是在堆上面存储数据的(这点可以查看源代码)。因此,只有对象v本身是在栈上的,它所管理的数据(这些数据大多数时候都会远大于其本身的大小)还是保存在堆上。

    2.2 性能

    关于第二点性能,有影响,不过一般可以忽略不计。确切的说,一般情况下你不需要考虑性能问题,除非它真的是一个问题。

    首先,**在堆上创建对象需要追踪内存的可用区域。这个算法是由操作系统提供,通常不会是常量时间的。**当内存出现大量碎片,或者几乎用到 100% 内存时,这个过程会变得更久。与此相比,栈分配是常量时间的。其次,栈的大小是固定的,并且远小于堆的大小。所以,如果你需要分配很大的对象,或者很多很多小对象,一般而言,堆是更好的选择。如果你分配的对象大小超出栈的大小,通常会抛出一个异常。尽管很罕见,但是有时候也的确会发生。有关性能方面的问题,更多出现在嵌入式开发中:频繁地分配、释放内存可能造成碎片问题。

    现代操作系统中,堆和栈都可以映射到虚拟内存中。在 32 位 Linux,我们可以把一个 2G 的数据放入堆中,而在 Mac OS 中,栈可能会限制为 65M。

    总的来说,关于究竟在堆上,还是在栈上创建对象,首要考虑你所需要的生命周期。当性能真正成为瓶颈的时候,才去考虑性能的问题。堆和栈是提供给开发者的两个不同的工具,不存在一个放之四海而皆准的规则告诉你,一个对象必须放在堆中还是在栈中。选择权在开发者手中,决定权在开发者的经验中。

    3. 堆空间是编译器分配,还是操作系统分配?

    内存管理是操作系统提供的核心功能之一。C++的内存管理,必须建立在操作系统的基础上。

    3.1 操作系统内存管理简介

    长期以来,在计算机系统中,内存都是一种紧缺和宝贵的资源,应用程序必须在载入内存后才能执行。早期,在内存空间不够大时,同时运行的应用程序的数量会受到很大的限制,甚至当某个应用程序在某个运行时所需内存超过物理内存时,应用程序就会无法运行。现代操作系统(Windows、Linux)通过引入虚拟内存进行内存管理,解决了应用程序在内存不足时不能运行的问题。

    本质上,虚拟内存就是要让一个程序的代码和数据在没有全部载入内存时即可运行。运行过程中,当执行到尚未载入内存的代码,或者要访问还没有载入到内存的数据时,虚拟内存管理器动态地将相应的代码或数据从硬盘载入到内存中。而且在通常情况下,虚拟内存管理器也会相应地先将内存中某些代码或数据置换到硬盘中,为即将载入的代码或数据腾出空间。

    因为内存和硬盘间的数据传输相对于代码执行非常慢,因此虚拟内存管理器在保证工作正确的前提下还必须考虑效率因素,如需要优化置换算法,尽量避免将要被执行的代码或访问的数据刚被置换出内存,而很久没有访问的代码或数据却一直驻留在内存中。虚拟内存管理器还需要将驻留在内存中的各个进程的代码数据维持在一个合理的数量上,并且根据进程性能的表现动态调整,使得程序运行时将涉及的磁盘IO次数降到尽可能低,以提高程序的运行性能。

    3.2 Windows内存管理

    3.2.1 Windows虚拟内存管理系统简介

    Win32虚拟内存管理器为每一个Win32进程提供了进程私有并且基于页的4GB(32bit)大小的线性虚拟地址空间。
    进程私有即每个进程只能访问属于自己的内存空间,而无法访问属于其它进程的地址空间,也不用担心自己的地址空间被其它进程看到(父子进程例外,比如调试器利用父子进程关系来访问被被调试进程的地址空间)。进程运行时用到的dll并没有属于自己的地址空间,而是其所属进程的虚拟地址空间,dll的全局数据,以及通过dll函数申请的内存都是从调用其进程的虚拟地址空间开辟的。

    基于页是指虚拟地址空间被划分为多个称为页的单元,页的大小由底层处理器决定,x86架构处理器中页的大小为4KB。页是Win32虚拟内存管理器处理的最小单元,相应的物理内存也被划分为多个页。虚拟内存地址空间的申请和释放,以及内存和磁盘的数据传输或置换都是以页为最小单位进行的。

    4GB大小意味着进程中的地址取值范围可以从0x00000000到0xFFFFFFFF,Win32将低区的2GB留给进程使用,高区的2GB留给系统使用。

    Win32中用来辅助实现虚拟内存的硬盘文件称为调页文件,可以有16个,调页文件用来存放被虚拟内存管理器置换出内存的数据。当调页文件的数据再次被进程访问时,虚拟内存管理器会将其从调页文件中置换进内存,进程可以正确对其访问。用户可以自己配置调页文件,出于空间利用效率和性能的考虑,程序代码不会被修改(包括exe和dll),所以当其所在页被置换出内存时,并不会被写进调页文件中,而是直接抛弃。当再次被需要时,虚拟内存管理器直接从存放程序代码的exe或dll文件中找到并调入内存。另外,对exe和dll文件中包含的只读数据的处理与程序代码处理相同,不会在调页文件中开辟空间存储。

    当进程执行某段代码或访问某些数据,而代码或数据还不在内存中时,称为缺页错误。缺页错误的原因很多,最常见的是代码和数据被虚拟内存管理器置换出内存,虚拟内存管理器会在代码被执行或数据被访问前将其调入内存。内存置换对开发人员来说是透明的,大大简化了开发人员的工作。但调页错误涉及磁盘IO,大量的调页错误会大大降低程序的总体性能,因此需要了解缺页错误的主要原因和规避方法。

    3.2.2 进程工作集

    因为频繁的调页操作引起的磁盘IO会大大降低程序的运行效率,因此对每一个进程,虚拟内存管理器都会将一定量的内存页驻留在物理内存中,并跟踪其执行的性能指标,并动态调整驻留的内存页数量。Win32中驻留在物理内存中的内存页称为进程的工作集(working set),进程的工作集可以通过任务管理器查看,内存使用列即为工作集大小。

    工作集是会动态变化的,进程初始时只有很少的代码页和数据页被调入物理内存。当执行到未被调入内存的代码或访问到尚未调入内存的数据时,相应代码页或数据页会被调入物理内存,工作集也会随之增加。但工作集不能无限增加,系统为每个进程设定了一个最小工作集和最大工作集,当工作集达到最大工作集大小,进程需要再次调入新页到物理内存时,虚拟内存管理器会架构原来工作集中某些内存页先置换出物理内存,然后再将需要调入的新页调入内存。

    因为工作集的页驻留在物理内存中,对工作集页的访问不会涉及磁盘IO,因此速度非常快。如果访问的代码或数据不在工作集中,会引发额外的磁盘IO,从而降低程序的执行效率。极端情况下会出现所谓的颠簸或抖动(thrashing),即程序的大部分执行时间都花在调页操作上,而不是执行代码上。

    3.3 Linux内存管理

    3.3.1 Linux内存管理机制简介

    Linux的内存管理主要分为两部分,一部分负责物理内存的申请与释放,物理内存的申请与释放的最小单位为页,在IA32中,页的大小为4KB;另一部分负责处理虚拟内存,虚拟内存的主要操作包括虚拟地址空间与物理地址空间的映射,物理内存页与磁盘页之间的置换等。

    3.3.2 Linux进程的内存布局

    一个32位Linux进程的地址空间为4GB,其中高位1GB,即0XC0000000–0XFFFFFFFF,为内核空间,低位3GB,即0X00000000–0XBFFFFFFF为用户地址空间。用户地址空间进一步被分为程序代码区、数据区(包括初始化数据区DATA和未初始化数据区BSS)、堆和栈。程序代码区占据最低端,往上是初始化数据区DATA和未初始化数据区BSS。代码区存放应用程序的机器代码,运行过程中代码不能修改,因此代码区内存为只读,且大小固定。数据区中存放应用程序的全局数据,静态数据和常量字符串,数据区大小也是固定的。

    堆从未初始化数据区开始,向上端动态增长,增长过程中虚拟地址值变大;栈从高位地址开始,向下动态增长,虚拟地址值变小。

    堆是应用程序在运行过程中动态申请的内存空间,如通过malloc/new动态生成对象或开辟内存空间时,最终会调用系统调用brk来动态调整数据区的大小。当申请的动态内存区域使用完毕,需要开发者明确使用相应的free/delete对申请的动态内存空间进行释放,free/delete最终也会使用brk系统调用调整数据区的大小。

    栈是用来存放函数的传入参数、临时变量以及返回地址等数据,不需要通过malloc/new开辟空间,栈的增长与缩减是因为函数的调用与返回,不需要开发人员操作,没有内存泄漏的危险。

    初始化数据区存放的是编译期就能够知道由程序设定初始值的全局变量及静态变量等,其初始值必须保存在最终生成的二进制文件中,并且在程序运行时会原封不动地将此区域映射到进程的初始化数据区。如果一个全局变量或静态变量在源代码中没有被赋初始值,在程序启动后,在第一次被赋值前,其初始值为0,本质上是有初始值的,其初始值为0。但当最终生成二进制文件时,未初始化数据区不会占据对应变量总大小的区域,而是只用一个值进行标识其未初始化数据区的总大小。如一个程序的代码指令有100KB,所有初始化数据总大小为100KB,所有未初始化数据总大小为150KB,则在最终生成的二进制文件中代码区有100KB,接着是100KB的初始化数据区,然后是4字节的大小空间,用于标记未初始化数据区大小,其值为150X1024,用于节省磁盘空间。但在进程虚拟地址空间中,对应未初始化数据区的大小必须是150KB,因为在程序运行时,程序必须真正能够访问到变量中的每一个,即当程序启动时,当检测到二进制文件中未初始化数据区的值为150X1024,则系统会开辟出150KB大小的区域作为进程的未初始化数据区并同时使用0对其进行初始化。

    3.3.3 Linux物理内存管理

    物理内存是用来存放代码指令与供代码指令操作的数据的最终场所,因此物理内存的管理是内存管理系统极其重要的任务。Linux使用页分配器(page allocator)来管理物理内存,页分配器负责分配和回收所有的物理内存页(物理内存的分配与回收的最小单位为4KB大小的页)。

    页分配器的核心算法称为兄弟堆算法(buddy-heap algorithm),算法思想是每个物理内存区域都会有一个与之相邻的所谓兄弟区域,当两个区域被回收后,会被合并成为一个区域。如果被合并区域的相邻区域也被回收后,会被进一步合并为更大的区域。当有物理内存请求到来时,页分配器会首先检测是否有大小与之一致的区域。如果有,直接使用找到的匹配区域满足请求;如果没有,则找到更大的一个区域,并继续划分,直到分出的区域能够满足请求。为了配合兄弟堆算法,必须有链表来记录自由的物理内存区域,对于每个相同大小的自由区域,会有一个链表将其连接,每种大小的区域都会有一个链表对其进行管理。自由区域的大小都是2的幂。

    当有一个8KB大小的内存请求到来,当前最小可供分配的区域为64KB,此时64KB会被划分为两个32KB,继而将低位的32KB继续划分为两个16KB大小的区域,再将最低位的16KB大小区域划分为两个8KB大小的区域,然后分配高位的8KB区域满足请求。

    3.3.4 Linux虚拟内存管理

    虚拟内存管理器的主要任务是维护应用程序的虚拟地址空间使用信息,如哪些区域已经被使用(映射),是否有磁盘文件作为备份存储。如果有,每个区域对应在磁盘的哪个区域,另外一个重要功能就是调页,如程序访问某些尚未调至物理内存的数据时,虚拟内存管理器负责定位数据,并将其置换进物理内存。如果物理内存此时没有自由页,还需要将物理内存中的某些页先置换出去。

    用来维护应用程序的虚拟地址空间使用信息的数据结构是vm_area_struct。每个vm_area_struct结构体都描述了一个进程虚拟地址空间中被分配的区域,当vm_area_struct个数不超过32个时,被连接成为一个链表;当超过32个时,所有的vm_area_struct会被组织为一棵自平衡二叉树,利于提高查询速度。当程序通过某个指针访问某个数据时,系统会查询vm_area_struct树,如果发现指针没有落在任何一个vm_area_struct所表示的区域内,则判定指针所代表的地址没有被分配,即非法的指针访问。

    3.3.5 虚拟地址映射为物理地址

    当通过程序的指针访问某个数据时,因为指针本质是一个虚拟地址值,因此虚拟地址值必须被转化为物理地址值,才能真正访问其所指代的数据。

    Linux使用三层映射策略将一个虚拟地址映射为一个物理地址。与Windows相比,多了Middle层,当对于IA32体系,Middle层没有用,因此Linux与Windows相同。

    4. 多线程算法的建议

    4.1 尽量在栈上分配数据数据对象

    函数局部变量、函数参数分配在栈上。

    void func()
    {
    	Obj myObj;
    	...
    }
    

    4.2 线程间共享数据对象,尽量加上 const 修饰符

    函数传递引用参数时,尽量加上 const 修饰符,这样就不会有副作用,也不用加锁。

    void func(const Obj& myObj)
    {
    	...
    }
    

    4.3 尽量减少在堆上频繁申请、释放数据对象

    尽管我们可以避免很多线程件的共享冲突,但有一点总无法回避,所有线程必须在同一台电脑的内存空间运行,频繁在堆上申请和释放内存,会导致性能劣化,可靠性降低。

    展开全文
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47题(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...

    史上最强多线程面试47题(含答案),建议收藏

    金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~

    1、并发编程三要素?

    1)原子性

    原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。

    2)可见性

    可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。

    3)有序性

    有序性,即程序的执行顺序按照代码的先后顺序来执行。

    2、实现可见性的方法有哪些?

    synchronized或者Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。

    3、多线程的价值?

    1)发挥多核CPU的优势

    多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。

    2)防止阻塞

    从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

    3)便于建模

    这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

    4、创建线程的有哪些方式?

    1)继承Thread类创建线程类

    2)通过Runnable接口创建线程类

    3)通过Callable和Future创建线程

    4)通过线程池创建

    5、创建线程的三种方式的对比?

    1)采用实现Runnable、Callable接口的方式创建多线程。

    优势是:

    线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

    在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

    劣势是:

    编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

    2)使用继承Thread类的方式创建多线程

    优势是:

    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

    劣势是:

    线程类已经继承了Thread类,所以不能再继承其他父类。

    3)Runnable和Callable的区别

    Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
    Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
    Call方法可以抛出异常,run方法不可以。
    运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

    6、线程的状态流转图

    线程的生命周期及五种基本状态:

    7、Java线程具有五中基本状态

    1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

    2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

    3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就
    绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

    4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

    根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

    b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

    c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    8、什么是线程池?有哪几种创建方式?

    线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

    java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。

    9、四种线程池的创建:

    1)newCachedThreadPool创建一个可缓存线程池

    2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。

    3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

    4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。

    10、线程池的优点?

    1)重用存在的线程,减少对象创建销毁的开销。

    2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。

    3)提供定时执行、定期执行、单线程、并发数控制等功能。

    11、常用的并发工具类有哪些?

    CountDownLatch
    CyclicBarrier
    Semaphore
    Exchanger

    12、CyclicBarrier和CountDownLatch的区别

    1)CountDownLatch简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。

    2)cyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!

    3)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。

    4)CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false。

    13、synchronized的作用?

    在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。

    synchronized既可以加在一段代码上,也可以加在方法上。

    14、volatile关键字的作用

    对于可见性,Java提供了volatile关键字来保证可见性。

    当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

    从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。

    15、什么是CAS

    CAS是compare and swap的缩写,即我们所说的比较交换。

    cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

    java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的( AtomicInteger,AtomicBoolean,AtomicLong)。

    16、CAS的问题

    1)CAS容易造成ABA问题

    一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。

    2) 不能保证代码块的原子性

    CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

    3)CAS造成CPU利用率增加

    之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。

    17、什么是Future?

    在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。

    Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

    18、什么是AQS

    AQS是AbustactQueuedSynchronizer的简称,它是一个Java提高的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。

    AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。

    19、AQS支持两种同步方式:

    1)独占式

    2)共享式

    这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。

    20、ReadWriteLock是什么

    首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

    因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

    21、FutureTask是什么

    这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    22、synchronized和ReentrantLock的区别

    synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:

    1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁

    2)ReentrantLock可以获取各种锁的信息

    3)ReentrantLock可以灵活地实现多路通知

    另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。

    23、什么是乐观锁和悲观锁

    1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

    2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

    24、线程B怎么知道线程A修改了变量

    volatile修饰变量
    synchronized修饰修改变量的方法
    wait/notify
    while轮询

    25、synchronized、volatile、CAS比较

    synchronized是悲观锁,属于抢占式,会引起其他线程阻塞。
    volatile提供多线程共享变量可见性和禁止指令重排序优化。
    CAS是基于冲突检测的乐观锁(非阻塞)

    26、sleep方法和wait方法有什么区别?

    这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器

    27、ThreadLocal是什么?有什么用?

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

    简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

    28、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

    这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁

    29、多线程同步有哪几种方法?

    Synchronized关键字,Lock锁实现,分布式锁等。

    30、线程的调度策略

    线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:

    1)线程体中调用了yield方法让出了对cpu的占用权利

    2)线程体中调用了sleep方法使线程进入睡眠状态

    3)线程由于IO操作受到阻塞

    4)另外一个更高优先级线程出现

    5)在支持时间片的系统中,该线程的时间片用完

    31、ConcurrentHashMap的并发度是什么

    ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

    32、Linux环境下如何查找哪个线程使用CPU最长

    1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过

    2)top -H -p pid,顺序不能改变

    33、Java死锁以及如何避免?

    Java中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java死锁情况出现至少两个线程和两个或更多资源。

    Java发生死锁的根本原因是:在申请锁时发生了交叉闭环申请。

    34、死锁的原因

    1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。

    例如:线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环。

    2)默认的锁申请操作是阻塞的。

    所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。

    35、怎么唤醒一个阻塞的线程

    如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

    36、不可变对象对多线程有什么帮助

    前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。

    37、什么是多线程的上下文切换

    多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

    38、如果你提交任务时,线程池队列已满,这时会发生什么

    这里区分一下:

    1)如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务

    2)如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

    39、Java中用到的线程调度算法是什么

    抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

    40、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

    线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

    41、什么是自旋

    很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

    42、Java
    Concurrency API中的Lock接口(Lock
    interface)是什么?对比同步它有什么优势?

    Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

    它的优势有:

    可以使锁更公平
    可以使线程在等待锁的时候响应中断
    可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
    可以在不同的范围,以不同的顺序获取和释放锁

    43、单例模式的线程安全性

    老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:

    1)饿汉式单例模式的写法:线程安全

    2)懒汉式单例模式的写法:非线程安全

    3)双检锁单例模式的写法:线程安全

    44、Semaphore有什么作用

    Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

    45、Executors类是什么?

    Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。

    Executors可以用于方便的创建线程池

    46、线程类的构造方法、静态块是被哪个线程调用的

    这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

    如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:

    1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的

    2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的

    47、同步方法和同步块,哪个是更好的选择?

    同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。

    48、Java线程数过多会造成什么异常?

    1)线程的生命周期开销非常高

    2)消耗过多的CPU资源

    如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争CPU资源时还将产生其他性能的开销。

    3)降低稳定性

    JVM在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM的启动参数、Thread构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError异常。

    展开全文
  • 如果有10个线程同时申请内存,而使用同步的话,效率比较低,那么如何保证这10个线程都能申请到内存????
  • 多线程下的内存释放问题

    千次阅读 2019-07-13 10:47:35
    问题由来, 考虑设计一个内存池类,...内存池类代码如下: .h文件 1 #pragma once 2 3 4 #include <memory> 5 #include <iostream> ...

    问题由来,

    考虑设计一个内存池类,http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html?ca=drs-cn

    内存池类代码如下:

    .h文件

     1 #pragma once
     2 
     3 
     4 #include <memory>
     5 #include <iostream>
     6 #include <windows.h>
     7 using namespace std;
     8 
     9 
    10 #define USHORT    unsigned int
    11 #define ULONG      unsigned long 
    12 #define MEMPOOL_ALIGNMENT  4
    13 
    14 #pragma warning( disable : 4291 )
    15 
    16 struct MemoryBlock
    17 {
    18     unsigned short          nSize;
    19     unsigned short          nFree;
    20     unsigned short          nFirst;
    21     //std::shared_ptr<MemoryBlock> pNext;
    22     MemoryBlock*            pNext;
    23     char                    aData[1];
    24 
    25     static void* operator new(size_t, unsigned short nTypes, unsigned short nUnitSize)
    26     {
    27         return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
    28     }
    29 
    30     static void  operator delete(void *p, size_t)
    31     {
    32         ::operator delete (p);
    33     }
    34 
    35     MemoryBlock (unsigned short nTypes = 1, unsigned short nUnitSize = 0);
    36     ~MemoryBlock() {}
    37 
    38 };
    39 
    40 class MemoryPool
    41 {
    42 private:
    43     //std::shared_ptr<MemoryBlock>  pBlock;
    44     MemoryBlock*            pBlock;
    45     unsigned short          nUnitSize;
    46     unsigned short          nInitCount;
    47     unsigned short          nGrowSize;
    48     CRITICAL_SECTION        allocSection;
    49     CRITICAL_SECTION        freeSection;
    50 
    51 public:
    52     static unsigned char   nMemoryIsDeleteFlag;            //标记MemoryPool内存是否释放
    53 
    54 public:
    55                      MemoryPool( unsigned short nUnitSize,
    56                                  unsigned short nInitCount = 1024,
    57                                  unsigned short nGrowSize = 256 );
    58                     ~MemoryPool();
    59 
    60     void*           Alloc();
    61     void            Free( void* p );
    62 };
    View Code

    .cpp文件

      1 #include "Memory.h"
      2 
      3 
      4 MemoryBlock::MemoryBlock(unsigned short nTypes /* = 1 */, unsigned short nUnitSize /* = 0 */)
      5     : nSize( nTypes * nUnitSize )
      6     , nFree( nTypes - 1 )
      7     , nFirst(1)
      8     , pNext(NULL)
      9 {
     10     char * pData = aData;                  
     11     for (unsigned short i = 1; i < nTypes; i++) 
     12     {
     13         *reinterpret_cast<unsigned short*>(pData) = i; 
     14         pData += nUnitSize;
     15     }
     16 }
     17 
     18 //不允许其他地方修改
     19 unsigned char MemoryPool::nMemoryIsDeleteFlag = 0;
     20 
     21 // UnitSize值不宜设置为过大值
     22 MemoryPool::MemoryPool( unsigned short _nUnitSize, unsigned short _nInitCount , \
     23     unsigned short _nGrowSize  ) 
     24 {
     25     if ( _nUnitSize > 4 )
     26         nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); 
     27     else if ( _nUnitSize <= 2 )
     28         nUnitSize = 2;              
     29     else
     30         nUnitSize = 4;
     31 
     32     nUnitSize = _nUnitSize;
     33     nInitCount = _nInitCount;
     34     nGrowSize = _nGrowSize;
     35     pBlock = NULL;
     36     
     37     InitializeCriticalSection (&allocSection);
     38     InitializeCriticalSection (&freeSection);
     39     
     40 }
     41 
     42 
     43 void * MemoryPool::Alloc()
     44 {
     45     //内存池没有MemoryBlock时,申请一块内存
     46     EnterCriticalSection (&allocSection);
     47     if ( !pBlock )
     48     {
     49         //auto pp = new(nInitCount,nUnitSize)  MemoryBlock(nInitCount,nUnitSize);
     50         pBlock = new(nInitCount,nUnitSize)  MemoryBlock(nInitCount,nUnitSize);
     51         LeaveCriticalSection (&allocSection);
     52         //返回MemoryBlock中的第一个内存块的地址
     53         return (void*)(pBlock->aData+1);
     54     }
     55 
     56     //内存池非空时
     57     MemoryBlock* pMyBlock = pBlock;                           //从链表头部pBlock开始
     58     while (pMyBlock && !pMyBlock->nFree )                           //搜索可用内存快  
     59         pMyBlock = pMyBlock->pNext;  
     60 
     61     // 最后一个MemoryBlock有空闲的内存块时
     62     if ( pMyBlock )            
     63     {
     64         //找到第一个空闲内存块的地址
     65         char* pFree = pMyBlock->aData + (pMyBlock->nFirst * nUnitSize);
     66 
     67         //MemoryBlock构造时,*((USHORT*)pFree)的值为所在的内存块数;
     68         //nFirst记录本MemoryBlock的第一个空闲内存块的计数;
     69         pMyBlock->nFirst = *((USHORT*)pFree);
     70 
     71         pMyBlock->nFree--;    
     72 
     73         LeaveCriticalSection(&allocSection);
     74         return (void*)pFree;
     75     }
     76     //所有的MemoryBlock都占满时
     77     else                   
     78     {
     79         if ( !nGrowSize )
     80             return NULL;
     81 
     82         pMyBlock = new(nInitCount, nUnitSize) MemoryBlock(nInitCount, nUnitSize);
     83         if ( !pMyBlock )
     84             return NULL;
     85 
     86         pMyBlock->pNext = pBlock ;
     87         pBlock = pMyBlock ;
     88 
     89         LeaveCriticalSection(&allocSection);
     90         return (void*)(pMyBlock->aData);
     91     }
     92 
     93     LeaveCriticalSection(&allocSection);
     94     return (void*)pBlock->aData;
     95 }
     96 
     97 //回收,内存返还给MemoryPool,而不是系统
     98 void MemoryPool::Free( void* p )
     99 {
    100     EnterCriticalSection (&freeSection);
    101     USHORT* pfree_us;   
    102     USHORT pfree_index, pHead;
    103     pfree_us = (USHORT *)p;
    104     MemoryBlock* pMyBlock = pBlock;
    105 
    106     if(pMyBlock == NULL)                                                               //pFree不是属于内存池管理的内存
    107     {
    108         LeaveCriticalSection (&freeSection); 
    109         return;
    110     }
    111 
    112     //定位pFree所在的块
    113     while ( ((ULONG)pMyBlock->aData > (ULONG)pfree_us) ||                        
    114         ((ULONG)pfree_us >= ((ULONG)pMyBlock->aData + pMyBlock->nSize*nUnitSize)) )                            
    115     {
    116         pMyBlock = pMyBlock->pNext;
    117     }
    118 
    119     if(pMyBlock == NULL)                                                               //pFree不是属于内存池管理的内存
    120     {
    121         LeaveCriticalSection (&freeSection); 
    122         return;
    123     }
    124 
    125     //回收pFree
    126     pMyBlock->nFree++;                 
    127     pHead = pMyBlock->nFirst;                                                            //第一个可用索引
    128     pfree_index = ( (ULONG)pfree_us-(ULONG)pMyBlock->aData )/nUnitSize;                    //获取pFree的索引号
    129     pMyBlock->nFirst = pfree_index;                                                     //pFree插入到可用块首
    130     *pfree_us = pHead;                                                                  //之前的块首链入
    131 
    132     //判断是否需要将Block内存返回给系统
    133     if ( (pMyBlock->nFree * nUnitSize)  == pMyBlock->nSize )                      
    134     {    
    135         pBlock = pMyBlock->pNext;                                      
    136         delete pMyBlock;
    137     }
    138     LeaveCriticalSection (&freeSection); 
    139 }
    140 
    141 
    142 MemoryPool::~MemoryPool()
    143 {
    144     if ( nMemoryIsDeleteFlag == 0 )
    145     {
    146         
    147         MemoryBlock*   my_block = pBlock;  
    148         MemoryBlock*   next_block = NULL;  
    149         while( my_block && my_block->nFree < nInitCount )  
    150         {  
    151             next_block = my_block->pNext;  
    152             delete my_block;
    153             my_block = NULL;
    154             my_block = next_block; 
    155         }
    156         DeleteCriticalSection (&allocSection);
    157         DeleteCriticalSection (&freeSection);
    158     }
    159     nMemoryIsDeleteFlag = 1;
    160 }
    View Code

    调用程序

     1 #include "Memory.h"
     2 #include <iostream>
     3 #include <cassert>
     4 
     5 using namespace std;
     6 
     7 #define    NCOUNT         24
     8 #define    NUNITSIZE     254
     9 #define    NALLSIZE      nCount * nUnitSize
    10 #define    THREADCOUNT   64
    11 
    12 int GloalInt = 0;
    13 CRITICAL_SECTION section;
    14 char *p_str = NULL;
    15 
    16 DWORD WINAPI Fun ( LPVOID lpThreadParameter )
    17 {
    18     //避免浅拷贝
    19     MemoryPool &pool = *((MemoryPool *)lpThreadParameter);
    20 
    21     EnterCriticalSection (&section);
    22     //第一个线程释放堆内存后,不允许其他线程再访问该堆内存;
    23     //nMemoryIsDeleteFlag为标记位
    24     //if ( MemoryPool::nMemoryIsDeleteFlag == 0)
    25     //{
    26         //MemoryPool::nMemoryIsDeleteFlag = 1;
    27         if (p_str == NULL)
    28             p_str = (char *)pool.Alloc();
    29 
    30         p_str[0] = 'c';
    31         cout <<  '\t' << p_str[0] << endl;
    32 
    33         //把p_str指向的空间释放并归还给内存池[注:不是归还给系统]
    34         pool.Free(p_str);
    35         //MemoryPool::nMemoryIsDeleteFlag = 0;
    36     //}
    37     LeaveCriticalSection (&section);
    38 
    39     return 0;
    40 }
    41 
    42 int main()
    43 {
    44     
    45     //创建一个内存池,每个固定内存块允许加载1024个对象(每个对象大小<=254字节).
    46     //每个MemoryBlock的大小 =(MemoryBlock的大小  + 254 * 1024) ;
    47     //向内存池申请NALLSIZE的内存空间,每个单元块的大小是NUNITSIZE;
    48     MemoryPool pool (NCOUNT, NUNITSIZE) ;
    49 
    50     InitializeCriticalSection (&section);
    51 
    52     HANDLE hThreadHandle[THREADCOUNT];
    53 
    54     //p_str是指向一个NUNITSIZE大小的空间.
    55     char *p_str = (char *)pool.Alloc();
    56 
    57     p_str[0] = 'b';
    58 
    59     //MAXIMUM_WAIT_OBJECTS
    60     //创建线程
    61     for(int i=0;i<THREADCOUNT;i++)
    62         hThreadHandle[i] = CreateThread (NULL,0,Fun,&pool,0,NULL);
    63 
    64     WaitForMultipleObjects (THREADCOUNT,hThreadHandle,TRUE,INFINITE);
    65 
    66     for(int i=0;i<THREADCOUNT;i++)
    67     {
    68         if ( hThreadHandle[i] != NULL )
    69         {
    70             CloseHandle (hThreadHandle[i]) ;
    71             hThreadHandle[i] = NULL;
    72         }
    73     }
    74     // do something...
    75 
    76     //把p_str指向的空间释放并归还给内存池[注:不是归还给系统]
    77     //pool.Free(p_str);
    78 
    79 
    80     //MemoryPool对象析构时,才是内存归还给系统
    81 
    82 
    83     return 0;
    84 }
    View Code

    在线程方法中,得到一个主线程传过来的内存池对象。

    问题,

      1、MemoryPool pool = *((MemoryPool *)lpThreadParameter);,出现浅拷贝的问题。

      答:某线程结束时,析构函数中有释放内存的代码,其他线程再释放就会报错。为此,增加了一个nMemoryIsDeleteFlag的标记变量。

      2、Free、析构函数、Alloc有大量申请内存,读取内存,内存写入和释放内存的操作。由此,运行程序会出现线程A释放了内存,线程B接着又访问该内存的问题。【如:http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html的问题】。

      答:使用share_ptr智能指针解决。

    share_ptr替代后的智能指针:

    .h文件

     1 #pragma once
     2 
     3 
     4 #include <memory>
     5 #include <iostream>
     6 #include <windows.h>
     7 using namespace std;
     8 
     9 
    10 #define USHORT    unsigned int
    11 #define ULONG      unsigned long 
    12 #define MEMPOOL_ALIGNMENT  4
    13 
    14 #pragma warning( disable : 4291 )
    15 
    16 struct MemoryBlock
    17 {
    18     unsigned short          nSize;
    19     unsigned short          nFree;
    20     unsigned short          nFirst;
    21     std::shared_ptr<MemoryBlock> pNext;
    22    // MemoryBlock*            pNext;
    23     char                    aData[1];
    24 
    25     static void* operator new(size_t, unsigned short nTypes, unsigned short nUnitSize)
    26     {
    27         return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
    28     }
    29 
    30     MemoryBlock (unsigned short nTypes = 1, unsigned short nUnitSize = 0);
    31     ~MemoryBlock() {}
    32 
    33 };
    34 
    35 class MemoryPool
    36 {
    37 private:
    38     std::shared_ptr<MemoryBlock>  pBlock;
    39     //MemoryBlock*            pBlock;
    40     unsigned short          nUnitSize;
    41     unsigned short          nInitCount;
    42     unsigned short          nGrowSize;
    43     CRITICAL_SECTION        allocSection;
    44     CRITICAL_SECTION        freeSection;
    45 
    46 public:
    47     //static unsigned char   nMemoryIsDeleteFlag;            //标记MemoryPool内存是否释放
    48 
    49 public:
    50                      MemoryPool( unsigned short nUnitSize,
    51                                  unsigned short nInitCount = 1024,
    52                                  unsigned short nGrowSize = 256 );
    53                     ~MemoryPool();
    54 
    55     void*           Alloc();
    56     void            Free( void* p );
    57 };
    View Code

    .cpp文件

      1 #include "Memory.h"
      2 
      3 
      4 MemoryBlock::MemoryBlock(unsigned short nTypes /* = 1 */, unsigned short nUnitSize /* = 0 */)
      5     : nSize( nTypes * nUnitSize )
      6     , nFree( nTypes - 1 )
      7     , nFirst(1)
      8     , pNext(NULL)
      9 {
     10     char * pData = aData;                  
     11     for (unsigned short i = 1; i < nTypes; i++) 
     12     {
     13         *reinterpret_cast<unsigned short*>(pData) = i; 
     14         pData += nUnitSize;
     15     }
     16 }
     17 
     18 //不允许其他地方修改
     19 //unsigned char MemoryPool::nMemoryIsDeleteFlag = 0;
     20 
     21 // UnitSize值不宜设置为过大值
     22 MemoryPool::MemoryPool( unsigned short _nUnitSize, unsigned short _nInitCount , \
     23     unsigned short _nGrowSize  ) 
     24 {
     25     if ( _nUnitSize > 4 )
     26         nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); 
     27     else if ( _nUnitSize <= 2 )
     28         nUnitSize = 2;              
     29     else
     30         nUnitSize = 4;
     31 
     32     nUnitSize = _nUnitSize;
     33     nInitCount = _nInitCount;
     34     nGrowSize = _nGrowSize;
     35     pBlock = NULL;
     36     
     37     InitializeCriticalSection (&allocSection);
     38     InitializeCriticalSection (&freeSection);
     39     
     40 }
     41 
     42 
     43 void * MemoryPool::Alloc()
     44 {
     45     //内存池没有MemoryBlock时,申请一块内存
     46     EnterCriticalSection (&allocSection);
     47     if ( !pBlock )
     48     {
     49         auto pp = new(nInitCount,nUnitSize)  MemoryBlock(nInitCount,nUnitSize);
     50         pBlock.reset(pp);
     51         LeaveCriticalSection (&allocSection);
     52         //返回MemoryBlock中的第一个内存块的地址
     53         return (void*)(pBlock->aData+1);
     54     }
     55 
     56     //内存池非空时
     57     MemoryBlock* pMyBlock = pBlock.get();                           //从链表头部pBlock开始
     58     while (pMyBlock && !pMyBlock->nFree )                           //搜索可用内存快  
     59         pMyBlock = pMyBlock->pNext.get();  
     60 
     61     // 最后一个MemoryBlock有空闲的内存块时
     62     if ( pMyBlock )            
     63     {
     64         //找到第一个空闲内存块的地址
     65         char* pFree = pMyBlock->aData + (pMyBlock->nFirst * nUnitSize);
     66 
     67         //MemoryBlock构造时,*((USHORT*)pFree)的值为所在的内存块数;
     68         //nFirst记录本MemoryBlock的第一个空闲内存块的计数;
     69         pMyBlock->nFirst = *((USHORT*)pFree);
     70 
     71         pMyBlock->nFree--;    
     72 
     73         LeaveCriticalSection(&allocSection);
     74         return (void*)pFree;
     75     }
     76     //所有的MemoryBlock都占满时
     77     else                   
     78     {
     79         if ( !nGrowSize )
     80             return NULL;
     81 
     82         pMyBlock = new(nInitCount, nUnitSize) MemoryBlock(nInitCount, nUnitSize);
     83         if ( !pMyBlock )
     84             return NULL;
     85 
     86         pMyBlock->pNext = pBlock ;
     87         pBlock.reset(pMyBlock) ;
     88 
     89         LeaveCriticalSection(&allocSection);
     90         return (void*)(pMyBlock->aData);
     91     }
     92 
     93     LeaveCriticalSection(&allocSection);
     94     return (void*)pBlock->aData;
     95 }
     96 
     97 //回收,内存返还给MemoryPool,而不是系统
     98 void MemoryPool::Free( void* p )
     99 {
    100     EnterCriticalSection (&freeSection);
    101     USHORT* pfree_us;   
    102     USHORT pfree_index, pHead;
    103     pfree_us = (USHORT *)p;
    104     MemoryBlock* pMyBlock = pBlock.get();
    105 
    106     if(pMyBlock == NULL)                                                               //pFree不是属于内存池管理的内存
    107     {
    108         LeaveCriticalSection (&freeSection); 
    109         return;
    110     }
    111 
    112     //定位pFree所在的块
    113     while ( ((ULONG)pMyBlock->aData > (ULONG)pfree_us) ||                        
    114         ((ULONG)pfree_us >= ((ULONG)pMyBlock->aData + pMyBlock->nSize*nUnitSize)) )                            
    115     {
    116         pMyBlock = pMyBlock->pNext.get();
    117     }
    118 
    119     if(pMyBlock == NULL)                                                               //pFree不是属于内存池管理的内存
    120     {
    121         LeaveCriticalSection (&freeSection); 
    122         return;
    123     }
    124 
    125     //回收pFree
    126     pMyBlock->nFree++;                 
    127     pHead = pMyBlock->nFirst;                                                            //第一个可用索引
    128     pfree_index = ( (ULONG)pfree_us-(ULONG)pMyBlock->aData )/nUnitSize;                    //获取pFree的索引号
    129     pMyBlock->nFirst = pfree_index;                                                     //pFree插入到可用块首
    130     *pfree_us = pHead;                                                                  //之前的块首链入
    131 
    132     //判断是否需要将Block内存返回给系统
    133 //     if ( (pMyBlock->nFree * nUnitSize)  == pMyBlock->nSize )                      
    134 //     {    
    135 //         pBlock = pMyBlock->pNext;                                      
    136 //         delete pMyBlock;
    137 //     }
    138     LeaveCriticalSection (&freeSection); 
    139 }
    140 
    141 
    142 MemoryPool::~MemoryPool()
    143 {
    144 //     if ( nMemoryIsDeleteFlag == 0 )
    145 //     {
    146 //         
    147 //         MemoryBlock*   my_block = pBlock;  
    148 //         MemoryBlock*   next_block = NULL;  
    149 //         while( my_block && my_block->nFree < nInitCount )  
    150 //         {  
    151 //             next_block = my_block->pNext;  
    152 //             delete my_block;
    153 //             my_block = NULL;
    154 //             my_block = next_block; 
    155 //         }
    156 //         DeleteCriticalSection (&allocSection);
    157 //         DeleteCriticalSection (&freeSection);
    158 //     }
    159 //     nMemoryIsDeleteFlag = 1;
    160 }
    View Code

    调用文件无需改变。

    总结:

      单线程下,因为程序是由前到后的执行顺序,所以内存方面的问题调试较为容易,可以使用new/delete管理内存。多线程下,多个线程下的执行顺序是随机的,不容易调试,这样关于内存方面的问题调试较难重现,所以多线程下考虑使用share_ptr智能指针来管理内存。

    智能指针的介绍:Boost程序库完全开发指南.pdf,其第三章有详细介绍。

     

      

    转载于:https://www.cnblogs.com/xuxu8511/p/3146761.html

    展开全文
  • 看到这个错误,我的第一感觉是创建了大量的线程,并且资源没有被回收,但是报错的却是其中一台应用服务器,表象看不太像是程序的问题,而此时在凌晨并发量也不应该会有这么大啊?同时我们不能因为报错暂停服务使用,...
  • 多线程内存溢出产生的实战分析

    万次阅读 多人点赞 2016-07-26 13:57:37
    通过上面的分析,我们看到其实多线程内存溢出有很大原因是因为系统设置和内存大小造成的,那么我们如何来分析当前系统配置能够支持多少线程呢? 对于java中的线程,我之前的理解一直是在java中new新线程的时候是...
  • 深入浅出多线程编程实战(六)ThreadLocal详解(内存泄漏) 文章目录前言一、内存泄露二、GC回收机制1.强引用与弱引用2.如何找到需要回收的对象三、ThreadLocal 内存泄漏四、ThreadLocal 正确使用 前言 在分析...
  • 定位多线程内存越界问题实践总结

    千次阅读 2014-03-14 16:57:09
    最近定位了在一个多线程服务器程序(OceanBase MergeServer)中,一个线程非法篡改另一个线程的内存而导致程序core掉的问题。定位这个问题花了整整一周的时间,期间历经曲折,尝试了各种内存调试的办法。往往感觉...
  • 多线程中的内存分配

    千次阅读 2014-04-10 10:39:27
    为什么说在堆上分配内存比在栈上分配内存慢?堆空间的开辟需要用系统函数,栈上直接修改指针 堆空间的管理需要系统记帐,栈上的空间可以由编译器管理或是保存在某个处理器寄存器中。 堆空间的释放需要系统管理,栈...
  • Linux 多线程内存占用分析

    千次阅读 2014-08-11 22:39:11
    大概现象为,一个很简单的程序逻辑,开启了几个线程,程序本身并没有过多的申请内存,或者说根本没有申请内存,但是在实际运行起来后,通过PS命令和status 查看内存占用居然达到了40M,详细分析了smaps文件没有得到...
  • 1、线程之间如何通信(线程之间交换信息的机制有两种:共享内存和消息传递) 在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读进行隐式通信;在消息传递中,必须通过发送消息显式通信。 2、线程...
  • C++编程中常见的线程内存问题

    千次阅读 2017-11-01 18:31:40
    避免多线程使用线程不安全的函数 避免多线程读写的数据不加锁保护 避免相互调用的函数加成同一把锁造成死锁 内存问题:覆盖、溢出、非法等● 使用未初始化的内存 全局变量和局部变量初始值为0,而局部变量和动态...
  • 关于多线程内存分配的问题

    千次阅读 2009-02-26 10:19:00
    by mayflowers Quote:资料上说User::Alloc 这个API 不是线程安全的(似乎除了继承RHandleBase的类是线程安全的其他有关内存的分配的API貌似都不是线程安全的)那么如果我用了多线程技术,并且这些线程共享了堆区...
  • 多线程内存池实现代码-双链表

    千次阅读 2013-05-24 16:19:03
    一下代码为一个简单的多线程内存池实现,内存池block块大小固定,采用双链表实现block申请、释放管理。 比较简单,参考注释和readme基本可以看懂,不多介绍了。 #ifndef _MEMPOOL_H_ #define _MEMPOOL_H_ /*...
  • 可以通过减少Java虚拟机堆和栈的容量来解决;
  • 线程的工作内存

    万次阅读 2017-04-21 14:48:39
    所谓线程的“工作内存”到底是个什么东西?有的人认为是线程的栈,其实这种理解是不正确的。看看JLS(java语言规范)对线程工作 内存的描述,线程的working memory只是cpu的寄存器和高速缓存的抽象描述。    ...
  • 架构师:『试试使用多线程优化』 第二天 头发很多的程序员:『师父,我已经使用了多线程,为什么接口还变慢了?』 架构师:『去给我买杯咖啡,我写篇文章告诉你』 ……吭哧吭哧买咖啡去了 在实际工作中,错误...
  • 异步执行函数线程Dome 调用MSVC CRT的函数_beginthread()或_beginthreadex()来创建线程。 _beginthread 参数和返回值 unsigned long _beginthread( void(_cdecl *start_address)(void *), //声明为void (*start_...
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...
  • C# 多线程

    万次阅读 多人点赞 2019-05-29 17:56:35
    一、基本概念 1、进程 首先打开任务管理器,查看当前运行的进程: 从任务管理器里面可以看到当前所有正在运行的进程。...线程是操作系统分配处理器时间的基本单元,在进程中可以有线程同时执行代码。进...
  • 多线程的部分经验: ...3:内存溢出的情况下,屏蔽部分代码调试,找到关键地方,做延迟,做内存申请试试。 4:绑定CPU可能使你无法火力全开,但能提高些稳定性。 5:很多人以为多线程越快越好,其实是越稳越好。
  • 多线程内存分配器mt_alloc

    千次阅读 2008-05-16 12:26:00
    size, multi-thread optimized allocator原文URL:http://list.cs.brown.edu/people/jwicks/libstdc++/html/ext/mt_allocator.html简介mt allocator是一个固定大小(2的幂)内存的分配器,最初是为多线程应用程序...
  • JAVA多线程并发

    千次阅读 多人点赞 2019-09-18 12:14:29
    JAVA多线程并发1 JAVA并发知识库2 JAVA 线程实现/创建方式2.1 继承 Thread 类2.2 实现 Runnable 接口2.3 Callable 、Future 、ExecutorService 有返回值线程2.4 基于线程池的方式2.4.1 4种线程池2.4.1.1 ...
  • 多线程内存

    千次阅读 2014-07-18 14:19:43
    如果一个内存池需要线程同步了,估计和默认的内存操作也差不了多远了...多线程内存池在实现上也就是在申请和释放外面包裹了一对加锁和解锁操作而已。如果我们采用模板编程,就可以实现一个功能强悍的模板类,该模板类有
  • java多线程全面详解

    千次阅读 多人点赞 2019-03-08 15:35:06
    多线程是什么?为什么要用多线程?  介绍多线程之前要介绍线程,介绍线程则离不开进程。  首先 , 进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制...
  • java 多线程编程之内存模型

    千次阅读 2013-07-23 14:57:27
    要认识java线程安全,必须了解两个主要的点:java的内存模型和java的线程同步机制...Java的多线程并发问题最终都会反映在Java的内存模型上,所谓线程安全无非要控制多个线程对某个资源的有序访问或修改。Java的内存模型
  • jvm线程栈空间内存分配位置

    千次阅读 2019-05-21 23:44:02
    jvm的线程申请内存空间属于堆外内存,是向操作系统申请的,也不是JVM直接内存,虽然类似。 JVM能创建的线程数需要的内存,不是JVM运行内存,堆内存,直接内存,而是操作系统剩余的可用内存,这个也决定了能创建...
  • 线程间共享内存的问题,许多同学是不是都觉得很简单?就是用全局变量来共享码,然而你说的并没有什么卵用…….(^__^)……. 对于线程内存关系不弄得清清楚楚,很难写好多线程程序。最简练而精准的话来形容线程间...
  • 线程分离 - 线程也会内存泄漏

    千次阅读 2020-05-03 17:12:48
    线程分离 - 线程也会内存泄漏 线程退出后,释放了所有占用的资源嘛? 上述问题我们通过以下代码进行测试,不断地创建线程,每次创建线程计数器加一,只到创建线程失败,打印出已创建的线程数 ​ #...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 140,127
精华内容 56,050
关键字:

多线程申请内存