【问题标题】:How many network ports does Linux allow python to use?Linux允许python使用多少个网络端口?
【发布时间】:2015-07-19 17:40:22
【问题描述】:

所以我一直在尝试在 python 中对一些互联网连接进行多线程处理。我一直在使用多处理模块,所以我可以绕过“全局解释器锁”。但似乎系统只给python一个开放的连接端口,或者至少它只允许一次连接发生。这是我所说的一个例子。

*请注意,这是在 linux 服务器上运行的

from multiprocessing import Process, Queue
import urllib
import random

# Generate 10,000 random urls to test and put them in the queue
queue = Queue()
for each in range(10000):
    rand_num = random.randint(1000,10000)
    url = ('http://www.' + str(rand_num) + '.com')
    queue.put(url)

# Main funtion for checking to see if generated url is active
def check(q):
    while True:
        try:
            url = q.get(False)
            try:
                request = urllib.urlopen(url)
                del request
                print url + ' is an active url!'
            except:
                print url + ' is not an active url!'
        except:
            if q.empty():
                break

# Then start all the threads (50)
for thread in range(50):
    task = Process(target=check, args=(queue,))
    task.start()

因此,如果您运行它,您会注意到它在函数上启动了 50 个实例,但一次只运行一个。您可能认为“全局解释器锁”正在执行此操作,但事实并非如此。尝试将函数更改为数学函数而不是网络请求,您将看到所有 50 个线程同时运行。

那么我必须使用套接字吗?或者我可以做些什么来让 python 访问更多端口?或者有什么我没有看到的?让我知道你的想法!谢谢!

*编辑

所以我编写了这个脚本来更好地测试 requests 库。好像我之前没有很好地测试过它。 (我主要用过 urllib 和 urllib2)

from multiprocessing import Process, Queue
from threading import Thread
from Queue import Queue as Q
import requests
import time

# A main timestamp
main_time = time.time()

# Generate 100 urls to test and put them in the queue
queue = Queue()
for each in range(100):
    url = ('http://www.' + str(each) + '.com')
    queue.put(url)

# Timer queue
time_queue = Queue()

# Main funtion for checking to see if generated url is active
def check(q, t_q): # args are queue and time_queue
    while True:
        try:
            url = q.get(False)
            # Make a timestamp
            t = time.time()
            try:
                request = requests.head(url, timeout=5)
                t = time.time() - t
                t_q.put(t)
                del request
            except:
                t = time.time() - t
                t_q.put(t)
        except:
            break

# Then start all the threads (20)
thread_list = []
for thread in range(20):
    task = Process(target=check, args=(queue, time_queue))
    task.start()
    thread_list.append(task)

# Join all the threads so the main process don't quit
for each in thread_list:
    each.join()
main_time_end = time.time()

# Put the timerQueue into a list to get the average
time_queue_list = []
while True:
    try:
        time_queue_list.append(time_queue.get(False))
    except:
        break

# Results of the time
average_response = sum(time_queue_list) / float(len(time_queue_list))
total_time = main_time_end - main_time
line =  "Multiprocessing: Average response time: %s sec. -- Total time: %s sec." % (average_response, total_time)
print line

# A main timestamp
main_time = time.time()

# Generate 100 urls to test and put them in the queue
queue = Q()
for each in range(100):
    url = ('http://www.' + str(each) + '.com')
    queue.put(url)

# Timer queue
time_queue = Queue()

# Main funtion for checking to see if generated url is active
def check(q, t_q): # args are queue and time_queue
    while True:
        try:
            url = q.get(False)
            # Make a timestamp
            t = time.time()
            try:
                request = requests.head(url, timeout=5)
                t = time.time() - t
                t_q.put(t)
                del request
            except:
                t = time.time() - t
                t_q.put(t)
        except:
            break

# Then start all the threads (20)
thread_list = []
for thread in range(20):
    task = Thread(target=check, args=(queue, time_queue))
    task.start()
    thread_list.append(task)

# Join all the threads so the main process don't quit
for each in thread_list:
    each.join()
main_time_end = time.time()

