刚开始学习的小白学Python,他对线程和进程的理解让我非常惊讶!

目录
线程与进程
线程与进程是操作系统里面的术语,简单来讲,每一个应用程序都有一个自己的进程。在给大家分享之前呢,小编推荐一下一个挺不错的交流宝地,里面都是一群热爱并在学习python的小伙伴们,大几千了吧,各种各样的人群都有,特别喜欢看到这种大家一起交流解决难题的氛围,群资料也上传了好多,各种大牛解决小白的问题,这个python群:330637182 欢迎大家进来一起交流讨论,一起进步,尽早掌握这门python语言。
操作系统会为这些进程分配一些执行资源,例如内存空间等。在进程中,又可以创建一些线程,他们共享这些内存空间,并由操作系统调用,以便并行计算。
我们都知道现代操作系统比如 mac os x,unix,linux,windows 等可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听敲代码,一边用 markdown 写博客,这就是多任务,至少同时有 3 个任务正在运行。当然还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。对于操作系统来说,一个任务就是一个进程(process),比如打开一个浏览器就是启动一个浏览器进程,打开 pycharm 就是一个启动了一个 ptcharm 进程,打开 markdown 就是启动了一个 md 的进程。
虽然现在多核 cpu 已经非常普及了。可是由于 cpu 执行代码都是顺序执行的,这时候我们就会有疑问,单核 cpu 是怎么执行多任务的呢?
其实就是操作系统轮流让各个任务交替执行,任务 1 执行 0.01 秒,切换到任务 2 ,任务 2 执行 0.01 秒,再切换到任务 3 ,执行 0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于 cpu的执行速度实在是太快了,我们肉眼和感觉上没法识别出来,就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核 cpu 上实现,但是,由于任务数量远远多于 cpu 的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
有些进程不仅仅只是干一件事的啊,比如浏览器,我们可以播放时视频,播放音频,看文章,编辑文章等等,其实这些都是在浏览器进程中的子任务。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,一个进程也可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。
那么在 python 中我们要同时执行多个任务怎么办?
有两种解决方案:
一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。
当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。
总结一下就是,多任务的实现有3种方式:
多进程模式;
多线程模式;
多进程+多线程模式。
同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务 1 必须暂停等待任务 2 完成后才能继续执行,有时,任务 3 和任务 4 又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。
因为复杂度高,调试困难,所以,不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。想想在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。
多线程编程
其实创建线程之后,线程并不是始终保持一个状态的,其状态大概如下:
new 创建
runnable 就绪。等待调度
running 运行
blocked 阻塞。阻塞可能在 wait locked sleeping
dead 消亡
线程有着不同的状态,也有不同的类型。大致可分为:
主线程
子线程
守护线程(后台线程)
前台线程
简单了解完这些之后,我们开始看看具体的代码使用了。
1、线程的创建
python 提供两个模块进行多线程的操作,分别是 thread和 threading
前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。
运行结果:
注意喔,这里不同的环境输出的结果肯定是不一样的。
2、线程合并(join方法)
上面的示例打印出来的结果来看,主线程结束后,子线程还在运行。那么我们需要主线程要等待子线程运行完后,再退出,要怎么办呢?
这时候,就需要用到 join方法了。
在上面的例子,新增一段代码,具体如下:
从打印的结果,可以清楚看到,相比上面示例打印出来的结果,主线程是在等待子线程运行结束后才结束的。
3、线程同步与互斥锁
使用线程加载获取数据,通常都会造成数据不同步的情况。当然,这时候我们可以给资源进行加锁,也就是访问资源的线程需要获得锁才能访问。
4、condition 条件变量
实用锁可以达到线程同步,但是在更复杂的环境,需要针对锁进行一些条件判断。python 提供了 condition 对象。使用 condition 对象可以在某些事件触发或者达到特定的条件后才处理数据,condition 除了具有 lock 对象的 acquire 方法和 release 方法外,还提供了 wait 和 notify 方法。线程首先 acquire 一个条件变量锁。如果条件不足,则该线程 wait,如果满足就执行线程,甚至可以 notify 其他线程。其他处于 wait 状态的线程接到通知后会重新判断条件。
其中条件变量可以看成不同的线程先后 acquire 获得锁,如果不满足条件,可以理解为被扔到一个( lock 或 rlock )的 waiting 池。直达其他线程 notify 之后再重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。
condition
该模式常用于生产者消费者模式,具体看看下面在线购物买家和卖家的示例:
输出的结果如下:
5、线程间通信
如果程序中有多个线程,这些线程避免不了需要相互通信的。那么我们怎样在这些线程之间安全地交换信息或数据呢?
从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 queue对象,这些线程通过使用 put()和 get() 操作来向队列中添加或者删除元素。
python 还提供了 event 对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位真,则其他线程等待直到信号接触。
event 对象实现了简单的线程通信机制,它提供了设置信号,清楚信号,等待等用于实现线程间的通信。
设置信号
6、后台线程
默认情况下,主线程退出之后,即使子线程没有 join。那么主线程结束后,子线程也依然会继续执行。如果希望主线程退出后,其子线程也退出而不再执行,则需要设置子线程为后台线程。python 提供了 setdeamon方法。
进程
python 中的多线程其实并不是真正的多线程,如果想要充分地使用多核 cpu 的资源,在 python 中大部分情况需要使用多进程。python 提供了非常好用的多进程包 multiprocessing,只需要定义一个函数,python 会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing 支持子进程、通信和共享数据、执行不同形式的同步,提供了 process、queue、pipe、lock 等组件。
下面看一个创建函数并将其作为多个进程的例子:
输出的结果:
多进程输出结果
2、把进程创建成类
当然我们也可以把进程创建成一个类,如下面的例子,当进程 p 调用 start() 时,自动调用 run() 方法。
输出结果如下:
创建进程类
3、daemon 属性
想知道 daemon 属性有什么用,看下下面两个例子吧,一个加了 daemon 属性,一个没有加,对比输出的结果:
没有加 deamon 属性的例子:
在上面示例中,进程 p 添加 daemon 属性:
根据输出结果可见,如果在子进程中添加了 daemon 属性,那么当主进程结束的时候,子进程也会跟着结束。所以没有打印子进程的信息。
4、join 方法
结合上面的例子继续,如果我们想要让子线程执行完该怎么做呢?
那么我们可以用到 join 方法,join 方法的主要作用是:阻塞当前进程,直到调用 join 方法的那个进程执行完,再继续执行当前进程。
因此看下加了 join 方法的例子:
5、pool
如果需要很多的子进程,难道我们需要一个一个的去创建吗?
当然不用,我们可以使用进程池的方法批量创建子进程。
例子如下:
6、进程间通信
process 之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。python 的 multiprocessing 模块包装了底层的机制,提供了queue、pipes 等多种方式来交换数据。
以 queue 为例,在父进程中创建两个子进程,一个往 queue 里写数据,一个从 queue 里读数据:
谢谢阅读,此篇为草根学python。非常全面!