ping ~ 100,000 台服务器,多线程或多处理哪个更好?

pinging ~ 100,000 servers, is multithreading or multiprocessing better?

我创建了一个简单的脚本,它遍历我需要 ping 和 nslookup 的服务器列表。问题是,ping 可能需要一些时间,尤其是 ping 的服务器比一天中的秒数还要多。

我对编程还很陌生,我知道多处理或多线程可能是使我的工作 运行 更快的解决方案。

我的计划是采用我的服务器列表,并且 1. 将其分成大小均匀的列表,列表的数量与线程/进程相匹配,或者 2. 如果这些选项之一支持它,循环遍历单个list 在完成其先前的 ping 和 nslookup 后将每个新服务器名称传递给线程或进程。这是更可取的,因为它确保我花费最少的时间,如果列表 1 有 200 个离线服务器而列表 6 有 2000 个,它将需要等待使用列表 6 的过程完成,即使所有其他人在那个时候都是免费的点.

  1. 哪个更适合这项任务,为什么?

  2. 如果可能,我将如何确保每个线程或进程具有基本相同的运行时间

代码片段,尽管现在相当简单

import subprocess
import time
server_file = open(r"myfilepath", "r")
initial_time = time.time()
for i in range(1000):
    print(server_file.readline()[0:-1]+ ' '+str(subprocess.run('ping '+server_file.readline()[0:-1]).returncode)) #This returns a string with the server name, and return code,
print(time.time()-initial_time)

出现此问题是因为每次 ping 失败平均需要超过 3 秒。我也知道不放置打印语句会使它更快,但我想在一个小案例中对其进行监控。我正在 ping 100,000 台服务器的结果,这需要定期完成,并且列表会不断增长

TLDR; 多线程 是适合您的解决方案- 线程模块使用线程,多处理模块使用进程。 区别在于线程运行在同一个内存space,而进程有单独的内存。

关于问题1-

对于 IO 任务,例如查询数据库或加载网页,CPU 除了等待答案什么都不做,这是一种资源浪费,因此多线程就是答案 (:

关于问题2-

您只需创建线程池即可同时管理 运行 个线程,而无需费力。

+1 到 Yoel 的回答。线程绝对是要走的路。

我很好奇它实际上会节省多少时间,所以只写了一个脚本来一遍又一遍地 ping google:

import subprocess  
import os

import threading
from multiprocessing import Process

FNULL = open(os.devnull, 'w')
def ping(host):
    command = ['ping', '-c', '1', host]

    return subprocess.call(command, stdout=FNULL, stderr=subprocess.STDOUT) == 0

def ping_hosts(hosts, i):
    for h in hosts:
        ping(h)
        # print "%d: %s" % (i, str(ping(h)))

hosts = ["www.google.com"] * 1000
num_threads = 5

for i in range(num_threads):
    ping_hosts(hosts, i)

#for i in range(num_threads):
#    p = Process(target=ping_hosts, args=(hosts, i))
#    p.start()

#for i in range(num_threads):
#    t = threading.Thread(target=ping_hosts, args=(hosts, i))
#    t.start()

结果:

# no threading no multiprocessing
$ time python ping_hosts.py  # 5000 in a row
real    0m34.657s
user    0m5.817s
sys 0m11.436s

# multiprocessing
$ time python ping_hosts.py
real    0m8.119s
user    0m6.021s
sys 0m16.365s

# threading
$ time python ping_hosts.py
real    0m8.392s
user    0m7.453s
sys 0m16.376s

显然测试中存在缺陷,但很明显,添加任一库都可以显着提升。请注意,节省的费用大致相同。但是,正如 Yoel 所说,因为大多数时候你只是花大部分时间等待线程是要走的路。很简单,只需将您的主机名转储到队列中,并让工作线程池在其中搅动。

为了获得最佳性能,您两者都不想要;有 100,000 个活动作业时,最好使用 异步 处理,在 单个 或可能的少数线程或进程中(但不超过可用的数量核心)。

使用异步 I/O 可以在单个线程中执行许多网络任务,由于节省了上下文切换,很容易达到 100,000 或更高的速率(也就是说,理论上您可以在 1 秒内 ping 100,000 台机器) .

Python 通过 asyncio (here's 对 asyncio 和协程的一个很好的介绍支持异步 I/O。

不要依赖像 ping 这样的外部进程也很重要,因为生成新进程是一项非常昂贵的操作。

aioping 是使用 asyncio 完成的本机 Python ping 示例(注意 ping 实际上是一对 ICMP request/reply 数据包)。让它同时执行多个 ping 应该很容易。

多线程可以 运行 比多处理更快,因为 ping 不是 CPU 密集的。可以运行 大量的线程。下面是我的工作代码

import subprocess
import threading
raw_list = []
def ping(host):
    raw_list.append(host+ ' '+ str((subprocess.run('ping '+host).returncode)))
with open(r'RedactedFilePath', "r") as server_list_file:
    hosts = server_list_file.read()
    hosts_list =hosts.split('\n')
num_threads = 75
number = 0
while number< len(hosts_list):
    print(number)
    for i in range(num_threads):
        t = threading.Thread(target=ping, args=(hosts_list[number+i],))
        t.start()
    t.join()
    number = number +75

可能有更 pythonic 的 ping 方式,这可能会使它更快,因为它不会为每个单独的进程启动子进程

此外,调整适当的线程数取决于机器,更取决于您对线程的处理方式。

当然,当服务器列表不能直接被线程数整除时,应该有一个try语句来阻止错误。

此外,拆分功能应该用于删除分隔符,在我的例子中,我的服务器列表在一个文本文件中,每个文件都在一个新行中,但 csv 将是 .split(',')而 tsv 将是 ('\t') etc