Python:线程、进程与协程(1)——概念

最近的业余时间主要放在了学习Python线程、进程和协程里,第一次用python的多线程和多进程是在两个月前,当时只是简单的看了几篇博文然后就跟着用,没有仔细去研究,第一次用的感觉它们其实挺简单的,最近这段时间通过看书, 看Python 中文官方文档等等相关资料,发现并没有想想中的那么简单,很多知识点需要仔细去理解,Python线程、进程和协程应该是Python的高级用法。Python的高级用法有很多,看看Python 中文官方文档就知道了,当然有时间看看这些模块是怎么实现的对自己的提高是很有帮助的。选择了编程这个行业,就是要不断的学习、思考、归纳总结经验,路漫漫其修远兮,吾将上下而求索,希望能与各位共勉。接下来要花好几篇博文的篇幅来讲讲我学习线程、进程和协程的经验,有讲得不好的地方,希望大家批评指正。这篇博文主要讲讲与之有关的概念。

(一)线程与多线程

线程

(1) 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。

(2)一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。有了这些它能够记录自己运行到了什么地方,可以称为线程的上下文。

(3)线程的运行可能被抢占(中断)或暂时的被挂起(也叫睡眠)让其它的线程运行,这叫做让步。

(4)线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

(5)线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不独立拥有系统资源,但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。

 多线程

(1)每一个应用程序都至少有一个进程和一个线程。线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的被划分成一块一块的工作,称为多线程。

很好理解,开发软件就是每个人或者每个小组负责一个模块,当所有人(组)相关的模块都编写完后,就开始合并代码,然后就测试,修复bug。

(2)除非代码依赖第方资源,否则在单处理器的机器上使用多线程,不会加速代码的执行速度,甚至会增加一些线程管理的开销。

实际上,在单处理器的系统中,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其它的线程去运行。比如线程切换等等,这些都是要花费资源和时间的,所以在单CPU机器上使用多线程有时候不但感觉不到执行速度变快,反而变慢了。

(3)多线程会从多处理器或者多核的机器上获益,它会在每个处理器上并行执行每个线程,从而提高执行速度。

(4)线程之间可以共享运行结果。但是这样做有一定的危险,比如两个线程更新同一个数据,但是这两个线程运行的结果不一样,这叫做竞态条件,这个会造成竞争危害,会发生不可预测的结果。所以利用锁机制可以保护数据。

Python中的多线程

python的多线程并没有想象中的那么理想,是因为有一个叫GIL的东西在限制。那什么是GIL呢?GIL中文名叫全局解释器锁,是python虚拟机上用作互斥线程的一种机制,它的作用就是要保证在任何情况下虚拟机上只有一个线程被运行,而其它线程都处在等待GIL锁被释放的状态。所以它是个“伪多线程",它的情况就跟上面说的在单处理器机器上运行多线程一样,不会加速代码的执行速度,甚至会增加一些线程管理的开销。

python虚拟机上多线程是按如下方式执行的:

a、设置 GIL

b、切换到一个线程去运行;

c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

d、把线程设置为睡眠状态;

e、解锁 GIL;

f、再次重复以上所有步骤

在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)。比如带有I/O操作(会调用内建的操作系统C代码,I/O操作就是输入输出操作,要想详细了解它可以参考其它资料)的线程,GIL会在这个I/O操作被调用之前就被释放。

对于纯计算的程序,没有I/O操作,解释器会根据sys.ssetcheckinterval()的设置来自动进行线程间的切换,默认情况下是每隔100个时钟就会释放GIL锁从而轮换到其它线程执行。

那为什么Python中还要在多线程中引入GIL呢?是为了保证对虚拟机内部共享资源访问的互斥性。python对象的对象管理与引用计数器密切相关,当计数器的值为0,该对象会被垃圾回收器回收(不了解这块知识的可以在网上查找相关资料或者看《Python源码解析》这本书),当撤销对一个对象的引用时,python解释器会对该对象以及其计数器管理进行以下两步操作:

a.使引用计数器减1

b.判断计数器的值是否为0,如果为0,则销毁该对象

假设现在有A、B两个线程同时引用同一个对象obj,这时obj对象的引用计数器的值就为2,如果现在A线程打算撤销对obj的引用,当执行完第一步”使引用计数器值减1“的时候,由于存在多线程调度机制,A恰好在这个关键点被挂起了,而进入了B线程执行的状态,如果这个时候B线程也是要撤销对obj的引用,并且完成了上面的a,b两个步骤,这时obj的引用计数器就是0了,obj对象就被销毁了,内存被释放出来了,麻烦就可能出现了,当A线程再次被唤醒时,它肯定会接着执行上面的b步骤,结果发现已经面目全非了,那么其操作结果完全未知。所以引入了GIL,保证对虚拟机内部共享资源访问的互斥性。


    GIL的引入使多线程不能在多核系统中发挥优势,但也带来了一些好处,就是大大简化了Python线程中共享资源的管理。不过Python提供了其它方式绕过了GIL的局限性来充分利用多核的计算能力,比如多进程multiprocessing模块、C语言扩展方式、ctypes库等等。

(二)进程

进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。进程也可以通过fork和spawn操作来完成其它的任务,不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。