# Put the timerQueue into a list to get the average
time_queue_list = []
while True:
    try:
        time_queue_list.append(time_queue.get(False))
    except:
        break

# Results of the time
average_response = sum(time_queue_list) / float(len(time_queue_list))
total_time = main_time_end - main_time
line =  "Standard Threading: Average response time: %s sec. -- Total time: %s sec." % (average_response, total_time)
print line

# Do the same thing all over again but this time do each url at a time
# A main timestamp
main_time = time.time()

# Generate 100 urls and test them
timer_list = []
for each in range(100):
    url = ('http://www.' + str(each) + '.com')
    t = time.time()
    try:
        request = requests.head(url, timeout=5)
        timer_list.append(time.time() - t)
    except:
        timer_list.append(time.time() - t)
main_time_end = time.time()

# Results of the time
average_response = sum(timer_list) / float(len(timer_list))
total_time = main_time_end - main_time
line = "Not using threads: Average response time: %s sec. -- Total time: %s sec." % (average_response, total_time)
print line

如您所见,它非常适合多线程。实际上,我的大多数测试表明 threading 模块实际上比 multiprocessing 模块快。 (我不明白为什么!)这是我的一些结果。

Multiprocessing: Average response time: 2.40511314869 sec. -- Total time: 25.6876308918 sec.
Standard Threading: Average response time: 2.2179402256 sec. -- Total time: 24.2941861153 sec.
Not using threads: Average response time: 2.1740363431 sec. -- Total time: 217.404567957 sec.

这是在我的家庭网络上完成的,我的服务器上的响应时间要快得多。我认为我的问题已经间接回答了,因为我在一个更复杂的脚本上遇到了问题。所有的建议都帮助我很好地优化了它。谢谢大家!

【问题讨论】:

  • 您是否尝试过使用不同的 python 模块来完成 HTTP 工作,可能是 requests?我们知道urllibisn't thread-safe,虽然我认为这不会影响多进程,但我会尝试使用不同的模块来找出答案。
  • 你怎么知道只有一个进程在运行?我认为数学函数比 http 请求完成得快得多,虽然看起来运行是同步的,但实际上它正在执行许多请求,但由于它们很慢,所以设法清楚地写入标准输出。跨度>
  • @ReutSharabani 好吧,我一直在“htop”中检查它,但它一次只打印一个。如果它实际上正在运行多个进程,它会一次打印出许多进程。
  • 这是我运行脚本时得到的:reut@sharabani:~/python/ports$ pgrep python | wc -l 输出:51
  • @ReutSharabani 是的。这正是我得到的。据我所知,这意味着已经启动了 51 个 python 线程,但这并不意味着所有 51 个线程都在运行。如果你打开“htop”,你会注意到你只有一两个线程在运行。

标签: python linux multithreading python-2.7


【解决方案1】:

它在函数上启动 50 个实例,但一次只运行一个

您误解了 htop 的结果。只有少数(如果有的话)python 副本可以在任何特定实例上运行。它们中的大多数将被阻塞等待网络 I/O。

这些进程实际上是并行运行的。

尝试将函数更改为数学函数而不是网络请求,您将看到所有 50 个线程同时运行。

将任务更改为数学函数仅说明了 CPU 绑定(例如数学)和 IO 绑定(例如 urlopen)进程之间的区别。前者总是可运行的,后者很少可运行。

它一次只打印一个。如果它实际上正在运行多个进程,它将一次打印出许多进程。

它一次打印一个,因为您正在向终端写入行。因为这些行是无法区分的,所以您无法判断它们是全部由一个线程编写的,还是每个由一个单独的线程依次编写的。

【讨论】:

  • 所以我的问题是——一台 linux 机器可以同时拥有多个 IO 绑定进程吗?我知道每个端口似乎都仅限于一个进程。我的系统是否只为“urlopen”打开一个端口?难道我不能以某种方式手动让每个进程在新端口上自行打开,也许会取得更大的成功吗?
  • 1) 是的,当然 Linux 可以有多个 IO-bound 进程。事实上,这正是 htop 向您展示的内容——在您启动的 50 个进程中,它们中的大多数都在等待 IO。 2)“我知道每个端口似乎都仅限于一个进程。”梦呓。没有这样的限制。 3)“取得更大的成功”——你究竟想要完成什么,是什么让你认为你还没有完成它?
  • 如果两个线程同时尝试打印,通常会导致每行打印多个语句。
  • 回答您关于我要完成的工作的评论。我试图让多个请求同时发生。例如:每个线程可以同时下载一些东西。显然不像你之前指出的那样。
  • 你成功了!您有多个请求同时发生。每个线程同时下载。您的程序正在按照您的预期运行。
