Python:为什么多线程函数比非多线程函数慢?

5

你好,我正在尝试计算前一万个质数。

我首先进行非线程计算,然后将计算分为1至5000和5001至10000。我期望使用线程可以使计算速度显著加快,但输出结果如下:

    --------Results--------
Non threaded Duration:  0.012244000000000005 seconds
Threaded Duration:  0.012839000000000017 seconds

实际上并没有太大的区别,只是线程函数稍微慢了一点。

有什么问题吗?

以下是我的代码:

import math
from threading import Thread

def nonThreaded():
    primeNtoM(1,10000)


def threaded():
    t1 = Thread(target=primeNtoM, args=(1,5000))
    t2 = Thread(target=primeNtoM, args=(5001,10000))
    t1.start()
    t2.start()
    t1.join()
    t2.join()


def is_prime(n):
    if n % 2 == 0 and n > 2: 
        return False
    for i in range(3, int(math.sqrt(n)) + 1, 2):
        if n % i == 0:
            return False
    return True

def primeNtoM(n,m):
    L = list()
    if (n > m):
        print("n should be smaller than m")
        return
    for i in range(n,m):
        if(is_prime(i)):
                L.append(i)

if __name__ == '__main__':
    import time
    print("--------Nonthreaded calculation--------")
    nTstart_time = time.clock()
    nonThreaded()
    nonThreadedTime = time.clock() - nTstart_time

    print("--------Threaded calculation--------")

    Tstart_time = time.clock()
    threaded()
    threadedTime = time.clock() - Tstart_time

    print("--------Results--------")
    print ("Non threaded Duration: ",nonThreadedTime, "seconds")
    print ("Threaded Duration: ",threadedTime, "seconds")
2个回答

12

来源:https://wiki.python.org/moin/GlobalInterpreterLock

在CPython中,全局解释器锁(GIL)是一个互斥锁,它防止多个本机线程同时执行Python字节码。这个锁主要是必要的,因为CPython的内存管理不是线程安全的。(然而,由于GIL的存在,其他特性已经增长依赖于它所强制执行的保证。)

这意味着:由于这是CPU密集型操作,并且Python不支持线程安全,它不允许在同一进程中同时运行多个字节码。因此,您的线程会交替运行,切换开销就是额外的时间。


你的回答比我的好多了,所以我删掉了 :) - Adam Smith
老实说我不知道,但我读过有一个 multiprocessing 模块。它可以产生分叉,而在Unix环境中,进程分叉只比线程分叉慢一点(上下文切换更快、更轻)。然而,在Windows中,这将再次成为一个问题。 - Luis Masuelli

4
您可以使用multiprocessing模块,它提供以下结果:
('Non threaded Duration: ', 0.016599999999999997, 'seconds')
('Threaded Duration: ', 0.007172000000000005, 'seconds')

只需对您的代码进行这些更改(将“Thread”更改为“Process”),即可实现以下效果:

import math
#from threading import Thread
from multiprocessing import Process

def nonThreaded():
    primeNtoM(1,10000)


def threaded():
    #t1 = Thread(target=primeNtoM, args=(1,5000))
    #t2 = Thread(target=primeNtoM, args=(5001,10000))
    t1 = Process(target=primeNtoM, args=(1,5000))
    t2 = Process(target=primeNtoM, args=(5001,10000))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

通过生成实际的操作系统进程而不是使用进程内线程,您可以消除@Luis Masuelli的答案中讨论的GIL问题。

multiprocessing是支持使用类似于线程模块的API来生成进程的软件包。multiprocessing软件包提供本地和远程并发,通过使用子进程而不是线程有效地规避了全局解释器锁定。因此,multiprocessing模块允许程序员充分利用给定计算机上的多个处理器。它适用于Unix和Windows。


但是我创建的是一个新进程而不是线程,我错了吗? - arnoapp
通过创建多个进程,可以在多个核心上运行代码,而不会受到全局解释器锁的影响。 - bgporter
请记住,在Unix环境中,在进程之间进行上下文切换并不比在同一进程中进行线程切换更加困难。在Unix中,PCB是轻量级的(但在Windows中不是,因此在Windows中这又成为了一个问题)。 - Luis Masuelli
好的,但事实上这不是我试图实现的解决方案。那么哪种类型的代码适合用于线程? - arnoapp
1
@AzzUrr1,如果你想在Python中得到令人满意的解决方案,使用线程是不够的。你必须使用多个进程。 - dano
@AzzUrr1 IO绑定操作。所有I/O原语在等待系统调用完成时都会放弃GIL,因此GIL与I/O性能无关。 - nitely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接