猪行天下之Python基础——9.1 Python多线程与多进程(上)(二)

简介: 内容简述: 线程与进程的相关概念 1、程序,进程,线程,多进程,多线程 2、线程的生命周期 3、并行与并发,同步与异步 4、线程同步安全 5、与锁有关的特殊情况:死锁,饥饿与活锁 6、守护线程 7、线程并发的经典问题:生产中与消费者问题 8、Python中的GIL锁 9、Python中对多线程与多进程的支持

4、线程同步安全


什么是线程同步安全问题


当有两个或以上线程在同一时刻访问同一资源,可能会带来一些问题。


比如:数据库表不允许插入重复数据,而线程1,2都得到了数据X,然后线程1,2同时查询了数据库,发现没有数据X,接着两线程都往数据库中插入了X,然后就出现异常了,这就是线程的同步安全问题,而这里的数据库资源我们又称为:临界资源(共享资源)


如何解决同步安全问题(同步锁)?


当多个线程访问临界资源的时候,有可能会出现线程安全问题;而基本所有并发模式在解决线程安全问题时都采用"系列化访问临界资源"的方式,就是同一时刻,只能有一个线程访问临界资源,也称"同步互斥访问"。通常的操作就是加锁(同步锁),当有线程访问临界资源时需要获得这个锁,其他线程无法访问,只能等待(堵塞),等这个线程使用完释放锁,供其他线程继续访问。


5、与锁有关的特殊情况:死锁,饥饿与活锁


有了同步锁并不意味着就一了百了了,当多个进程/线程的操作涉及到了多个锁,就可能出现下述三种情况:


  • 死锁(DeadLock)


两个或以上进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,如果无外力作用,他们将继续这样僵持下去。举个形象化的例子:


开一个门需要两条钥匙,而两个人手上各持有一条,然后都不愿意把自己的钥匙给对方,就一直那样僵持着,这种状态就叫死锁。


死锁发生的条件


  • 互斥条件(临界资源);


  • 请求和保持条件(请求资源但不释放自己暂用的资源);


  • 不剥夺条件(线程获得的资源只有线程使用完后自己释放,不能被其他线程剥夺);


  • 环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,t1等t2,t2等t1;


如何避免死锁


破坏四个条件中的一个或多个条件,常见的预防方法有如下两种:


① 有序资源分配法:资源按某种规则统一编号,申请时必须按照升序申请: 属于同一类的资源要一次申请完,申请不同类资源按照一定的顺序申请。


② 银行家算法:就是检查申请者对资源的最大需求量,如果当前各类资源都可以满足的 申请者的请求,就满足申请者的请求,这样申请者就可很快完成其计算,然后释放它占用 的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生。 理论上能够非常有效的避免死锁,但从某种意义上说,缺乏使用价值,因为很少有进程能够知道所需资源的最大值,而且进程数目也不是固定的,往往是不断变化的, 况且原本可用的资源也可能突然间变得不可用(比如打印机损坏)。


  • 2.饥饿(starvation)与饿死(starve to death)


资源分配策略有可能是不公平的,即不能保证等待时间上界的存在,即使没有发生死锁, 某些进程可能因长时间的等待对进程推进与相应带来明显影响,此时的进程就是 发生了进程饥饿(starvation),当饥饿达到一定程度即使此时进程即使完成了任务也没有实际意义时,此时称该进程被饿死(starve to death),举个典型的例子:文件打印采用短文件优先策略,如果短文件太多,长文件会一直推迟,那还打印个毛。


  • 3.活锁(LiveLock)


特殊的饥饿,一系列进程轮询等待某个不可能为真的条件为真,此时进程不会进入blocked状态,但会占用CPU资源,活锁还有几率能自己解开,而死锁则无法自己解开。(例子:都觉得对方优先级比自己高,相互谦让,导致无法使用某资源),简单避免活锁的方法:先来先服务策略


6、守护线程


又称「后台线程」,是一种为其他线程提供服务的线程,比如一个简单的例子:你有两个线程在协同的做一件事,如果有一个线程死掉,事情就无法继续下去,此时可以引入守护线程,轮询地去判断两个线程是否活着(调isAlive()),如果死掉就start开启线程,在Python中可以在线程初始化的时候调用setDaemon(True)把线程设置为守护线程,另外如果程序中只剩下守护线程的话程序会自动退出。


