微信公众号:复旦大数据
ID:FuDanBigData
本文转自稀土掘金、CSDN。
转载时对文字表述进行了调整,以便于阅读。
原文链接:http://blog.csdn.net/dd864140130/article/details/57128782#reply
作者:江湖人称小白哥
谈高效并发,往往脱离不了以下三种方案:
进程:每个逻辑控制流都是一个进程,由内核来调度和维护.因为进程有独立的虚拟地址空间,想要和其他控制流通信必须依靠显示的进程间通信,即我们所说的IPC机制
线程:线程应该是我们最为熟知的.它本质是运行在一个单一进程上下文中的逻辑流,由内核进行调度.
I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流.逻辑流被模型化为状态机,数据到达文件描述符之后,主程序显式地从一个状态转换为另一个状态.因为程序都是以一个单独的进程,所以所有的流都共享同一个地址空间.基本的思路就是使用select函数要求内核挂起进程,只有一个或多个I/O事件发生后,才将控制权返回给应用程序。
Python中针对这三方面都提供了响应的支持,简化了操作。首先,
大部分人都有线程这个概念
相比进程,线程更轻量级
相比协程,线程更易于理解
进程和线程之间的关系可以用简单的图来表示:
线程的状态
任何一门支持线程的语言都可以具备以下几种运行状态,无论是你做Java,Python还是C,首先来看下面一张图:
在这里我简单来解释以下这几种状态的含义:
新建:使用线程的第一步就是创建线程,创建后的线程只是进入可执行的状态,也就是Runnable
Runnable:进入此状态的线程还并未开始运行,一旦CPU分配时间片给这个线程后,该线程才正式的开始运行
Running:线程正式开始运行,在运行过程中线程可能会进入阻塞的状态,即Blocked
Blocked:在该状态下,线程暂停运行,解除阻塞后,线程会进入Runnable状态,等待CPU再次分配时间片给它
结束:线程方法执行完毕或者因为异常终止返回
这就和人的一生出生、学习(工作之前的准备)、工作、休假是相似的。
其中最复杂的是线程从Running进入Blocked状态,通常有三种情况:
睡眠:线程主动调用sleep()或join()方法后.
等待:线程中调用wait()方法,此时需要有其他线程通过notify()方法来唤醒
同步:线程中获取线程锁,但是因为资源已经被其他线程占用时.
到现在,我们对线程有个基本的概念,光说不练假把式,下面我们就通过是三个小的示例来聊聊线程的使用以及线程中最终的两个概念:同步和通信.
线程简单使用
Python当中要实现多线程有两种方式:一种是使用低级的_thread模块,另一种高级threading模块,相比而言,我推荐使用threading模块..在开始之前呢,先来了解下threading模块给我提供哪些常用的类: Thread,Lock,RLock,Condition,Event,Semaphore,Timer和Local. 这几个类可谓开发多线程中的神兵利器.但是介于篇幅,咱就不展开讲了.
我们直接来看如何使用多线程,这才是至关重要的,有句老话是这么说的:要想让小孩子跑得先让他学会走.我们这就走两步:
import threading def run(number):
print(threading.currentThread().getName() '\n')
print(number) if __name__ == '__main__':
for i in range(10):
my_thread = threading.Thread(target=run, args=(i,))
my_thread.start()
多线程的创建和运行都是套路啊,写的多了自然熟了,来看看运行结果:
Thread-1,value=0 Thread-2,value=1 Thread-3,value=2 Thread-4,value=3 Thread-5,value=4 Thread-6,value=5 Thread-7,value=6 Thread-8,value=7 Thread-9,value=8 Thread-10,value=9
同步与通信
多线程开发中最难的问题不是如何使用,而是如何写出正确高效的代码,要写出正确而高效的代码必须要理解两个很重要的概念:同步和通信. 所谓的通信指的是线程之间如何交换消息,而同步则用于控制不同线程之间操作发生的相对顺序.简单点说同步就是控制多个线程访问代码的顺序,通信就是线程之间如何传递消息.在python中实现同步的最简单的方案就是使用锁机制,实现通信最简单的方案就是Event.下面就来看看这两者的具体使用.
线程同步
当多个线程同时访问同一资源的时候,就会发生竞争,这有点像很多个男性都在追同一个妹纸一样,结果是不可预期的.因此有必要使用某种机制来保证每个男生都有机会和女生相处,这有点像将小姑娘放在一间房子里,然后进去的男生锁上门,下一个男生要想进去,必须等待上一个男生出来.只不过在这里叫线程锁.
Python的threading模块为我们提供了线程锁功能,在threading中提供RLock对象,RLock对象内部维护着一个Lock对象,它是一种可重入锁。对于Lock对象而言,如果一个线程连续两次进行acquire操作,那么由于第一次acquire之后没有release,第二次acquire将挂起线程。这会导致Lock对象永远不会release,使得线程死锁。而RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护着线程acquire的次数。而且每一次的acquire操作必须有一个release操作与之对应,在所有的release操作完成之后,别的线程才能申请该RLock对象.
通过锁机制,最终多线程访问共享资源的过程就类似以下:
上图其实演示了在使用锁来解决线程同步最本质的一点:将所有线程对共享资源的读写操作串行化.
同样举个简单的例子来演示RLock最简单的用法:
import threading
mylock = threading.RLock()
num = 0
class WorkThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.t_name = name def run(self):
global num while True:
mylock.acquire()
print('\n%s locked, number: %d' % (self.t_name, num)) if num
|