Python [de]压缩模块在Linux上是否是线程安全的?在Google App Engine上?

时间:2013-06-18 23:55:06

标签: python linux multithreading google-app-engine compression

我应该使用什么压缩 - 解压缩Python模块来构建一个系统,其中Google App Engine(Python 2.7)与Linux机器上的应用程序交换压缩数据?

还有两个额外的限制因素:

  • Linux机器和GAE都将进行压缩/解压缩,需要线程安全操作;
  • 我想在不使用类似文件的对象的情况下完成所有操作,因为App Engine无法为动态文件提供传统的Python文件名。

我问,因为从文档中不清楚某些[de]压缩模块是否是线程安全的。

有人可以帮忙填写压缩模块表吗?

谢谢!

编辑(回答abarnert的问题):

  • RAM与类似文件的对象= App Engine不提供打开类文件对象的方法(除非文件是作为应用程序的一部分上传的)。因此,如果GAE从Linux盒子中获取压缩数据,如果压缩模块坚持要通过类似文件的对象,我不知道如何解压缩它。例如,gzip模块坚持使用文件名:http://docs.python.org/2/library/gzip.html

  • Linux上的“线程安全”=应用程序将位于Web服务器后面,因此可能会同时调用单独的线程进行压缩和/或解压缩。 Linux应用程序首先从磁盘中读取几千个(数百万个)半随机块压缩数据,然后解压缩每个,然后更改每个未压缩的块,然后压缩更改的块,然后发送到GAE。现在,该应用程序使用zlib并在cherrypy中轻负载下运行完美,但是一旦请求开始并行发生,就会引发zlib错误。一旦这个压缩事情被整理出来,我就会切换到nginx。

1 个答案:

答案 0 :(得分:3)

你的问题基本上没有意义,因为你误解了一些基本的东西并产生了不存在的问题。我试着在评论中回答,但是你可以用这种方式限制,所以...


  

我想在不使用类似文件的对象的情况下完成所有操作,因为App Engine无法为动态文件提供传统的Python文件名。

您不需要文件类对象的文件名或文件。这就是文件类对象背后的整个想法。


  

App Engine不提供打开类文件对象的方法(除非该文件是作为应用程序的一部分上传的。)

不,您仍然会混淆文件对象和类文件对象。 file object表示磁盘上的实际文件。 GAE限制了那些。 类似文件的对象是具有相同API的任何对象,即一个 文件的对象,没有(必然)实际为一个。 GAE没有做任何事情来阻止你创建类似文件的对象。

-

类文件对象的范例示例StringIO.StringIO就像一个文件对象,但它不是实际读写文件,而是读取和写入内存中的字符串缓冲区。

因此,您可以像这样编写类似文件的对象:

my_file_like_obj = StringIO()

或者,如果你在内存中有一个缓冲区,并且你希望能够像文件那样从中读取它:

my_file_like_obj = StringIO(buffer)

但是,在许多情况下,Python / GAE已经为您提供了一个类似文件的对象,您可以按原样使用它,而无需将其读入缓冲区并将其包装在另一个类似文件的对象中。许多网络API为您提供类似文件的对象,但不是全部。

例如,如果你调用urllib2.urlopen,结果就是一个类似文件的对象;如果您拨打urlfetch.fetch,则不会,如果您需要,则必须使用StringIO(response.content)


  

因此,如果GAE从Linux机箱获取压缩数据,如果压缩模块坚持要通过类似文件的对象,我就不知道如何解压缩它。

如果它坚持使用类似文件的对象,请为其提供类似文件的对象。创建实际文件是一种方法,但不是唯一的方法。如果您收到urllib2.urlopen回复,请通过该回复。如果您在内存中有缓冲区,只需将其包装在StringIO中即可。等等。


  

例如,gzip模块坚持使用文件名:http://docs.python.org/2/library/gzip.html

不,它没有。阅读您链接到的文档:

  

class gzip.GzipFile([filename [,mode [,compress level [,fileobj [,mtime]]]]])

请注意,fileobj参数以及filename参数?文档的第一行说:

  

... fileobj filename 中的至少一个必须被赋予非平凡的价值......

因此,除非filenamefileobj ,否则它不会坚持None。为了解决这个问题,只是......不要None通过fileobj

fileobj必须是真正的文件对象,还是可以是另一个类似文件的对象?那么,下一段说:

  

...新的类实例基于 fileobj ,它可以是常规文件,StringIO对象或任何其他模拟文件的对象。

