Python基础—线程、进程和协程
今天已是学习Python的第十一天,来干一碗鸡汤继续今天的内容,今天的鸡汤是:超越别人对你的期望。本篇博客主要介绍以下几点内容:
-
线程的基本使用;
-
线程的锁机制;
-
生产者消费之模型(队列);
-
如何自定义线程池;
-
进程的基本使用;
-
进程的锁机制;
-
进程之间如何实现数据共享;
-
进程池;
-
协程的基本使用。
一、线程
1、创建线程
上篇博客已经介绍过如何创建多线程的程序,在这里在复习一下如何创建线程过程以及线程的一些方法:
线程的方法:
-
start:线程准备就绪,等待CPU调度;
-
setName:为线程设置名称;
-
getName:获取线程名称;
-
setDaemon(布尔值):设置为主线程是否等待子线程执行(默认False);
如果是将setDaemon设置成True,主线程执行过程中,子线程也在进行,主线程执行完毕后,子线程不论成功与否,均停止主线程不会等子线程;
如果值为False,主线程执行过程中,子线程也在执行,主线程执行完毕后,等待子线程也执行完成后,程序停止。
-
join(秒):表示主线程到此,会等待子线程执行,参数表示主线程在此最多等待N秒后,继续往下执行;
-
run:线程被CPU调度后自动执行线程对象的run方法。
2、线程的锁机制
下面我们来介绍一下线程的锁机制,由于线程之间是进行随机调度,并且每个线程可能只执行N条操作,当多个线程同时修改同一条数据时可能会出现脏数据,所以出现了线程锁。在python中分为三种线程锁:互斥锁(lock,Rlock)、信号量(Semaphore)、事件(event),还有一个条件(Condition)配合线程锁来使用,下面分别介绍这几种锁:
(1)、互斥锁(lock,Rlock)
我们先看一下不加线程锁的程序的执行结果:
当我们加上线程锁后,效果就会避免上面现象的发生:
(2)、信号量(Semaphore)
上面我们介绍了互斥锁,我们发现,互斥锁同时只能允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如肯德基有3个购餐的窗口,那最多只允许3个人购买,后面的人只能等前面的人买完才能购买。
(3)、事件(event)
Python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法:set、wait、clear。
事件处理的机制:全局定义了一个"Flag",如果"Flag"值为Flase,那么当程序执行event.wait方法时就会阻塞,如果"Flag"值为True,那么event.wait方法时便不再阻塞。
-
event.clear:将"Flag"设置成False,(加锁);
-
event.set:将"Flag"设置成True,(解锁)。
(4)、条件(Condition)
使得线程等待,只有满足条件的时候,才释放N个线程去更改数据,下面通过两种方法来演示加条件的线程锁操作:
(5)、Timer
Timer:定时器,指定N秒之后执行某操作。
3、生产者消费者模型(队列)
Queue模块实现了多生产者、多消费者队列,它特别适用于多线程编程。Queue类中实现了所有需要的锁语义,Queue模块实现了四种类型的队列:
-
queue.Queue:先进先出队列(FIFO),第一加入队列的任务,被第一个取出;
-
queue.LifoQueue:后进先出队列(LIFO),最后加入队列的任务,被第一个取出
-
queue.PriorityQueue:优先级队列,保持队列数据有序,是根据权重判断取出顺序,最小值被先取出。
-
queue.deque:双向队列,一种支持向两端高效地插入数据、支持随机访问的容器
下面通过例子来详细介绍一下先进先出队列的使用方法:
queue.Queue(先进先出):
通过上面的例子,我们总结一下queue队列提供的公共方法:
-
Queue.put:向队列中放入元素,block是否阻塞(默认True),timeout阻塞时的超时时间;
-
Queue.get:移除队列中的元素,block是否阻塞,timeout阻塞时超时时间;
-
queue.Queue(Maxsize):Maxsize,设置队列支持最大的个数;
-
Queue.qsize:队列的真实个数;
-
Queue.join,Queue.task_done:阻塞进程,当队列中任务执行完毕后,不再阻塞;
-
Queue.empty:判断队列是否为空。
queue.LifoQueue(后进先出):
[strong]queue.PriorityQueue(优先级队列):[/strong]
[strong]queue.deque(高性能双向队列):[/strong]
为什么说它是高性能的队列我们来对比双向队列、普通队列和列表的处理速度我们一起来看一下:
下面结合上面的知识来写一个生产者消费者模型:
4、自定义线程池
在使用多线程处理任务也不是线程越多越好,由于在切换线程的时候,需要切换上下文环境,依然会造成CPU的大量开销。为了解决这个问题,就引出了线程池的概念。预先创建一个批线程,然过来的任务立刻能够使用,使用完以后自动释放来去处理新的任务,在Python中,没有内置的较好的线程池模块,需要自己实现或使用第三方模块,下面我们尝试来自定义一个线程池:
初级版:
进阶版:
二、进程
1、创建进程
注意:由于进程之间的数据需要各自持有一份,所以创建进程需要非常大的开销。
2、进程间数据共享
进程各自持有一份数据,默认是无法共享数据的。
通过上面的例子可以看出,每个进程都有自己的一份数据,没有共享数据,在Python中我们通常通过调用第三方模块的方式来实现进程之间的数据共享,主要是调用multiprocessing的Queues、Array、Manager这三个模块。下面我们通过例子类看一下具体用法:
方法一:通过调用queues共享数据
方法二:通过调用数组Array来共享数据
当看到这端代码时有个疑问就是Array数组中的'i',是什么?在Array类在实例化的时候必须指定数组的数据类型和数组的大小,具体数据类型的对照请参考下面的对应关系:
方法三:通过调用Manager字典来共享数据
3、进程锁
为了防止和多线程一样的出现数据抢夺和脏数据的问题,同样需要设置进程锁。在multprocessing里也有与线程一样支持Rlock,Lock,Event,Condition,Semaphore几种锁,用法也相同,我们来看一下进程数的例子:
4、进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可使用的进程,那么程序就会等待,知道进程池中有可用进程为止。
进程池中有两个方法:
-
apply:子进程串行执行任务,达不到并发的效果;
-
apply_async:apply的异步版本,支持并发,推荐使用这个。
总结一句话:IO密集型使用多线程,计算密集型使用多进程。
三、协程
线程和进程的操作是由程序触发系统接口,最后的执行者是系统,而协程的操作则是程序员自己。
协程的原理:利用一个线程,分解一个线程成为多个"微线程",程序级别。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定摸个代码块执行顺序。
协程的适用场景:当程序存在大量不需要CPU的操作时(IO),适用于协程。
使用协协程需要调用两个模块:greenlet模块(底层)、gevent模块(高性能)。
使用greenlet模块:
使用gevent模块:
下面的例子我们去分别请求多个网站,遇到IO操作来实现自动切换:
今天的内容就到这里了,例子中少了不少的注释,还有一些自己不太理解,见谅。
- python文件操作
- 安装python-memached
- 推荐10 款最好的 Python IDE
- python report中文显示乱码
- Python中使用HTMLParser解析HTML文档
- Python--with的用法详解
- Some Powerful Python Tools to Analyze Data
- python解析yaml文件
- 【转载】关于Python中的yield
- Python--列表
- 如何编写pythonGNURADIO应用
- 读取.csv数据/写入另外一个.csv (Python)
- python 学习 笔记1
- Python time和logging模块
- 分析python处理基本数据<四>
- python开源IP代理池--IPProxys
- The Python Tutorial - Input and Output
- python--模块
- python-django
- python核心编程学习笔记-2016-07-21-01-decimal模块