您当前的位置:首页 > 互联网教程

python堆和栈的区别有哪些

发布时间:2025-05-15 06:28:12    发布人:远客网络

python堆和栈的区别有哪些

一、python堆和栈的区别有哪些

1、堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:

2、(1)程序内存布局场景下,堆与栈表示的是两种内存管理方式;

3、(2)数据结构场景下,堆与栈表示两种常用的数据结构。

4、堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:

5、(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

6、(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits的 Windows默认 1MB,64bits的 Linux默认 10MB;

7、(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

8、(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。

9、(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

10、(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

11、从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

12、无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

二、python中为什么要讲堆栈

1、因为堆栈是Python中处理数据不可或缺的一部分。

2、栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。

3、由于栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。

三、python stackless 怎么多线程并发

Stackless Python是Python编程语言的一个增强版本,它使程序员从基于线程的编程方式中获得好处,并避免传统线程所带来的性能与复杂度问题。Stackless为 Python带来的微线程扩展,是一种低开销、轻量级的便利工具,如果使用得当,可以获益如下:

以上是Stackless Python很简明的释义,但其对我们意义何在?——就在于Stackless提供的并发建模工具,比目前其它大多数传统编程语言所提供的,都更加易用:不仅是Python自身,也包括Java、C++,以及其它。尽管还有其他一些语言提供并发特性,可它们要么是主要用于学术研究的(如 Mozart/Oz),要么是罕为使用、或用于特殊目的的专业语言(如Erlang)。而使用stackless,你将会在Python本身的所有优势之上,在一个(但愿)你已经很熟悉的环境中,再获得并发的特性。

这自然引出了个问题:为什么要并发?

现实世界就是“并发”的,它是由一群事物(或“演员”)所组成,而这些事物以一种对彼此所知有限的、松散耦合的方式相互作用。传说中面向对象编程有一个好处,就是对象能够对现实的世界进行模拟。这在一定程度上是正确的,面向对象编程很好地模拟了对象个体,但对于这些对象个体之间的交互,却无法以一种理想的方式来表现。例如,如下代码实例,有什么问题?

daughter.eat(dinner)

第一印象,没问题。但是,上例中存在一个微妙的安排:所有事件是次序发生的,即:直到丈夫吃完饭,妻子才开始吃;儿子则一直等到母亲吃完才吃;而女儿则是最后一个。在现实世界中,哪怕是丈夫还堵车在路上,妻子、儿子和女儿仍然可以该吃就吃,而要在上例中的话,他们只能饿死了——甚至更糟:永远没有人会知道这件事,因为他们永远不会有机会抛出一个异常来通知这个世界!

第一印象,没问题。但是,上例中存在一个微妙的安排:所有事件是次序发生的,即:直到丈夫吃完饭,妻子才开始吃;儿子则一直等到母亲吃完才吃;而女儿则是最后一个。在现实世界中,哪怕是丈夫还堵车在路上,妻子、儿子和女儿仍然可以该吃就吃,而要在上例中的话,他们只能饿死了——甚至更糟:永远没有人会知道这件事,因为他们永远不会有机会抛出一个异常来通知这个世界!

1.1.2并发可能是(仅仅可能是)下一个重要的编程范式

我个人相信,并发将是软件世界里的下一个重要范式。随着程序变得更加复杂和耗费资源,我们已经不能指望摩尔定律来每年给我们提供更快的CPU了,当前,日常使用的个人计算机的性能提升来自于多核与多CPU机。一旦单个CPU的性能达到极限,软件开发者们将不得不转向分布式模型,靠多台计算机的互相协作来建立强大的应用(想想GooglePlex)。为了取得多核机和分布式编程的优势,并发将很快成为做事情的方式的事实标准。

安装Stackless的细节可以在其网站上找到。现在Linux用户可以通过SubVersion取得源代码并编译;而对于Windows用户,则有一个.zip文件供使用,需要将其解压到现有的Python安装目录中。接下来,本教程假设Stackless Python已经安装好了,可以工作,并且假设你对Python语言本身有基本的了解。

本章简要介绍了stackless的基本概念,后面章节将基于这些基础,来展示更加实用的功能。

微进程是stackless的基本构成单元,你可以通过提供任一个Python可调用对象(通常为函数或类的方法)来建立它,这将建立一个微进程并将其添加到调度器。这是一个快速演示:

Type"help","copyright","credits" or"license" for more information.

>>> stackless.tasklet(print_x)('one')

<stackless.tasklet object at 0x00A45870>

>>> stackless.tasklet(print_x)('two')

<stackless.tasklet object at 0x00A45A30>

>>> stackless.tasklet(print_x)('three')

<stackless.tasklet object at 0x00A45AB0>

>>>

注意,微进程将排起队来,并不运行,直到调用stackless.run()。

注意,微进程将排起队来,并不运行,直到调用stackless.run()。

调度器控制各个微进程运行的顺序。如果刚刚建立了一组微进程,它们将按照建立的顺序来执行。在现实中,一般会建立一组可以再次被调度的微进程,好让每个都有轮次机会。一个快速演示:

Type"help","copyright","credits" or"license" for more information.

>>> def print_three_times(x):

>>> stackless.tasklet(print_three_times)('first')

<stackless.tasklet object at 0x00A45870>

>>> stackless.tasklet(print_three_times)('second')

<stackless.tasklet object at 0x00A45A30>

>>> stackless.tasklet(print_three_times)('third')

<stackless.tasklet object at 0x00A45AB0>

>>>

注意:当调用stackless.schedule()的时候,当前活动微进程将暂停执行,并将自身重新插入到调度器队列的末尾,好让下一个微进程被执行。一旦在它前面的所有其他微进程都运行过了,它将从上次停止的地方继续开始运行。这个过程会持续,直到所有的活动微进程都完成了运行过程。这就是使用stackless达到合作式多任务的方式。

注意:当调用stackless.schedule()的时候,当前活动微进程将暂停执行,并将自身重新插入到调度器队列的末尾,好让下一个微进程被执行。一旦在它前面的所有其他微进程都运行过了,它将从上次停止的地方继续开始运行。这个过程会持续,直到所有的活动微进程都完成了运行过程。这就是使用stackless达到合作式多任务的方式。

通道使得微进程之间的信息传递成为可能。它做到了两件事:

Python 2.4.3 Stackless 3.1b3 060504(#69, May 3 2006, 19:20:41) [MSC v.1310 32

Type"help","copyright","credits" or"license" for more information.

>>> channel= stackless.channel()

>>> def receiving_tasklet():

... print"Recieving tasklet started"

... print"Receiving tasklet finished"

>>> def sending_tasklet():

... print"Sending tasklet started"

... channel.send("send from sending_tasklet")

... print"sending tasklet finished"

>>> def another_tasklet():

... print"Just another tasklet in the scheduler"

>>> stackless.tasklet(receiving_tasklet)()

<stackless.tasklet object at 0x00A45B30>

>>> stackless.tasklet(sending_tasklet)()

<stackless.tasklet object at 0x00A45B70>

>>> stackless.tasklet(another_tasklet)()

<stackless.tasklet object at 0x00A45BF0>

Just another tasklet in the scheduler

>>>

接收的微进程调用channel.receive()的时候,便阻塞住,这意味着该微进程暂停执行,直到有信息从这个通道送过来。除了往这个通道发送信息以外,没有其他任何方式可以让这个微进程恢复运行。

接收的微进程调用channel.receive()的时候,便阻塞住,这意味着该微进程暂停执行,直到有信息从这个通道送过来。除了往这个通道发送信息以外,没有其他任何方式可以让这个微进程恢复运行。

若有其他微进程向这个通道发送了信息,则不管当前的调度到了哪里,这个接收的微进程都立即恢复执行;而发送信息的微进程则被转移到调度列表的末尾,就像调用了stackless.schedule()一样。

同样注意,发送信息的时候,若当时没有微进程正在这个通道上接收,也会使当前微进程阻塞:

<stackless.tasklet object at 0x00A45B70>

>>> stackless.tasklet(another_tasklet)()

<stackless.tasklet object at 0x00A45BF0>

Just another tasklet in the scheduler

>>> stackless.tasklet(another_tasklet)()

<stackless.tasklet object at 0x00A45B30>

Just another tasklet in the scheduler

>>>#Finally adding the receiving tasklet

>>> stackless.tasklet(receiving_tasklet)()

<stackless.tasklet object at 0x00A45BF0>

sending tasklet finished

发送信息的微进程,只有在成功地将数据发送到了另一个微进程之后,才会重新被插入到调度器中。

发送信息的微进程,只有在成功地将数据发送到了另一个微进程之后,才会重新被插入到调度器中。

以上涵盖了stackless的大部分功能。似乎不多是吧?——我们只使用了少许对象,和大约四五个函数调用,来进行操作。但是,使用这种简单的API作为基本建造单元,我们可以开始做一些真正有趣的事情。

大多数传统编程语言具有子例程的概念。一个子例程被另一个例程(可能还是其它某个例程的子例程)所调用,或返回一个结果,或不返回结果。从定义上说,一个子例程是从属于其调用者的。

ping()

有经验的编程者会看到这个程序的问题所在:它导致了堆栈溢出。如果运行这个程序,它将显示一大堆讨厌的跟踪信息,来指出堆栈空间已经耗尽。

有经验的编程者会看到这个程序的问题所在:它导致了堆栈溢出。如果运行这个程序,它将显示一大堆讨厌的跟踪信息,来指出堆栈空间已经耗尽。

我仔细考虑了,自己对C语言堆栈的细节究竟了解多少,最终还是决定完全不去讲它。似乎,其他人对其所尝试的描述,以及图表,只有本身已经理解了的人才能看得懂。我将试着给出一个最简单的说明,而对其有更多兴趣的读者可以从网上查找更多信息。

每当一个子例程被调用,都有一个“栈帧”被建立,这是用来保存变量,以及其他子例程局部信息的区域。于是,当你调用 ping(),则有一个栈帧被建立,来保存这次调用相关的信息。简言之,这个帧记载着 ping被调用了。当再调用 pong(),则又建立了一个栈帧,记载着 pong也被调用了。这些栈帧是串联在一起的,每个子例程调用都是其中的一环。就这样,堆栈中显示: ping被调用所以 pong接下来被调用。显然,当 pong()再调用 ping(),则使堆栈再扩展。下面是个直观的表示:

3 ping被调用,所以 pong被调用,所以 ping被调用

4 ping被调用,所以 pong被调用,所以 ping被调用,所以 pong被调用

5 ping被调用,所以 pong被调用,所以 ping被调用,所以 pong被调用,所以 ping被调用

6 ping被调用,所以 pong被调用,所以 ping被调用,所以 pong被调用,所以 ping被调用……

现在假设,这个页面的宽度就表示系统为堆栈所分配的全部内存空间,当其顶到页面的边缘的时候,将会发生溢出,系统内存耗尽,即术语“堆栈溢出”。

上例是有意设计的,用来体现堆栈的问题所在。在大多数情况下,当每个子例程返回的时候,其栈帧将被清除掉,就是说堆栈将会自行实现清理过程。这一般来说是件好事,在C语言中,堆栈就是一个不需要编程者来手动进行内存管理的区域。很幸运,Python程序员也不需要直接来担心内存管理与堆栈。但是由于 Python解释器本身也是用C实现的,那些实现者们可是需要担心这个的。使用堆栈是会使事情方便,除非我们开始调用那种从不返回的函数,如上例中的,那时候,堆栈的表现就开始和程序员别扭起来,并耗尽可用的内存。

此时,将堆栈弄溢出是有点愚蠢的。 ping()和 pong()本不是真正意义的子例程,因为其中哪个也不从属于另一个,它们是“协程”,处于同等的地位,并可以彼此间进行无缝通信。

在stackless中,我们使用通道来建立协程。还记得吗,通道所带来的两个好处中的一个,就是能够控制微进程之间运行的流程。使用通道,我们可以在 ping和 pong这两个协程之间自由来回,要多少次就多少次,都不会堆栈溢出:

ping_channel= stackless.channel()

pong_channel= stackless.channel()

while ping_channel.receive():#在此阻塞

pong_channel.send("from ping")

while pong_channel.receive():

ping_channel.send("from pong")

#我们需要发送一个消息来初始化这个游戏的状态

stackless.tasklet(ping_channel.send)('startup')

stackless.run()

你可以运行这个程序要多久有多久,它都不会崩溃,且如果你检查其内存使用量(使用Windows的任务管理器或Linux的top命令),将会发现使用量是恒定的。这个程序的协程版本,不管运行一分钟还是一天,使用的内存都是一样的。而如果你检查原先那个递归版本的内存用量,则会发现其迅速增长,直到崩溃。

你可以运行这个程序要多久有多久,它都不会崩溃,且如果你检查其内存使用量(使用Windows的任务管理器或Linux的top命令),将会发现使用量是恒定的。这个程序的协程版本,不管运行一分钟还是一天,使用的内存都是一样的。而如果你检查原先那个递归版本的内存用量,则会发现其迅速增长,直到崩溃。

是否还记得,先前我提到过,那个代码的递归版本,有经验的程序员会一眼看出毛病。但老实说,这里面并没有什么“计算机科学”方面的原因在阻碍它的正常工作,有些让人坚信的东西,其实只是个与实现细节有关的小问题——只因为大多数传统编程语言都使用堆栈。某种意义上说,有经验的程序员都是被洗了脑,从而相信这是个可以接受的问题。而stackless,则真正察觉了这个问题,并除掉了它。

与当今的操作系统中内建的、和标准Python代码中所支持的普通线程相比,“微线程”要更为轻量级,正如其名称所暗示。它比传统线程占用更少的内存,并且微线程之间的切换,要比传统线程之间的切换更加节省资源。

为了准确说明微线程的效率究竟比传统线程高多少,我们用两者来写同一个程序。

Hackysack是一种游戏,就是一伙脏乎乎的小子围成一个圈,来回踢一个装满了豆粒的沙包,目标是不让这个沙包落地,当传球给别人的时候,可以耍各种把戏。踢沙包只可以用脚。

在我们的简易模拟中,我们假设一旦游戏开始,圈里人数就是恒定的,并且每个人都是如此厉害,以至于如果允许的话,这个游戏可以永远停不下来。

def __init__(self,name,circle):

self.messageQueue= Queue.Queue()

thread.start_new_thread(self.messageLoop,())

if hackysacker.counter>= turns:

hs.messageQueue.put('exit')

message= self.messageQueue.get()

debugPrint("%s is going home"% self.name)

debugPrint("%s got hackeysack from%s"%(self.name, message.name))

kickTo= self.circle[random.randint(0,len(self.circle)-1)]

debugPrint("%s kicking hackeysack to%s"%(self.name, kickTo.name))

kickTo.messageQueue.put(self)