【问题标题】:When to use threading and how many threads to use何时使用线程以及使用多少线程
【发布时间】:2017-05-02 21:08:06
【问题描述】:

我有一个工作项目。我们编写了一个模块,并在那里作为#TODO 来实现线程以改进模块。我是一个相当新的 python 程序员,并决定尝试一下。在学习和实现线程时,我遇到了类似于How many threads is too many? 的问题,因为我们有一个大约需要处理 6 个对象的队列,那么为什么要创建 6 个线程(或任何线程)来处理列表中的对象还是在处理时间可以忽略不计时排队? (每个对象最多需要大约 2 秒来处理)

所以我做了一个小实验。我想知道使用线程是否有性能提升。请参阅下面的 python 代码:

import threading
import queue
import math
import time

results_total = []
results_calculation = []
results_threads = []

class MyThread(threading.Thread):
    def __init__(self, thread_id, q):
        threading.Thread.__init__(self)
        self.threadID = thread_id
        self.q = q

    def run(self):
        # print("Starting " + self.name)
        process_data(self.q)
        # print("Exiting " + self.name)


def process_data(q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            potentially_prime = True
            data = q.get()
            queueLock.release()
            # check if the data is a prime number
            # print("Testing {0} for primality.".format(data))
            for i in range(2, int(math.sqrt(data)+1)):
                if data % i == 0:
                    potentially_prime = False
                    break
            if potentially_prime is True:
                prime_numbers.append(data)
        else:
            queueLock.release()

for j in [1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100, 150, 250, 500,
          750, 1000, 2500, 5000, 10000]:
    threads = []
    numberList = list(range(1, 10001))
    queueLock = threading.Lock()
    workQueue = queue.Queue()
    numberThreads = j
    prime_numbers = list()
    exitFlag = 0

    start_time_total = time.time()
    # Create new threads
    for threadID in range(0, numberThreads):
        thread = MyThread(threadID, workQueue)
        thread.start()
        threads.append(thread)

    # Fill the queue
    queueLock.acquire()
    # print("Filling the queue...")
    for number in numberList:
        workQueue.put(number)
    queueLock.release()
    # print("Queue filled...")
    start_time_calculation = time.time()
    # Wait for queue to empty
    while not workQueue.empty():
        pass

    # Notify threads it's time to exit
    exitFlag = 1

    # Wait for all threads to complete
    for t in threads:
        t.join()
    # print("Exiting Main Thread")
    # print(prime_numbers)
    end_time = time.time()
    results_total.append(
            "The test took {0} seconds for {1} threads.".format(
                end_time - start_time_total, j)
            )
    results_calculation.append(
            "The calculation took {0} seconds for {1} threads.".format(
                    end_time - start_time_calculation, j)
            )
    results_threads.append(
            "The thread setup time took {0} seconds for {1} threads.".format(
                    start_time_calculation - start_time_total, j)
            )
for result in results_total:
    print(result)
for result in results_calculation:
    print(result)
for result in results_threads:
    print(result)

这个测试找到 1 到 10000 之间的素数。这个设置几乎是从https://www.tutorialspoint.com/python3/python_multithreading.htm 中获取的,但我不是打印一个简单的字符串,而是让线程找到素数。这实际上不是我的真实世界应用程序,但我目前无法测试我为模块编写的代码。我认为这是衡量附加线程效果的一个很好的测试。我的真实世界应用程序处理与多个串行设备的通信。我进行了 5 次测试并取平均值。以下是图表中的结果:

我关于线程和这个测试的问题如下:

  1. 这个测试是否能很好地说明应该如何使用线程?这不是服务器/客户端情况。就效率而言,当您不为客户提供服务或处理添加到队列中的任务/工作时,避免并行处理会更好吗?

  2. 如果对 1 的回答是“否,此测试不适合使用线程”。那么什么时候呢?一般来说。

  3. 如果对 1 的回答是“是的,在这种情况下可以使用线程。”,为什么添加线程最终会花费更长的时间并很快达到平稳状态?相反,为什么要使用线程,因为它比循环计算要长很多倍。

我注意到随着工作与线程的比率接近 1:1,设置线程所需的时间变得更长。那么,线程仅在您创建一次线程并尽可能长时间保持活动状态以处理排队速度可能快于计算速度的请求时有用吗?

【问题讨论】:

  • 这个问题确实应该有一个与主题相关的名称。这个想法是为了帮助将来有同样问题的人提供答案。

标签: python multithreading python-multithreading


【解决方案1】:

不,这不是使用线程的好地方。

通常,您希望在代码受 IO 限制的情况下使用线程;也就是说,它花费大量时间等待输入或输出。一个例子可能是从一个 URL 列表中并行下载数据;代码可以开始从下一个 URL 请求数据,同时仍在等待前一个 URL 返回。

这里不是这样;计算素数是 cpu-bound。

【讨论】:

【解决方案2】:

您认为多线程是一个值得商榷的举动是对的,这是有充分理由的。就目前而言,多线程非常棒,并且在正确的应用程序中可以在运行时间上产生天壤之别。

然而,另一方面,它也给任何实现它的程序增加了额外的复杂性(尤其是在 python 中)。使用多线程时还需要考虑时间损失,例如在执行上下文切换或实际创建线程所需的时间时发生的时间损失。

当您的程序必须处理成千上万的资源密集型任务时,这些时间惩罚是疏忽的,因为您从多线程中节省的时间远远超过了准备线程所需的一点时间。不过,对于您的情况,我不确定您的需求是否满足这些要求。我没有深入了解您正在处理什么类型的对象,但您说它们只花了大约 2 秒,这并不可怕,您还说一次只处理 6 个项目。所以平均而言,我们可以预期脚本的主要部分运行 12 秒。在我看来,这对于多线程来说是没有必要的,因为它需要一两秒钟来准备好线程,然后将指令传递给它们,而在一个线程中,你的 python 脚本在那个时候已经很好地处理了它的第二个对象.

简而言之,除非您需要,否则我会保存多线程。例如,用于基因测序的大型数据集(Python 中的大事)从中受益匪浅,因为多个线程可以帮助同时处理这些海量文件。在你的情况下,看起来目的并不能证明手段是合理的。希望这会有所帮助

【讨论】:

    【解决方案3】:

    python 中的线程用于同时运行多个线程(任务、函数调用)。请注意,这并不意味着它们在不同的 CPU 上执行。如果 Python 线程已经使用 100% CPU 时间,它不会让你的程序更快。在这种情况下,您可能想研究并行编程。

    来自:https://en.wikibooks.org/wiki/Python_Programming/Threading

    这是由于称为 GIL 的机制。正如 Daniel 指出的那样,python 中的线程仅在您具有 IO 绑定代码时才有用。但话又说回来,对于 IO 绑定代码,最好使用在某个事件循环之上运行的轻量级线程(使用 gevent、eventlet、asyncio 或类似的),因为这样您就可以轻松运行 100 次(甚至更多)并行操作每个线程的开销很小。

    如果您想要使用超过 1 个 CPU 核心来加快执行速度,请查看多处理模块。

    【讨论】:

    • Python 中的线程确实利用了多个 CPU 内核(在大多数用例下)。它没有利用多个CPU。 Async 并没有真正并行运行。例如,它只是利用了线程等待服务器响应时发生的空闲时间。为了获得最佳性能,最好将异步与多线程结合起来,有时甚至结合多处理。
    • @IonutHulub 你错了。在大多数用例中,线程不会使用超过 1 个 CPU 内核,尤其是在处理与此问题有关的任何类型的计算时。 Here你可以了解更多。你似乎也误解了我写的关于异步的内容——因为我从未说过它允许并行处理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-22
    • 2011-12-30
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多