python 多进程和多线程的区别_单进程多线程

python 多进程和多线程的区别_单进程多线程在多核CPU的时代,使用多线程或多进程能够充分利用CPU多核性能来提高程序的执行效率,但Python的多线程为什么有时候耗时比单一线程更长,为什么多数情况下推荐使用Python多进程替代多线程,针对这些问题本文将重点介绍下Python的多进程和多线程区别和应用场景的选取。

基于Python的多进程和多线程对比和应用分析"

前言

在多核CPU的时代,使用多线程或多进程能够充分利用CPU多核性能来提高程序的执行效率,但Python的多线程为什么有时候耗时比单一线程更长,为什么多数情况下推荐使用Python多进程替代多线程,针对这些问题本文将重点介绍下Python的多进程和多线程区别和应用场景的选取。


进程和线程介绍

程序为存储在磁盘上的可执行文件,当把程序加载到内存中并被操作系统调用,则拥有了生命周期,进程即为运行中的程序。一个进程可以并行运行多个线程,每个线程执行不同的任务,也就是说线程是进程的组成部分。当一个进程启动时至少要执行一个任务,因此至少有一个主线程,由主线程再创建其他的子线程。多线程的执行方式和多进程相似,由操作系统在多个线程之间快速切换,让每个线程都短暂的交替运行,看起来像同时执行一样。当然,多核CPU可真正意义上实现多线程或多进程的同时执行。

python 多进程和多线程的区别_单进程多线程

进程和线程之间存在不同的特点。每个进程拥有自己的地址空间、内存和数据栈,由操作系统管理所有的进程,并为其合理分配执行时间。由于进程间资源相互独立,不同进程之间需要通过IPC(进程间通信)方式共享信息,但单个进程崩溃时不会导致系统崩溃。而多线程是在同一个进程下执行的,共享同一片数据空间,相比于进程而言,线程间的信息共享更加容易,但当一个线程崩溃时会导致整个进程崩溃。


Python GIL

Python代码的执行由Python解释器进行控制。目前Python的解释器有多种,如CPython、PyPy、Jython等,其中CPython为最广泛使用的Python解释器。理论上CPU是多核时支持多个线程同时执行,但在Python设计之初考虑到在Python解释器的主循环中执行Python代码,于是CPython中设计了全局解释器锁GIL(Global Interpreter Lock)机制()用于管理解释器的访问,Python线程的执行必须先竞争到GIL权限才能执行。因此无论是单核还是多核CPU,任意给定时刻只有一个线程会被Python解释器执行,这也是为什么在多核CPU上,Python的多线程有时效率并不高的根本原因。

注:关于Python解释器,简单的说,任何一种编程语言都需要用另一种语言来实现它,比如C语言是用机器语言来实现的。所以,Python作为一门编程语言,根据实现方式不同分为了CPyhton、Pypy、Jython等。


执行对比

Python多任务的解决方案主要由这么几种:

  • 启动多进程,每个进程只有一个线程,通过多进程执行多任务;
  • 启动单进程,在进程内启动多线程,通过多线程执行多任务;
  • 启动多进程,在每个进程内再启动多个线程,同时执行更多的任务;

由于第三种方法模型复杂,实际较少使用,本文针对前两种方案在双核CPU(Intel(R) Core(TM)2 Duo CPU E7500@ 2.93GHz)硬件平台上,Linux操作系统下使用Python2.7对计算密集型和I/O密集型任务进行执行效率测试。

(1)计算密集型测试。计算密集型任务的特点是需要进行大量的计算,在整个时间片内始终消耗CPU的资源。由于GIL机制的原因多线程中无法利用多核参与计算,但多线程之间切换的开销时间仍然存在,因此多线程比单一线程需要更多的执行时间。而多进程中有各自独立的GIL锁互不影响,可以充分利用多核参与计算,加快了执行速度。

测试代码如下:
#!/usr/bin/python
from threading import Thread
from multiprocessing import Process,Manager
from timeit import timeit
def count(n):
    while n > 0:
        n-=1
def test_normal():
    count(1000000)
    count(1000000)
def test_Thread():
    t1 = Thread(target=count,args=(1000000,))
    t2 = Thread(target=count,args=(1000000,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()            
def test_Thread():
    t1 = Thread(target=count,args=(1000000,))
    t2 = Thread(target=count,args=(1000000,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()        
def test_Process():
    t1 = Process(target=count,args=(1000000,))
    t2 = Process(target=count,args=(1000000,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()        
if __name__ == '__main__':
    print "test_normal",timeit('test_normal()','from __main__ import test_normal',number=10)
    print "test_Thread",timeit('test_Thread()','from __main__ import test_Thread',number=10)
    print "test_Process",timeit('test_Process()','from __main__ import test_Process',number=10)

执行结果如下:

test_normal 1.19412684441

test_Thread 1.88164782524

test_Process 0.687992095947

注:Join函数为逐个执行进程/线程,直到子进程/线程结束后主进程/线程才退出

(2)I/O密集型测试。I/O密集型任务的特点是CPU消耗很少,任务大部分时间都在等待I/O操作的完成(I/O速度远低于CPU和内存速度)。此处将count()函数内容替换为time.sleep(0.5),使用挂起方式模拟I/O阻塞。当挂起时I/O任务释放GIL,此时允许其他并发线程执行,提升了运行程序的效率。由于多进程创建和销毁的开销比多线程大,此处创建两个线程和进程时可发现线程执行效率更高,当线程和进程数量增加至100个时差距更加明显。

2个线程和进程测试代执行结果如下:

test_normal 10.0101339817

test_Thread 5.00919413567

test_Process 5.03114795685

增加至100个线程和进程测试代码:

def test_Thread():
    l = []
    for i in range(100):
        p = Thread(target=count,args=(1000000,))
        l.append(p)
        p.start()
    for j in l:
        j.join()
        
def test_Process():
    l = []
    for i in range(100):
        p = Process(target=count,args=(1000000,))
        l.append(p)
        p.start()
    for j in l:
        j.join()

执行结果如下:

test_Thread 5.15325403214

test_Process 5.84798789024


总结

由于Python的GIL限制,多线程更适合于I/O密集型应用(如典型的爬虫程序)。而对于计算密集型的应用,为了实现更好的并行性,可使用多进程以使CPU的其他内核加入执行。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13894.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注