python - Python多线程编程有什么优势?

标签 python multithreading eventlet

当我听说多线程编程时,我想到了加速我的程序的机会,但事实并非如此?

import eventlet
from eventlet.green import socket
from iptools import IpRangeList


class Scanner(object):
    def __init__(self, ip_range, port_range, workers_num):
        self.workers_num = workers_num or 1000
        self.ip_range = self._get_ip_range(ip_range)
        self.port_range = self._get_port_range(port_range)
        self.scaned_range = self._get_scaned_range()

    def _get_ip_range(self, ip_range):
        return [ip for ip in IpRangeList(ip_range)]

    def _get_port_range(self, port_range):
        return [r for r in range(*port_range)]

    def _get_scaned_range(self):
        for ip in self.ip_range:
            for port in self.port_range:
                yield (ip, port)

    def scan(self, address):
        try:
            return bool(socket.create_connection(address))
        except:
            return False

    def run(self):
        pool = eventlet.GreenPool(self.workers_num)
        for status in pool.imap(self.scan, self.scaned_range):
            if status:
                yield True

    def run_std(self):
        for status in map(self.scan, self.scaned_range):
            if status:
                yield True


if __name__ == '__main__':
    s = Scanner(('127.0.0.1'), (1, 65000), 100000)
    import time
    now = time.time()
    open_ports = [i for i in s.run()]
    print 'Eventlet time: %s (sec) open: %s' % (now - time.time(),
                                                len(open_ports))
    del s
    s = Scanner(('127.0.0.1'), (1, 65000), 100000)
    now = time.time()
    open_ports = [i for i in s.run()]
    print 'CPython time: %s (sec) open: %s' % (now - time.time(),
                                                len(open_ports))

结果:

Eventlet time: -4.40343403816 (sec) open: 2
CPython time: -4.48356699944 (sec) open: 2

我的问题是,如果我不在我的笔记本电脑上而是在服务器上运行这段代码,并设置更多的 worker 值,它会比 CPython 版本运行得更快吗? 线程的优点是什么?

添加: 所以我使用原始 cpython 的线程重写了应用程序

import socket
from threading import Thread
from Queue import Queue

from iptools import IpRangeList

class Scanner(object):
    def __init__(self, ip_range, port_range, workers_num):
        self.workers_num = workers_num or 1000
        self.ip_range = self._get_ip_range(ip_range)
        self.port_range = self._get_port_range(port_range)
        self.scaned_range = [i for i in self._get_scaned_range()]

    def _get_ip_range(self, ip_range):
        return [ip for ip in IpRangeList(ip_range)]

    def _get_port_range(self, port_range):
        return [r for r in range(*port_range)]

    def _get_scaned_range(self):
        for ip in self.ip_range:
            for port in self.port_range:
                yield (ip, port)

    def scan(self, q):
        while True:
            try:
                r = bool(socket.create_conection(q.get()))
            except Exception:
                r = False
            q.task_done()

    def run(self):
        queue = Queue()
        for address in self.scaned_range:
                queue.put(address)
        for i in range(self.workers_num):
                worker = Thread(target=self.scan,args=(queue,))
                worker.setDaemon(True)
                worker.start()
        queue.join()


if __name__ == '__main__':
    s = Scanner(('127.0.0.1'), (1, 65000), 5)
    import time
    now = time.time()
    s.run()
    print time.time() - now

结果是

 Cpython's thread: 1.4 sec

而且我认为这是一个非常好的结果。我把 nmap 扫描时间作为标准:

$ nmap 127.0.0.1 -p1-65000

Starting Nmap 5.21 ( http://nmap.org ) at 2012-10-22 18:43 MSK
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00021s latency).
Not shown: 64986 closed ports
PORT      STATE SERVICE
53/tcp    open  domain
80/tcp    open  http
443/tcp   open  https
631/tcp   open  ipp
3306/tcp  open  mysql
6379/tcp  open  unknown
8000/tcp  open  http-alt
8020/tcp  open  unknown
8888/tcp  open  sun-answerbook
9980/tcp  open  unknown
27017/tcp open  unknown
27634/tcp open  unknown
28017/tcp open  unknown
39900/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.85 seconds

现在我的问题是:据我所知,线程是如何在 Eventlet 中实现的,这不是线程,而是 Eventlet 的特殊事物,为什么它们不能加快任务速度?

Eventlet 被许多主要项目使用,如 OpenStack 等。 但为什么?只是以异步方式或其他方式对数据库进行繁重的查询?

最佳答案

Cpython 线程:

  • 每个 cpython 线程映射到操作系统级线程(用户空间中的轻量级进程/pthread)

  • 如果有多个cpython线程并发执行python代码:由于全局解释器锁,一次只有一个cpython线程可以解释python。其余线程在需要解释 python 指令时将被阻塞在 GIL 上。当有很多 python 线程时,这会大大降低速度。

  • 现在,如果您的 Python 代码大部分时间用于网络操作(发送、连接等):在这种情况下,争用 GIL 来解释代码的线程将会减少。所以GIL的效果还算不错。

Eventlet/绿色线程:

  • 从上面我们知道 cpython 在线程方面有性能限制。 Eventlets 试图通过使用在单个内核上运行的单个线程并为所有内容使用非阻塞 i/o 来解决这个问题。

  • 绿色线程不是真正的操作系统级线程。它们是并发的用户空间抽象。最重要的是,N 个绿色线程将映射到 1 个操作系统线程。这避免了 GIL 问题。

  • 绿色线程相互合作让步,而不是抢先调度。 对于网络操作,套接字库在运行时进行了修补(猴子修补),因此所有调用都是非阻塞的。

  • 因此,即使您创建了一个 eventlet 绿色线程池,您实际上也只创建了一个操作系统级别的线程。这个单一的操作系统级线程将执行所有的事件。这个想法是,如果所有网络调用都是非阻塞的,那么在某些情况下这应该比 python 线程更快。

总结

对于上面的程序,“真正的”并发性恰好比 eventlet 模型(单线程在 1 个处理器上运行)更快(cpython 版本,5 个线程在多个处理器上运行)。

有一些 cpython 工作负载在许多线程/核心上表现不佳(例如,如果您有 100 个客户端连接到服务器,并且每个客户端一个线程)。 Eventlet 是适用于此类工作负载的优雅编程模型,因此在多个地方都有使用。

关于python - Python多线程编程有什么优势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12996254/

相关文章:

python - 隐藏 stdlib 包名称的子包中的绝对导入失败

java - 通过 toStrict() 将 HttpEntity 转换为 String 后导致延迟的原因

java - Java 中 Do-While 循环的 ExecutorService

python - 除非 waitall(),否则为什么 eventlet GreenPool 在 spawn_n 之后不调用 func?

python - docker 容器将监听队列大小增加到 128 以上

python - 如何仅使用 OpenCV/numpy/scipy 在 python 中计算色彩空间 Delta E?

multithreading - 当客户端是多线程时,客户端对 HTTP 429 的正确 react 是什么?

python - 有哪些用 Python 编写的现代 Comet 服务器?

python - 扭曲的 gevent eventlet - 我什么时候使用它们

python - 用于 pydrake 模拟的 MultibodyPlant、SceneGraph、Context 和 Simulator 的结构