1. 并发与并行

  • 所谓的并行(Parallelism),就是多个彼此独立的任务可以同时一起执行,彼此并不相互干扰,并行强调的是同时且独立的运行,彼此不需要协作。
  • 而所谓并发(Concurrency),则是多个任务彼此交替执行,但是同一时间只能有一个处于运行状态,并发执行强调任务之间的彼此协作。

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

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

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

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

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

线程(Thread)是操作系统可以调度的最小运行程序单位,其包含在进程中,一个进程中通常至少包含1个线程,一些进程中会包含多个线程。多个线程运行的都是父进程的相同代码,理想情况下,这些线程是并行执行的,但由于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解释器锁内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Python并发编程多进程,多线程及GIL全局解释器锁的更多相关文章

  1. iOS:核心图像和多线程应用程序

    我试图以最有效的方式运行一些核心图像过滤器.试图避免内存警告和崩溃,这是我在渲染大图像时得到的.我正在看Apple的核心图像编程指南.关于多线程,它说:“每个线程必须创建自己的CIFilter对象.否则,你的应用程序可能会出现意外行为.”这是什么意思?我实际上是试图在后台线程上运行我的过滤器,所以我可以在主线程上运行HUD(见下文).这在coreImage的上下文中是否有意义?

  2. XCode 3.2 Ruby和Python模板

    在xcode3.2下,我的ObjectiveCPython/Ruby项目仍然可以打开更新和编译,但是你无法创建新项目.鉴于xcode3.2中缺少ruby和python的所有痕迹(即创建项目并添加新的ruby/python文件),是否有一种简单的方法可以再次安装模板?我发现了一些关于将它们复制到某个文件夹的信息,但我似乎无法让它工作,我怀疑文件夹的位置已经改变为3.2.解决方法3.2中的应用程序模板

  3. ios – 意外的核心数据多线程违规

    我正在使用苹果的并发核心数据调试器.-com.apple.CoreData.ConcurrencyDebug1有时候我得到__Multithreading_Violation_AllThatIsLeftToUsIsHonor__,即使我几乎肯定线程没有被违反.这是发生异常的代码的一部分(代码是扩展NSManagedobject的协议的一部分):代码在上下文的执行:块中执行.这里是线程信息:和调试器

  4. ios – UIGraphicsBeginImageContextWithOptions和多线程

    我对UIGraphicsBeginImageContextWithOptions和线程有点困惑,因为根据UIKitFunctionReferenceUIGraphicsBeginImageContextWithOptions应该只在主线程上调用.当被调用时,它创建一个基于位图的上下文,可以使用CoreGraphics的函数或者像-drawInRect这样的方法来处理:对于UIImage,-draw

  5. Swift基本使用-函数和闭包(三)

    声明函数和其他脚本语言有相似的地方,比较明显的地方是声明函数的关键字swift也出现了Python中的组元,可以通过一个组元返回多个值。传递可变参数,函数以数组的形式获取参数swift中函数可以嵌套,被嵌套的函数可以访问外部函数的变量。可以通过函数的潜逃来重构过长或者太复杂的函数。

  6. Swift之dispatch_source实现多线程定时关闭功能

    由于在项目中需要用到定时关闭音频功能,本来打算用NSTimer的,可是写起来并不是那么精简好用,所以又在网上找到相关的实例,结合自己项目需要,就写出了如下代码,还请大家指教,废话不多说:

  7. 10 个Python中Pip的使用技巧分享

    众所周知,pip 可以安装、更新、卸载 Python 的第三方库,非常方便。本文小编为大家总结了Python中Pip的使用技巧,需要的可以参考一下

  8. swift 多线程实现

  9. swift_多线程基础_最简单用法GCD, NSOperationQueue, NSThread

    ////ViewController.swift//study1-1//Createdbyadminon15/12/28.//copyright2015年admin.Allrightsreserved.//importUIKitclassViewController:UIViewController{@IBOutletvarmyLable:UILabel?@IBActionfuncclickBut

  10. swift__多线程GCD详解

    有以下*-disPATCH_QUEUE_PRIORITY_HIGH:*-disPATCH_QUEUE_PRIORITY_DEFAULT:多用默认*-disPATCH_QUEUE_PRIORITY_LOW:*-disPATCH_QUEUE_PRIORITY_BACKGROUND:*第二个参数为预留参数,一般为0*/letmyQueue:dispatch_queue_t=dispatch_get_global_queue//用异步的方式运行队列里的任务dispatch_async//-------------

随机推荐

  1. 10 个Python中Pip的使用技巧分享

    众所周知,pip 可以安装、更新、卸载 Python 的第三方库,非常方便。本文小编为大家总结了Python中Pip的使用技巧,需要的可以参考一下

  2. python数学建模之三大模型与十大常用算法详情

    这篇文章主要介绍了python数学建模之三大模型与十大常用算法详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感想取得小伙伴可以参考一下

  3. Python爬取奶茶店数据分析哪家最好喝以及性价比

    这篇文章主要介绍了用Python告诉你奶茶哪家最好喝性价比最高,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧

  4. 使用pyinstaller打包.exe文件的详细教程

    PyInstaller是一个跨平台的Python应用打包工具,能够把 Python 脚本及其所在的 Python 解释器打包成可执行文件,下面这篇文章主要给大家介绍了关于使用pyinstaller打包.exe文件的相关资料,需要的朋友可以参考下

  5. 基于Python实现射击小游戏的制作

    这篇文章主要介绍了如何利用Python制作一个自己专属的第一人称射击小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起动手试一试

  6. Python list append方法之给列表追加元素

    这篇文章主要介绍了Python list append方法如何给列表追加元素,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  7. Pytest+Request+Allure+Jenkins实现接口自动化

    这篇文章介绍了Pytest+Request+Allure+Jenkins实现接口自动化的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  8. 利用python实现简单的情感分析实例教程

    商品评论挖掘、电影推荐、股市预测……情感分析大有用武之地,下面这篇文章主要给大家介绍了关于利用python实现简单的情感分析的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

  9. 利用Python上传日志并监控告警的方法详解

    这篇文章将详细为大家介绍如何通过阿里云日志服务搭建一套通过Python上传日志、配置日志告警的监控服务,感兴趣的小伙伴可以了解一下

  10. Pycharm中运行程序在Python console中执行,不是直接Run问题

    这篇文章主要介绍了Pycharm中运行程序在Python console中执行,不是直接Run问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

返回
顶部