7、线程并发的经典问题:生产中与消费者问题


说到线程并发,不得不说的一个经典问题就是:生产中与消费者问题


两个共享固定缓冲区大小的线程,生产者线程负责生产一定量的数据 放入缓冲区, 而消费者线程则负责消耗缓冲区中的数据,关键问题是需要保证两点:


  • 缓冲区满的时候,生产者不再往缓冲区中填充数据。


  • 缓存区空的时候,消费者不在消耗缓冲区中的数据。


8、Python中的GIL锁


上面讲到Python在实现Python解析器(CPython)时引入了GIL锁,使得「任何时候仅有 一个线程在执行」,Python多线程的效率可能还比不上单线程,那么这个GIL锁是什么?


概念:全局解释器锁,用于同步线程的一种机制,使得任何时候仅有一个线程在执行。GIL 并不是Python的特性,只是在实现Python解析器(CPython)时引入的一个概念。换句话说,Python完全可以不依赖于GILPython解释器进程内的多线程是以协作多任务方式执行的,当一个线程遇到I/O操作时会释放GIL,而依赖CPU计算的线程则是执行代码量到一定的阀值才会释放GIL


而在Python 3.2开始使用新的GIL,使用固定的超时时间来指示当前线程放弃全局锁,就是:「当前线程持有这个锁,且其他线程请求这个锁时,当前线程就会在5毫秒后被强制释放掉该锁。」多线程在处理CPU密集型操作因为各种循环处理计数等,会很快达到阀值,而**多个线程来回切换是会消耗资源的,所以多线程的效率往往可能还比不上单线程!


而在多核CPU上效率会更低,因为多核环境下,持有锁的CPU释放锁后,其他CPU上的线程都会进行竞争,但GIL可能马上又会被之前的CPU拿到拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,从而造成 线程颠簸(thrashing),导致效率更低


问题因为GIL锁的原因,对于CPU密集型操作,Python多线程就是鸡肋了?


答:是的!尽管多线程开销小,但却无法利用多核优势!可以使用多进程来规避这个问题,Python提供了multiprocessing这个跨平台的模块来帮助我们实现多进程代码的编写。每个进程都有自己独立的GIL,因此不会出现进程间GIL锁抢夺的问题,但是也增加程序实现线程间数据通讯和同步时的成本,这个需要自行进行权衡。


9、Python中对多线程与多进程的支持


Python与线程,进程相关的官方文档链接:docs.python.org/3/library/c…


简单说下这些模块都是干嘛的:


  • threading—— 提供线程相关的操作。


  • multiprocessing—— 提供进程程相关的操作。


  • concurrent.futures—— 异步并发模块,实现多线程和多进程的异步并发(3.2后引入)。


  • subprocess—— 创建子进程,并提供链接到他们输入/输出/错误管道的方法,并获得他们的返回码,该模块旨在替换几个较旧的模块和功能:os.system与os.spawn*。


  • sched——任务调度(延时处理机制)。


  • queue——提供同步的、线程安全的队列类。


还有几个是兼容模块,比如Python 2.x上用threading和Python 3.x上用thread:


  • dummy_threading:提供和threading模块相同的接口,2.x使用threading兼容。


  • _thread:threading模块的基础模块,应该尽量使用 threading 模块替代。


  • dummy_thread:提供和thread模块相同的接口,3.x使用threading兼容。


相关文章
|
1天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
15 5
|
17天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
22天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
19天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
25 1
|
24天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
37 2
|
26天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
29天前
|
Java Unix 调度
python多线程!
本文介绍了线程的基本概念、多线程技术、线程的创建与管理、线程间的通信与同步机制,以及线程池和队列模块的使用。文章详细讲解了如何使用 `_thread` 和 `threading` 模块创建和管理线程,介绍了线程锁 `Lock` 的作用和使用方法,解决了多线程环境下的数据共享问题。此外,还介绍了 `Timer` 定时器和 `ThreadPoolExecutor` 线程池的使用,最后通过一个具体的案例展示了如何使用多线程爬取电影票房数据。文章还对比了进程和线程的优缺点,并讨论了计算密集型和IO密集型任务的适用场景。
50 4
|
29天前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
53 1
|
12天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
47 0
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能