Skip to main content
 首页 » 编程设计

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

2025年05月04日14think

我有一个工作项目。我们已经编写了一个模块,并作为#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,设置线程所花费的时间变得更长。那么,线程仅在您创建一次线程并尽可能长时间地保持其事件状态以处理可能比计算速度更快排队的请求时才有用吗?

请您参考如下方法:

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

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

这里不是这种情况;计算素数受 CPU 限制。