(三)协程

(1)协程是一种用户级的轻量级线程,不同于线程的地方在于协程不是操作系统进行切换,而是由程序员编码进行切换的,也就是说切换是由程序员控制的,这样就没有了线程所谓的安全问题。

(2)协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

python里面怎么使用协程?答案是使用gevent模块。使用协程,可以不受线程开销的限制。所以最推荐的方法,是多进程+协程(可以看作是每个进程里都是单线程,而这个单线程是协程化的)多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来。

以上是对线程、进程及协程的理解,希望对你有帮助!

时间: 2024-08-01 10:43:48

Python:线程、进程与协程(1)——概念的相关文章

多任务-python实现-进程,协程,线程总结(2.1.16)

目录 1.类比 2.总结 关于作者 @ 1.类比 一个生产玩具的工厂: 一个生产线成为一个进程,一个生产线有多个工人,所以工人为线程 单进程-多线程:一条生产线,多个工人 多进程-多线程:多条生产线,多个工人 协程:工人空闲的时候安排做其他事 2.总结 1.进程是资源分配的单位 2.线程为操作系统调度的单位 3.进程切换需要的资源很大,效率很低 4.线程需要的资源一般,效率一般(不考虑GIL) 5.协程切换的任务资源很小,效率高 6.多进程,多线程根据cpu核数不同可能是并行的,但协程是在一个线

Python菜鸟之路:Python基础-线程、进程、协程

上节内容,简单的介绍了线程和进程,并且介绍了Python中的GIL机制.本节详细介绍线程.进程以及协程的概念及实现. 线程 基本使用 方法1: 创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入 import threading import time def worker(): time.sleep(2) print("test") for i in range(5): t = threading.Thread(target=

Python:线程、进程与协程(4)——multiprocessing模块(1)

multiprocessing模块是Python提供的用于多进程开发的包,multiprocessing包提供本地和远程两种并发,通过使用子进程而非线程有效地回避了全局解释器锁. (一)创建进程Process 类 创建进程的类,其源码在multiprocessing包的process.py里,有兴趣的可以对照着源码边理解边学习.它的用法同threading.Thread差不多,从它的类定义上就可以看的出来,如下: class Process(object):     '''     Proces

Python:线程、进程与协程(2)——threading模块(1)

上一篇博文介绍了Python中线程.进程与协程的基本概念,通过这几天的学习总结,下面来讲讲Python的threading模块.首先来看看threading模块有哪些方法和类吧. 主要有: Thread :线程类,这是用的最多的一个类,可以指定线程函数执行或者继承自它都可以实现子线程功能. Timer:与Thread类似,但要等待一段时间后才开始运行,是Thread的子类. Lock :原锁,是一个同步原语,当它锁住时不归某个特定的线程所有,这个可以对全局变量互斥时使用. RLock :可重入锁

Python之路【第七篇】:线程、进程和协程

Python之路[第七篇]:线程.进程和协程 Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time   def show(arg):     time.sleep(1)     print 'thread'+str(arg)   for i in

Python:线程、进程与协程(3)——Queue模块及源码分析

Queue模块是提供队列操作的模块,队列是线程间最常用的交换数据的形式.该模块提供了三种队列: Queue.Queue(maxsize):先进先出,maxsize是队列的大小,其值为非正数时为无线循环队列 Queue.LifoQueue(maxsize):后进先出,相当于栈 Queue.PriorityQueue(maxsize):优先级队列. 其中LifoQueue,PriorityQueue是Queue的子类.三者拥有以下共同的方法: qsize():返回近似的队列大小.为什么要加"近似&q

线程、进程、协程和GIL(一)

参考链接:https://www.cnblogs.com/alex3714/articles/5230609.html https://www.cnblogs.com/work115/p/5620272.html 编程离不开并发,而并发的基础就离不开线程.进程.协程.那么什么是线程.进程.协程呢? 进程: 进程是对资源进行分配和调度的最小单位,是操作系统结构的基础,是线程的容器(就像是一幢房子,一个空壳子,并不能运动). 线程的概念主要有两点: 1.进程是一个实体,每个进程都有自己的地址空间,一

python运维开发(十)----IO多路复用多线程、进程、协程

内容目录: python作用域 python2.7和python3.5的多继承区别 IO多路复用 多线程.进程.协程 python作用域  python中无块级作用域 if 1 == 1: name = 'jabe' print(name) #可以正常输出jabe #在python中无块级作用域 #在c#或者java中是不能这样使用的,提示name未定义的 python中以函数为作用域 def func(): name = 'jbae' func() print(name) #会提示name为定

进程池与线程池、协程、协程实现TCP服务端并发、IO模型

进程池与线程池.协程.协程实现TCP服务端并发.IO模型 一.进程池与线程池 1.线程池 ''' 开进程开线程都需要消耗资源,只不过两者比较的情况下线程消耗的资源比较少 在计算机能够承受范围内最大限度的利用计算机 什么是池? 在保证计算机硬件安全的情况下最大限度的利用计算机 池其实是降低了程序的运行效率,但是保证了计算机硬件的安全 (硬件的发展跟不上软件的速度) ''' from concurrent.futures import ThreadPoolExecutor import time p