所以,你去吧。


不幸的是,Python 2.x对于什么算作类似文件的对象并不是100%一致,并且文档并不总是清楚。 (这在3.x中被清理了很多,但如果您使用GAE,这对您没有任何好处。)

如果某些API不喜欢您的文件类对象,因为它没有模拟足够的API,您会发现AttributeError。例如,您可能会收到错误消息,指出您从urllib2.urlopen返回的对象没有seek属性。

解决方法很简单:将其读入内存并创建StringIO。换句话说,只需将fileobj=my_file_obj更改为fileobj=StringIO(my_file_obj.read())


另请注意,GzipFile本身就是一个类似文件的对象。这很重要,因为这意味着您可以将事物链接在一起 - 您可以从GzipFile中创建StringIO,然后从TarFile中创建GzipFile,等等。


  

"线程安全"在Linux上=应用程序将在Web服务器后面,因此可能会同时调用单独的线程进行压缩和/或解压缩。

这不是问题。再次,阅读您链接到的文档:

  

如果您需要从多个线程使用单个LZMAFile实例,则需要使用锁来保护它。

压缩和/或解压缩多个独立的LZMAFile实例不是问题。只有当您想跨线程共享同一个实例时才可以。并且几乎没有充分的理由这样做。


  

Linux应用程序首先从磁盘中读取几千个(数百万个)半随机块压缩数据,然后解压缩每个,然后更改每个未压缩的块,然后压缩更改的块,然后发送到GAE。 / p>

您所谈论的所有压缩机都是流式压缩机。在不压缩文件的情况下,您无法从文件中间解压缩任意块。

这对我来说意味着你实际拥有的是一堆独立压缩的块(无论是在单独的文件中,还是连接成一个单独的文件都不清楚,但并不重要)。

这意味着您无需在任何地方共享解压缩程序或压缩程序。例如:

with lzma.LZMAFile(chunk_path) as f:
    decompressed_chunk = f.read()
new_chunk = alter(decompressed_chunk)
sio = StringIO.StringIO()
with lzma.LZMAFile(fileobj=sio) as f:
    f.write(new_chunk)
compressed_chunk = sio.getvalue()
send_to_gae(compressed_chunk)

线程之间没有什么可分享的。即使200个线程同时执行此操作,即使其中100个正在尝试处理相同的块文件,仍然不会出现问题。唯一需要排序的是最后send_to_gae


  

现在,该应用程序使用zlib并在cherrypy中轻负载下运行完美,但在请求开始并行发生时立即引发zlib错误。

在不了解您的代码的情况下,调试起来非常困难,但我有一个很好的猜测:您通过写入临时文件来进行压缩,而不是使用tempfile中的安全API,你已经彻底改造了轮子,带有独特的错误,这意味着你最终会用线程覆盖彼此。临时文件。


  

关于个别锁定的评论对于bz2

是什么意思

不可否认,这有点令人困惑。它只是说:

  • 线程安全使用单独的锁定机制。

这显然意味着它的线程安全,但为什么你关心它们使用什么锁定机制?什么是"个人锁定机制"呢?

你只能看the source)。

它们的含义是每个BZ2Compressor(和BZ2Decompressor)对象都有自己独立的锁,因此其中一个可以锁定而不会影响其他对象。

如果您还没有处理Python C扩展中的线程,您可能无法理解这是什么。通常,在Python中,每个线程都需要保持GIL来执行任何工作,这意味着一次只能运行一个线程。但是C扩展模块可以释放GIL,同时使用非Python对象(例如,压缩大缓冲区)进行CPU繁重的工作。如果N个线程释放GIL,则最多可以并行运行N + 1个线程,这意味着您可以从您的8核CPU中获得很大的优势,而无需运行多个进程。但是,除非您使用锁保护它们,否则在GIL发布时您无法触摸任何Python对象。

许多发布GIL以加速的模块会创建一个模块锁(有时因为它不容易弄清楚代码可能触及的对象)。这意味着你可以运行一个线程,与执行其他操作的线程并行处理该模块的内容,但是执行该模块的东西不会超过一个线程。

但是如果每个线程只需要触摸一个对象,你就可以为每个对象使用不同的锁,这意味着你可以并行运行任意数量的线程,只要它们全部正常工作即可。在不同的对象上。

如果你尝试同时在两个线程中使用同一个对象,它就不会破坏任何东西;你最终会得到一个等待获取锁定的线程,直到其他线程完成(这比等待GIL更好或更差)。