如何解决Python2的ngrok1.7内存泄漏问题题

您所在的位置: &
分享.net常见的内存泄露及解决方法
分享.net常见的内存泄露及解决方法
狩月的博客
本文讨论的是.NET内存泄漏的问题,介绍了常见的内存泄漏和解决的办法,介绍的很详细,希望对你有帮助,一起来看。
关于内存泄漏的问题,之前也为大家介绍过,比如:,是关于C++内存泄漏的。今天为大家介绍的是关于.NET内存泄漏的问题。
前段时间帮项目组内做了一次内存优化,产品是使用c#开发的winForm程序,一直以为.net提供了垃圾收集机制,开发的时候也没怎么注意内存的释放,导致最后的产品做出来之后,运行一个多小时就内存直接崩溃了,看来.net的垃圾收集还是得需要开发者加以控制,也不是万能的啊。
下面将对垃圾收集做以简介,然后描述一下我在内存优化过程中常见的内存泄露及解决方法。
托管堆的内存分配(下文中的托管堆指的是GC堆)
托管堆是以应用程序域为依托的,即每一个应用程序域有一个托管堆,每一个托管堆也只属于一个应用程序域,且托管堆是一块连续的内存,其中的对象也是紧密排列的。相对于C++中的非连续内存堆来说,托管堆的内存分配效率要高。托管堆维护了一个指针,指向当前已使用内存的末尾,当需要分配内存的时候,只需要指针向后移动指定数量的位置即可。而且托管堆通过应用程序域实现了应用程序之间内存的隔离,即不同的应用程序域之间在正常情况下是不能相互访问各自的托管堆的。
垃圾收集的算法有很多。例如引用计数、标记清除等等,托管堆使用的标记清除算法。
托管堆使用的是分代标记清除算法。
标记清除算法
首先,系统将托管堆内所有的对象视为可以回收的垃圾,然后系统从GCRoot开始遍历托管堆内所有的对象,将遍历到的对象标记为可达对象,在遍历完成之后,回收所有的非可达对象,完成一遍垃圾收集。
注意,托管堆的垃圾收集只会自己收集托管对象!
由于在执行完垃圾收集之后,托管堆中会产生很多的内存碎片,导致内存不再连续,因此在垃圾收集完成之后,系统会执行一次内存压缩,将不连续的内存重新排列整齐,变成连续的内存。(关于垃圾收集的详细信息,大家可以参考《CLR Via C#》)
通过上面的简述,大家都知道什么样的对象不会被收集,即能从GCRoot开始遍历到的对象。
最常见的GCRoot是线程的栈,线程的栈里面通常包含方法的参数、临时变量等。另外常见的GCRoot还有静态字段、CPU寄存器以及LOH堆上的大的集合。因此,如果想要让托管对象的内存顺利的释放,只需要断开与跟之间的联系即可。而对于非托管对象的内存,必须进行手动释放。
下面我根据自己在优化内存的过场中的一些常见错误以及一些解决方法。
在.net内存泄露的原因当中,事件占据了非常大的一部分比例,事件是一种委托的实例,也就是与我们类中其他的字段一样,也是一个字段。
委托为什么能阻止垃圾收集呢?即委托是如何让相关的对象在垃圾收集的时候被标记为可达对象的呢?首先要从委托的本质看起,
我们通常使用的委托是从类
public&abstract&class&MulticastDelegate&:&Delegate&
继承的,MulticastDelegate内部维护了一个private object _invocationL,即我们通常所有的委托链(ps:委托链同字符串一样,是不可变的),这个委托链是以个object [],内部保存了Delegate对象,及每一个委托实际上是一个Delegate对象,而Delegate包含了两个非常重要的字段:
internal&object&_ &internal&object&_methodB&
其中_target就是订阅事件的对象,_methodBase则是订阅事件的方法的 MethodInfo。其关联关系如下例所示:
Code: &public&event&EventHandler&TestE &void&MethodEndTempVarClear() &{ &Test&tempTestEvent&=&new&Test(); &TestEvent&+=&tempTestEvent.TestE &}&
我们假设此段代码所在的对象即为一个可达的对象,则其引用关系如下图所示:
由上图我们可以看出,原本应该在方法结束后就可以变为不可达对象的tempTestEvent变成了可达对象,因此也不能对其进行收集了。
个人建议:将类中所有的事件订阅添加到一个专门的方法当中,且实现一个与其匹配的取消订阅的方法,并在必要的时候,调用取消订阅的方法。
非托管对象
非托管对象无论在什么时候,都不会被垃圾收集所回收,必须手动释放。
.net中的非托管资源都实现了IDispose接口,我们可以在使用的时候,使用using(){}类实现非托管资源的释放。
其中有一种情况非常容易遗漏,即通过一个方法创建了一个非托管的对象,如下所示:
public&MemoryStream&CreateAStream() &{ &return&new&MemoryStream(); &}&
大家在使用的时候非常容易遗忘通过这种方法的形式创建的非托管对象,尤其是一些名字意义表达不准确的时候,例如
var&temp&=&CreateATemp();&
大家可能会漏掉对temp的内存释放,因此建议大家尽量少用方法创建或者初始化非托管对象,如果需要,则使用如下的方式:
bool&InitializeStream(out&MemoryStream&stream) &{ &stream&=&new&MemoryStream(); &return&true; &}&
即使用out关键字,这样大家在使用这个方法的时候,需要首先声明相关的非托管对象,可以在使用完成之后,及时的释放,减少遗漏。
集合/静态字段
对于集合,我们在使用完成之后,需要即时的clear,尤其是将一些方法中的临时变量添加到集合当中之后,会导致集合膨胀,并使得其中的内存泄露。
对于静态字段,我们应该尽量减少其可见的域,因为静态字段在整个程序运行期间都不会被释放,减少其可见域就减少了其内存泄露的可能性,注意,不到万不得以,千万不要声明静态的集合,就是使用了,那也一定要小心再小心。静态集合很容易造成内存泄露。
最好,大家有什么好的建议后者方法,欢迎补充!!
【编辑推荐】
【责任编辑: TEL:(010)】
关于&&的更多文章
掌握一门技术,首要的是掌握其基础。笔者从事.NET相关开发多年,
随着云计算、物联网、大数据、移动互联网的大发展,你应该知道这些。
Hadoop Summit 2013 大会讲师 PPT 第二季重磅来袭!如
现在这天气到处都是高温,还是老老实实的呆在家里上网
、27日,在美国圣何塞举行的Hadoop Summit
本书主要介绍采用Visual Studio 2005的C#语言为前台,SQL Server 2005数据库为后台的数据库系统开发技术。
全书分为15章,内容
51CTO旗下网站一般来说在&Python 中,为了解决内存泄漏问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收。由于Python 有了自动垃圾回收功能,就造成了不少初学者误认为自己从此过上了好日子,不必再受内存泄漏的骚扰了。但如果仔细查看一下Python文档对 __del__() 函数的描述,就知道这种好日子里也是有阴云的。下面摘抄一点文档内容如下:
Some common situations that may prevent the reference count of an object from going to zero include: circular references between objects (e.g., a doubly-linked list or a tree data structure with parent and child pointers); a reference to the object on the stack frame of a function that caught an exception (the traceback stored in sys.exc_traceback keeps the stack frame alive); or a reference to the object on the stack frame that raised an unhandled exception in interactive mode (the traceback stored in sys.last_traceback keeps the stack frame alive).
可见,有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。另外需要说明:对没有 __del__() 函数的 Python 对象间的循环引用,是可以被自动垃圾回收掉的。
如何知道一个对象是否内存泄漏了呢?
方法一、当你认为一个对象应该被销毁时(即引用计数为 0),可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。如果返回的引用计数不为 0,说明在此刻对象 obj 是不能被垃圾回收器回收掉的。
方法二、也可以通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
首先,来看一段正常的测试代码:
#--------------- code begin --------------
# -*- coding: utf-8 -*-
import sys
class CGcLeak(object):
def __init__(self):
self._text = '#'*10
def __del__(self):
def make_circle_ref():
_gcleak = CGcLeak()
_gcleak._self = _gcleak # test_code_1
print '_gcleak ref count0:%d' % sys.getrefcount(_gcleak)
del _gcleak
print '_gcleak ref count1:%d' % sys.getrefcount(_gcleak)
except UnboundLocalError:
print '_gcleak is invalid!'
def test_gcleak():
# Enable automatic garbage collection.
gc.enable()
# Set the garbage collection debugging flags.
gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | /
gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS)
print 'begin leak test...'
make_circle_ref()
print 'begin collect...'
_unreachable = gc.collect()
print 'unreachable object num:%d' % _unreachable
print 'garbage object num:%d' % len(gc.garbage)
if __name__ == '__main__':
test_gcleak()
在 test_gcleak() 中,设置垃圾回收器调试标志后,再用 collect() 进行垃圾回收,最后打印出该次垃圾回收发现的不可达的垃圾对象数和整个解释器中的垃圾对象数。
gc.garbage 是一个 list 对象,列表项是垃圾收集器发现的不可达(即是垃圾对象)、但又不能释放(即不能回收)的对象。文档描述为:A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects).通常,gc.garbage 中的对象是引用环中的对象。因为 Python 不知道按照什么样的安全次序来调用环中对象的 __del__() 函数,导致对象始终存活在 gc.garbage 中,造成内存泄漏。如果知道一个安全的次序,那么就打破引用环,再执行 del gc.garbage[:] ,以清空垃圾对象列表。
上段代码输出为(#后字符串为笔者所加注释):
#-----------------------------------------
begin leak test...
# 变量 _gcleak 的引用计数为 2.
_gcleak ref count0:2
# _gcleak 变为不可达(unreachable)的非法变量.
_gcleak is invalid!
# 开始垃圾回收
begin collect...
# 本次垃圾回收发现的不可达的垃圾对象数为 0.
unreachable object num:0
# 整个解释器中的垃圾对象数为 0.
garbage object num:0
#-----------------------------------------
由此可见 _gcleak 对象的引用计数是正确的,也没有任何对象发生内存泄漏。
如果不注释掉 make_circle_ref() 中的 test_code_1 语句:
_gcleak._self = _gcleak
也就是让 _gcleak 形成一个自己对自己的循环引用。再运行上述代码,输出结果就变成:
#-----------------------------------------
begin leak test...
_gcleak ref count0:3
_gcleak is invalid!
begin collect...
# 发现可以回收的垃圾对象: 地址为 012AA090,类型为 CGcLeak.
gc: uncollectable &CGcLeak 012AA090&
gc: uncollectable &dict 012AC1E0&
unreachable object num:2
#!! 不能回收的垃圾对象数为 1,导致内存泄漏!
garbage object num:1
#-----------------------------------------
可见 &CGcLeak 012AA090& 对象发生了内存泄漏!!而多出的 dict 垃圾就是泄漏的 _gcleak 对象的字典,打印出字典信息为:
{'_self': &__main__.CGcLeak object at 0x012AA090&, '_text': '##########'}
除了对自己的循环引用,多个对象间的循环引用也会导致内存泄漏。简单举例如下:
#--------------- code begin --------------
class CGcLeakA(object):
def __init__(self):
self._text = '#'*10
def __del__(self):
class CGcLeakB(object):
def __init__(self):
self._text = '*'*10
def __del__(self):
def make_circle_ref():
_a = CGcLeakA()
_b = CGcLeakB()
_a._b = _b # test_code_2
_b._a = _a # test_code_3
print 'ref count0:a=%d b=%d' % /
(sys.getrefcount(_a), sys.getrefcount(_b))
_b._a = None
# test_code_4
print 'ref count1:a=%d' % sys.getrefcount(_a)
except UnboundLocalError:
print '_a is invalid!'
print 'ref count2:b=%d' % sys.getrefcount(_b)
except UnboundLocalError:
print '_b is invalid!'
#--------------- code end ----------------
这次测试后输出结果为:
#-----------------------------------------
begin leak test...
ref count0:a=3 b=3
_a is invalid!
_b is invalid!
begin collect...
gc: uncollectable &CGcLeakA 012AA110&
gc: uncollectable &CGcLeakB 012AA0B0&
gc: uncollectable &dict 012AC1E0&
gc: uncollectable &dict 012AC0C0&
unreachable object num:4
garbage object num:2
#-----------------------------------------
可见 _a,_b 对象都发生了内存泄漏。因为二者是循环引用,垃圾回收器不知道该如何回收,也就是不知道该首先调用那个对象的 __del__() 函数。
采用以下任一方法,打破环状引用,就可以避免内存泄漏:1.注释掉 make_circle_ref() 中的 test_code_2 语句;2.注释掉 make_circle_ref() 中的 test_code_3 语句;3.取消对 make_circle_ref() 中的 test_code_4 语句的注释。
相应输出结果变为:
#-----------------------------------------
begin leak test...
ref count0:a=2 b=3 # 注:此处输出结果视情况变化.
_a is invalid!
_b is invalid!
begin collect...
unreachable object num:0
garbage object num:0
#-----------------------------------------
结论:Python 的 gc 有比较强的功能,比如设置 gc.set_debug(gc.DEBUG_LEAK) 就可以进行循环引用导致的内存泄露的检查。如果在开发时进行内存泄露检查;在发布时能够确保不会内存泄露,那么就可以延长 Python 的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。
阅读(...) 评论()标签:至少1个,最多5个
“墙上的斑点”
我第一次注意到短裤上的那个破洞,大概是在金年的三月上旬。如果想要知道具体的时间,那就得回想一下当时我看见的东西。我还能够回忆起,游泳池顶上,摇曳的、白色的灯光不停地映在我的短裤上;有三五名少年一同扎进了水里。哦,那是大概是冬天,因为我回忆起当时的前一天我和室友吃了部队锅,那段时间我没有吸烟,反而嚼了许多口香糖,糖纸总是掉下去,无意中埋下头,这是我第一次看到短裤上的那个破洞。
今天在机场等shuttle时,听到旁边的两个年轻人神采飞扬地讨论游泳的话题。莫名地回想起来,几年前看了一篇讲述Python内部整数对象管理机制的文章,其中谈到了Python应用内存池机制来对“大”整数对象进行管理。从它出发,我想到了一些问题,想要在这里讨论一下。
注:本文讨论的Python版本是Python 2 (2.7.11),C实现。
“一切皆对象”
我们知道,Python的对象,本质上是C中的结构体(生存于在系统堆上)。所有Python对象的根源都是PyObject这个结构体。
打开Python源码目录的Include/,可以找到object.h这一文件,这一个文件,是整个Python对象机制的基础。搜索PyObject,我们将会找到:
typedef struct _object {
PyObject_HEAD
再看看PyObject_HEAD这个宏:
#define PyObject_HEAD
_PyObject_HEAD_EXTRA
Py_ssize_t ob_
struct _typeobject *ob_
在实际编译出的PyObject中,有ob_refcnt这个变量和ob_type这个指针。前者用于Python的引用计数垃圾收集,后者用于指定这个对象的“类型对象”。Python中可以把对象分为“普通”对象和类型对象。也就是说,表示对象的类型,是通过一个指针来指向另一个对象,即类型对象,来实现的。这是“一切皆对象”的一个关键体现。
Python中的整数对象
Python里面,整数对象的头文件intobject.h,也可以在Include/目录里找到,这一文件定义了PyIntObject这一结构体作为Python中的整数对象:
typedef struct {
PyObject_HEAD
上面提过了,每一个Python对象的ob_type都指向一个类型对象,这里PyIntObject则指向PyInt_Type。想要了解PyInt_Type的相关信息,我们可以打开intobject.c,并找到如下内容:
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
sizeof(PyIntObject),
(destructor)int_dealloc,
/* tp_dealloc */
(printfunc)int_print,
/* tp_print */
/* tp_getattr */
/* tp_setattr */
(cmpfunc)int_compare,
/* tp_compare */
(reprfunc)int_repr,
/* tp_repr */
&int_as_number,
/* tp_as_number */
/* tp_as_sequence */
/* tp_as_mapping */
(hashfunc)int_hash,
/* tp_hash */
/* tp_call */
(reprfunc)int_repr,
/* tp_str */
PyObject_GenericGetAttr,
/* tp_getattro */
/* tp_setattro */
/* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE,
/* tp_flags */
/* tp_doc */
/* tp_traverse */
/* tp_clear */
/* tp_richcompare */
/* tp_weaklistoffset */
/* tp_iter */
/* tp_iternext */
int_methods,
/* tp_methods */
/* tp_members */
/* tp_getset */
/* tp_base */
/* tp_dict */
/* tp_descr_get */
/* tp_descr_set */
/* tp_dictoffset */
/* tp_init */
/* tp_alloc */
/* tp_new */
(freefunc)int_free,
/* tp_free */
这里给Python的整数类型定义了许多的操作。拿int_dealloc,int_free,int_new这几个操作举例。显而易见,int_dealloc负责析构,int_free负责释放该对象所占用的内存,int_new负责创建新的对象。int_as_number也是比较有意思的一个field。它指向一个PyNumberMethods结构体。PyNumberMethods含有许多个函数指针,用以定义对数字的操作,比如加减乘除等等。
通用整数对象池
Python里面,对象的创建一般是通过Python的C API或者是其类型对象。这里就不详述具体的创建机制,具体内容可以参考Python的。这里我们想要关注的是,整数对象是如何存活在系统内存中的。
整数对象大概会是常见Python程序中使用最频繁的对象了。并且,正如上面提到过的,Python的一切皆对象而且对象都生存在系统的堆上,整数对象当然不例外,那么以整数对象的使用频度,系统堆将面临难以想象的高频的访问。一些简单的循环和计算,都会致使malloc和free一次次被调用,由此带来的开销是难以计数的。此外,heap也会有很多的fragmentation的情况,进一步导致性能下降。
这也是为什么通用整数对象池机制在Python中得到了应用。这里需要说明的是,“小”的整数对象,将全部直接放置于内存中。怎么样定义“小”呢?继续看intobject.c,我们可以看到:
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS
#if NSMALLNEGINTS + NSMALLPOSINTS & 0
/* References to small integers are saved in this array so that they
can be shared.
The integers that are saved are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
值在这个范围内的整数对象将被直接换存在内存中,small_ints负责保存它们的指针。可以理解,这个数组越大,使用整数对象的性能(很可能)就越高。但是这里也是一个trade-off,毕竟系统内存大小有限,直接缓存的小整数数量太多也会影响整体效率。
与小整数相对的是“大”整数对象,也就是除开小整数对象之外的其他整数对象。既然不可能再缓存所有,或者说大部分常用范围的整数,那么一个妥协的办法就是提供一片空间让这些大整数对象按需依次使用。Python也正是这么做的。它维护了两个单向链表block_list和free_list。前者保存了许多被称为PyIntBlock的结构,用于存储被使用的大整数的PyIntObject;后者则用于维护前者所有block之中的空闲内存。
仍旧是在intobject.c之中,我们可以看到:
struct _intblock {
struct _intblock *
PyIntObject objects[N_INTOBJECTS];
typedef struct _intblock PyIntB
一个PyIntBlock保存N_INTOBJECTS个PyIntObject。
现在我们来思考一下一个Python整数对象在内存中的“一生”。
被创建出来之前,先检查其值的大小,如果在小整数的范围内,则直接使用小整数池,只用更新其对应整数对象的引用计数就可以了。如果是大整数,则需要先检查free_list看是否有空闲的空间,要是没有则申请新的内存空间,更新block_list和free_list,否则就使用free_list指向的下一个空闲内存位置并且更新free_list。
“内存泄漏”?
So far so good. 上述的机制可以很好减轻fragmentation的问题,同时可以根据所跑的程序不同的特点来做fine tuning从而编译出自己认为合适的Python。但是我们只说了Python整数对象的“来”还没有提它的“去”。当一个整数对象的引用计数变成0以后,会发生什么事情呢?
小整数对象自是不必担心,始终都是在内存中的;大整数对象则需要调用析构操作,int_dealloc (intobject.c):
static void
int_dealloc(PyIntObject *v)
if (PyInt_CheckExact(v)) {
Py_TYPE(v) = (struct _typeobject *)free_
free_list =
Py_TYPE(v)-&tp_free((PyObject *)v);
这个PyInt_CheckExact,来自于intobject.h:
#define PyInt_CheckExact(op) ((op)-&ob_type == &PyInt_Type)
它起到了类型检查的作用。所以如果这个指针v指向的不是Python原生整数对象,则int_dealloc直接调用该类型的tp_free操作;否则把不再需要的那块内存放入free_list之中。
Py_TYPE的定义:
#ifndef Py_TYPE
#define Py_TYPE(ob) (((PyObject*)(ob))-&ob_type)
可以看出,free_list所维护的单向链表,是使用ob_type这个field来链接前后元素的。
这也就是说,当一个大整数PyIntObject的生命周期结束时,它之前的内存不会交换给系统堆,而是通过free_list继续被该Python进程占有。
倘若一个程序使用很多的大整数呢?倘若每个大整数只被使用一次呢?是不是很像内存泄漏?
我们来做个简单的计算,假如你的电脑是Macbook Air,8GB Memory,如果你的PyIntObject占用24个Byte,那么满打满算,能够存下大约个整数对象。
下面做个实验。以下程序运行在Macbook Pro (mid 2015), 2.5Ghz i7, 16 GB Memory,Python 2.7.11的环境下:
l = list()
for i in range(0, num):
l.append(i)
if len(l) % 100000 == 0:
运行这个程序,会发现它占用了5.44GB的内存:
如果把整数个数减半,比如使用,则会占用2.72GB内存(正好原来一半):
一个PyIntObject占用多大内存呢?
讲道理,24 bytes x
bytes,约等于2^32,也就是4GB,那么为什么会占用5.44GB呢?
这并非程序其他部分的overhead,因为,就算你的程序只含有:
for i in range(0, ):
它仍旧会占用5.44GB内存。5.44 x 2^30 / 大约等于32.64,也就是均摊下来一个整数对象占用了32.64个Byte.
这个问题可以作为一个简单的思考题,这里就不讨论了。
Python的整数对象管理机制并不复杂,但也有趣,刚接触Python的时候是很好的学习材料。细纠下来会发现有很多工程上的考虑以及与之相关的现象,值得我们深入挖掘。
2 收藏&&|&&1
你可能感兴趣的文章
1 收藏,121
1 收藏,526
8 收藏,2.8k
你好,请教一下,在int_dealloc函数中:Py_TYPE(v) = (struct _typeobject *)free_free_list =这里的free_list只是一个PyIntObject类型的指针,是怎么保留所有的空闲空间呢?还是说只保留了最后一次的,如果是这样那为什么没有free操作呢?
block_list和free_list是两个单向链表。从int_dealloc可以看出来,一个整数对象的引用计数到0之后,它所占用的内存又放到free_list的表头。
可是block_list和free_list的类型分别是PyIntBlock* 和 PyIntObject*,free_list不是链表。还有在int_dealloc函数里面对free_list的操作也只是一个普通的指针操作。
static PyIntObject *
fill_free_list(void)
PyIntObject *p, *q;
/* Python's object allocator isn't appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
((PyIntBlock *)p)-&next = block_
block_list = (PyIntBlock *)p;
/* Link the int objects together, from rear to front, then return
the address of the last int object in the block. */
p = &((PyIntBlock *)p)-&objects[0];
q = p + N_INTOBJECTS;
while (--q & p)
Py_TYPE(q) = (struct _typeobject *)(q-1);
Py_TYPE(q) = NULL;
return p + N_INTOBJECTS - 1;
这个是intobject.c里面的fill_free_list函数,在free_list指向NULL时调用,用来分配新的空闲block。其中的Py_TYPE这个macro表示(PyObject*)(ob))-&ob_type。也就是说,这个单向链表是使用ob_type这个域来作为链接的指针。
啊,懂了,谢谢你。
分享到微博?
技术专栏,帮你记录编程中的点滴,提升你对技术的理解收藏感兴趣的文章,丰富自己的知识库
明天提醒我
我要该,理由是:
扫扫下载 App问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
再编写爬虫的时候,总是跑了一段时间(不会超过12个小时)就会被OOM掉。很是无奈!!!根据官方的文档, 使用这个prefs()但是实在找不出问题的所在。
Live References
HtmlResponse
oldest: 753s ago
MySuteSpider
oldest: 2964s ago
oldest: 2920s ago
oldest: 751s ago
oldest: 751s ago
爬虫的处理是获取所有页面的a标签的链接:
#获取域名的后缀
def get_domain_suffix(domain):
if 'com' in tldextract.extract(domain).suffix:
return True
return False
#拼接域名。只存主域名
def save_domain(domain):
domain_name = tldextract.extract(domain).domain
suffix_name = tldextract.extract(domain).suffix
return domain_name + '.' + suffix_name
#获取域名ip
def get_domain_ip(domain):
ip = socket.gethostbyname(domain)
return '114.114.114.114'
# 获取域名所在的国家
def get_domain_ct_iso(ip):
GEO = geoip2.database.Reader(
'/var/test/geodb/GeoLite2-City.mmdb')
r = GEO.city(ip)
return r.country.iso_code
class MyDomainSpider(scrapy.Spider):
name = 'my_domain'
start_urls = [
def parse_items(self, response):
item = TripItem()
for url in response.xpath('//a/@href').extract():
if url.startswith('http'):
domain = urlparse.urlparse(url).netloc
if get_domain_tw(domain) or get_domain_ct_iso(get_domain_ip(domain)) == 'US':
item['domain'] = save_domain(domain)
item['ip'] = get_domain_ip(domain)
item['datetime'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
yield item
def parse(self, response):
for url in response.xpath('//a/@href').extract():
if url.startswith('http'):
domain = urlparse.urlparse(url).netloc
if get_domain_tw(domain) or get_domain_ct_iso(get_domain_ip(domain)) == 'US':
yield scrapy.Request(url, callback=self.parse_items)
请指教一下谢谢
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
yield item 是不是得落地,存文件或者db,不然一直存内存了
同步到新浪微博
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
扫扫下载 App

我要回帖

更多关于 js内存泄漏及解决办法 的文章

 

随机推荐