何时使用线程以及使用多少线程

时间:2017-05-02 21:08:07

标签: python multithreading python-multithreading

我有一个工作项目。我们编写了一个模块,并在那里作为#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次测试并平均了时间。以下是图表中的结果:

Test Time vs. Number of Threads

关于线程和此测试的问题如下:

  1. 这个测试是否可以很好地表示如何使用线程?这不是服务器/客户端情况。在效率方面,当您没有为客户提供服务或处理正在添加到队列中的作业/工作时,最好避免并行吗?

  2. 如果对1的回答是"否,则此测试不是应该使用线程的地方。"那么什么时候?一般来说。

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

  4. 我注意到随着工作线程比率接近1:1,设置线程所需的时间变得更长。那么线程只在你创建一次线程并且尽可能长时间保持活动来处理可能比计算它们的速度更快的请求时才有用吗?

3 个答案:

答案 0 :(得分:4)

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

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

这不是这种情况;计算素数是cpu-bound。

答案 1 :(得分:2)

你认为多线程是一个值得怀疑的举动是正确的。多线程,就目前而言,非常棒,在正确的应用程序中可以在运行时间方面产生世界性的差异。

然而,另一方面,它还为实现它的任何程序增加了额外的复杂性(特别是在python中)。使用多线程时还需要考虑时间惩罚,例如在执行上下文切换时或实际创建线程所花费的时间。

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

简而言之,除非你需要,否则我会保存多线程。例如,像基因排序那样庞大的数据集(Python中的重要数据集)从中受益匪浅,因为多个线程可以帮助同时处理这些大量文件。在你的情况下,它看起来并不像手段的结果。希望这有帮助

答案 2 :(得分:1)

  

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

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

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

如果您想要的是使用多于1个CPU的核心来加速执行,请查看多处理模块。