【解决方案2】:

首先,使用multiprocessing 并行化网络I/O 是一种矫枉过正。使用内置的threading 或像gevent 这样的轻量级greenlet 库是一个更好的选择,而且开销更少。 GIL 与阻塞 IO 调用无关,因此您完全不必担心。

其次,如果您正在监视 stdout,则查看您的子进程/线程/greenlets 是否并行运行的一种简单方法是在子进程/线程/greenlets 生成之后,在函数的最开头打印出一些内容。例如,像这样修改您的check() 函数

def check(q):
    print 'Start checking urls!'
    while True:
        ...

如果您的代码正确,您应该会看到在打印出任何url + ' is [not] an active url!' 之前打印出许多Start checking urls! 行。它可以在我的机器上运行,所以看起来你的代码是正确的。

【讨论】:

  • 我认为这里的问题不是“检查是否并行运行?”。它是“urllib.urlopen 是否并行运行?”.
  • 如果check() 并行运行,那么urllib.urlopen() 将并行运行(除非他的文件描述符设置有严重问题,我对此表示怀疑)。如果您想要证明,请按顺序运行check()(即,将for thread in range... 块替换为check(queue),您会发现url 检查需要更多时间。
  • 我认为它确实并行运行,我只是说你的回答忽略了这个问题。他明确表示,数学计算对他来说确实是并行运行的。
  • @oxymor0n 就像 Reut 说的,我不担心我可以并行启动多少正常功能。我的问题是我的系统似乎一次将 python 限制为一个网络请求。所以我可以让许多'检查'并行运行,但它们都互相等待完成请求,因为它一次只允许一个。
  • Reut Sharabani 我现在明白你的意思了。 @TysonU 您比较了并行和顺序版本的速度吗?在我的机器上,你的代码运行完美,所以这不是问题的原因。如果并行和顺序版本以相同的速度运行,请检查文件描述符的数量
【解决方案3】:

看来您的问题实际上与gethostbyname(3) 的串行行为有关。这在this SO thread 中进行了讨论。

试试这个使用 Twisted 异步 I/O 库的代码:

import random
import sys
from twisted.internet import reactor
from twisted.internet import defer
from twisted.internet.task import cooperate
from twisted.web import client

SIMULTANEOUS_CONNECTIONS = 25
# Generate 10,000 random urls to test and put them in the queue
pages = []
for each in range(10000):
    rand_num = random.randint(1000,10000)
    url = ('http://www.' + str(rand_num) + '.com')
    pages.append(url)

# Main function for checking to see if generated url is active
def check(page):
    def successback(data, page):
        print "{} is an active URL!".format(page)

    def errback(err, page):
        print "{} is not an active URL!; errmsg:{}".format(page, err.value)

    d = client.getPage(page, timeout=3) # timeout in seconds
    d.addCallback(successback, page)
    d.addErrback(errback, page)
    return d

def generate_checks(pages):
    for i in xrange(0, len(pages)):
        page = pages[i]
        #print "Page no. {}".format(i)
        yield check(page)

def work(pages):
    print "started work(): {}".format(len(pages))
    batch_size = len(pages) / SIMULTANEOUS_CONNECTIONS
    for i in xrange(0, len(pages), batch_size):
        task = cooperate(generate_checks(pages[i:i+batch_size]))

print "starting..."
reactor.callWhenRunning(work, pages)
reactor.run()

【讨论】:

    猜你喜欢
    • 2010-09-11
    • 2021-01-05
    • 1970-01-01
    • 2011-05-16
    • 2013-01-08
    • 2019-01-21
    • 2012-10-19
    • 1970-01-01
    相关资源
    最近更新 更多