温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Python多进程,多线程及GIL全局解释器锁实例分析

发布时间:2022-07-19 11:30:26 来源:亿速云 阅读:141 作者:iii 栏目:开发技术

这篇文章主要讲解了“Python多进程,多线程及GIL全局解释器锁实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python多进程,多线程及GIL全局解释器锁实例分析”吧!

    1. 并发与并行

    • 所谓的并行(Parallelism),就是多个彼此独立的任务可以同时一起执行,彼此并不相互干扰,并行强调的是同时且独立的运行,彼此不需要协作。

    • 而所谓并发(Concurrency),则是多个任务彼此交替执行,但是同一时间只能有一个处于运行状态,并发执行强调任务之间的彼此协作。

    并发通常被误解为并行,并发实际是隐式的调度独立的代码,以协作的方式运行。比如在等待IO线程完成IO操作之前,可以启动IO线程之外的其他独立代码同步执行其他操作。

    关于并发、并行的图示,如下:

    Python多进程,多线程及GIL全局解释器锁实例分析

    由于CPython解释器中的全局解释器锁(GIL,Global Interpreter Lock)的存在,所以Python中的并行实际上是一种假并行,并不是真正意义上的同时独立运行。

    2. 线程与进程的应用场景

    进程(Process)是操作系统层面的一个抽象概念,它是运行代码创建出来的、加载到内存中运行的程序。电脑上通常运行着多个进程,这些进程之间是彼此独立运行的。

    线程(Thread)是操作系统可以调度的最小运行程序单位,其包含在进程中,一个进程中通常至少包含1个线程,一些进程中会包含多个线程。多个线程运行的都是父进程的相同代码,理想情况下,这些线程是并行执行的,但由于GIL的存在,所以它们实际上是交替执行的,并不是真正意义上的独立、并行的执行的。

    下表是进程与线程的对比:

    Python多进程,多线程及GIL全局解释器锁实例分析

    对于IO密集型的操作,更适合使用多线程编程的方式来解决问题;对于CPU密集型的操作,则更适合使用多进程编程的方式来解决问题。

    2.1. 并行/并发编程相关的技术栈

    Python中提供了一些模块用于实现并行/并发编程,具体如下所示:

    threading:Python中进行多线程编程的标准库,是一个对_thread进行再封装的高级模块。multiprocessing:类似于threading模块,提供的API接口也与threading模块类似,不同的是它进行多进程编程。concurrent.futures:标准库中的一个模块,在线程编程模块的基础上抽象出来的更高级实现,且该模块的编程为异步模式。queue:任务队列模块,queue中提供的队列是线程安全的,所以可以使用这个模块进行线程之间进行安全的数据交换操作。不支持分布式。celery:一个高级的分布式任务队列,通过multiprocessing模块或者gevent模块可以实现队列中人物的并发执行。支持多节点之间的分布式计算。 2.2. 通过编码比较多进程与多线程的执行效果

    在下面的代码中,定义了两个函数:only_sleep()以及crunch_numbers(),前者用于模拟IO密集型操作(需要频繁中断),后者用于模拟CPU密集型操作。

    然后在串行调用、多线程的方式调用、多进程的方式调用,三种不同的执行环境中,比较各个函数的执行效率情况。

    具体代码以及执行结果如下所示:

    import time
    import datetime
    import logging
    import threading
    import multiprocessing
    
    FORMAT = "%(asctime)s [%(processName)s %(process)d] %(threadName)s %(thread)d <=%(message)s=>"
    logging.basicConfig(format=FORMAT, level=logging.INFO, datefmt='%H:%M:%S')
    def only_sleep():
    	"""
    	模拟IO阻塞型操作,此时多线程优势明显
    
    	:return:
    	"""
    	logging.info('in only_sleep function')
    	time.sleep(1)
    def crunch_numbers():
    	"""
    	模拟CPU密集型操作,此时多进程优势明显
    	:return:
    	"""
    	logging.info('in crunch_numbers function')
    	x = 0
    	while x < 1000000:
        	x += 1
    if __name__ == '__main__':
    	work_repeat = 4
    	print('==>> only_sleep function test')
    	count = 0
    	for func in (only_sleep, crunch_numbers):
        	count += 1
    	    # run tasks serially
        	start1 = datetime.datetime.now()
    	    for i in range(work_repeat):
        	    func()
    	    stop1 = datetime.datetime.now()
        	delta1 = (stop1 - start1).total_seconds()
    	    print('Serial Execution takes {} seconds~'.format(delta1))
    
        	# run tasks with multi-threads
    	    start2 = datetime.datetime.now()
        	thread_lst = [threading.Thread(target=func, name='thread-worker' + str(i)) for i in range(work_repeat)]
    	    [thd.start() for thd in thread_lst]
        	[thd.join() for thd in thread_lst]
    	    stop2 = datetime.datetime.now()
        	delta2 = (stop2 - start2).total_seconds()
    	    print('Multi-Threads takes {} seconds~'.format(delta2))
        	# run tasks with multiprocessing
    	    start3 = datetime.datetime.now()
        	proc_lst = [multiprocessing.Process(target=func, name='process-worker' + str(i)) for i in range(work_repeat)]
    	    [thd.start() for thd in proc_lst]
        	[thd.join() for thd in proc_lst]
    	    stop3 = datetime.datetime.now()
        	delta3 = (stop3 - start3).total_seconds()
    	    print('Multi-Processing takes {} seconds~'.format(delta3))
        	if count == 1:
            	print('\n', '*.' * 30, end='\n\n')
    	        print('==>> crunch_numbers function test')

    上述代码的执行结果如下:

    23:55:51 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
    ==>> only_sleep function test
    23:55:52 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
    23:55:53 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
    23:55:54 [MainProcess 568124] MainThread 182168 <=in only_sleep function=>
    23:55:55 [MainProcess 568124] thread-worker0 553012 <=in only_sleep function=>
    23:55:55 [MainProcess 568124] thread-worker1 567212 <=in only_sleep function=>
    23:55:55 [MainProcess 568124] thread-worker2 547252 <=in only_sleep function=>
    23:55:55 [MainProcess 568124] thread-worker3 561892 <=in only_sleep function=>
    Serial Execution takes 4.022761 seconds~
    Multi-Threads takes 1.01416 seconds~
    23:55:56 [process-worker0 563068] MainThread 567480 <=in only_sleep function=>
    23:55:56 [process-worker1 567080] MainThread 567628 <=in only_sleep function=>
    23:55:56 [process-worker2 567868] MainThread 563656 <=in only_sleep function=>
    23:55:56 [process-worker3 567444] MainThread 566436 <=in only_sleep function=>
    23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
    Multi-Processing takes 1.11466 seconds~

    *.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.

    ==>> crunch_numbers function test
    23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
    23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
    23:55:57 [MainProcess 568124] MainThread 182168 <=in crunch_numbers function=>
    Serial Execution takes 0.1786 seconds~
    23:55:57 [MainProcess 568124] thread-worker0 567412 <=in crunch_numbers function=>
    23:55:57 [MainProcess 568124] thread-worker1 566468 <=in crunch_numbers function=>
    23:55:57 [MainProcess 568124] thread-worker2 565272 <=in crunch_numbers function=>
    23:55:57 [MainProcess 568124] thread-worker3 568044 <=in crunch_numbers function=>
    Multi-Threads takes 0.195057 seconds~
    23:55:58 [process-worker0 567652] MainThread 561892 <=in crunch_numbers function=>
    23:55:58 [process-worker1 553012] MainThread 547252 <=in crunch_numbers function=>
    23:55:58 [process-worker2 554024] MainThread 556500 <=in crunch_numbers function=>
    23:55:58 [process-worker3 565004] MainThread 566108 <=in crunch_numbers function=>
    Multi-Processing takes 0.155246 seconds~

    Process finished with exit code 0

    从上述执行结果中可以看出:

    上述代码的执行结果也验证了此前的结论:对于IO密集型操作,适合使用多线程编程的方式解决问题;而对于CPU密集型的操作,则适合使用多进程编程的方式解决问题。

    3. Python中的GIL是什么,它影响什么

    GIL是CPython中实现的全局解释器锁 (Global Interpreter Lock),由于CPython是使用最广泛的Python解释器,所以GIL也是Python世界中最饱受争议的一个主题。

    GIL是互斥锁,其目的是为了确保线程安全,正是因为有了GIL,所以可以很方便的与外部非线程安全的模块或者库结合起来。但是这也是有代价的,由于有了GIL,所以导致Python中的并行并不是真正意义上的并行,所以也就无法同时创建两个使用相同代码段的线程,相同代码的线程只能有一个处于执行状态。因为GIL互斥锁,相同代码访问的数据会被加锁,只有当一个线程释放锁之后,相同代码的另一个线程才能访问未被加锁的数据。

    所以Python中的多线程是相互交替执行的,并不是真正的并行执行的。但是在CPython之外的一些库,是可以实现真正意义上的并行的,比如numpy这个数据处理常用的库。

    感谢各位的阅读,以上就是“Python多进程,多线程及GIL全局解释器锁实例分析”的内容了,经过本文的学习后,相信大家对Python多进程,多线程及GIL全局解释器锁实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI