Python多线程编程
简介
多线程编程适用任务对象为该任务:
1.本质上是异步的;
2.需要多个并发活动;
3.每个活动的处理顺序可能是不确定的,或者说是随机的、不可预测的。
使用多线程编程,以及类似Queue的共享数据结构,这个编程任务可以规划成几个执行特定函数的线程。
UserRequestThread:
负责读取客户端输入,该输入可能来自于I/O通道。
RequsetProcessor:
该线程负责从队列中获取请求并进行处理。
ReplyThread:
负责向用户输出,将结果传回给用户(如果是网络应用),或者把数据写到本地文件系统或者数据库中。
线程和进程
进程
进程是一个执行中的程序。每个进程都有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。进程可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息。
线程
线程包括开始、执行程序、结束三个部分。它有一个临时指针,用于记录当前运行的上下文。
让步:当其他线程运行时,它可以被抢占(中断)和临时挂起(睡眠)
在Python中使用线程
使用单线程执行循环
首先,创建两个时间循环:一个睡眠4秒,一个睡眠2秒。如果在一个单线程或者单线程的程序中执行,就会至少达到6秒。在启动和执行其他代码时,也可能存在一秒的开销,使得进程时间达到七秒。
执行结果如下:
Python的threading模块
Python提供了多个模块来支持多线程编程,包括thread、threading和Queue模块等。程序是可以使用thread、threading模块来创建与管理线程。thread模块提供了基本的线程和锁定支持;而threading模块提供了更高级别、功能更全面的线程管理。使用Queue模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。
thread模块
除了派生之外,thread模块还提供了基本的同步数据结构,成为锁对象。
thread模块的核心函数是start_new_thread()。它的参数包括函数(对象)、函数的参数以及可选的关键字参数。将专门派生新的线程来调用这个函数。
thread模块和锁对象:
一、thread模块的函数:
1.派生一个新的线程
start_new_thread(function,args,kwargs=None)
2.分配LockType锁对象
allocate_lock()
3.给线程退出指令
exit()
二、LockType锁对象的方法
1.尝试获取锁对象
acquire(wait=None)
2.如果获取了锁对象返回True,否则,返回False
locked()
3.释放锁
release()
下面是一个实例:
mtsleepA.py
start_new_thread()必须包含开始的两个参数,于是即使要执行的函数不需要参数,也需要传递一个空元组。
执行结果如下:
与之前的onethr.py相比,原本需要运行6-7秒,本程序脚本运行时间只需要4秒,也就是最长的循环加上其他所有开销的时间之和。
然而我们没有写让主线程等待子线程全部完成后再继续的代码,即我们所说的线程需要某种形式的同步。在这个实例中,调用sleep(6)是因为我们知道所有线程会在主线程计时到6秒前完成。
所以,为了改进代码,可以引入锁,并去除单独的循环函数。修改实例如下:
执行结果如下:
然而,在这里使用thread模块只是为了向大家介绍多线程编程,多线程应用程序应当使用更高级别的模块,比如接下来要介绍的threading
模块。
threading模块
除了Thread类以外,threading模块还包括了许多非常好用的同步机制。下面就来介绍一下:
Thread: 表示执行一个线程的对象
Lock: 锁原语对象
RLock: 可重入锁对象,使单一线程可以(再次)获得已有的锁(递归锁)
Condition: 条件变量对象,使得一个线程等待另外一个线程满足特定条件,比如改变状态成某个数据值
Event: 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将会被激活
Semaphore: 为线程间共享的有限资源提供一个“计数器”,如果没有可用资源时会被阻塞
BoundedSemaphore: 与Semaphore相似,但不允许超过初始值
Timer: 与Thread相似,不过它需要在运行前等待一段时间
Barrier: 创建一个“障碍”,必须达到指定数量的线程后才可以继续
避免使用thread模块的另外一个原因就是该模块不支持守护线程这个概念。什么是守护线程?
当主线程退出时,所有子线程都将终止,不管它们是否在工作。
threading模块支持守护线程,其工作方式是:守护线程一般是一个等待客户端请求服务的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。
如果主线程准备退出时,不需要等待某些子线程完成,就可以为这些子线程设置守护线程标记。
要将一个线程设置为守护线程,需要在启动线程之前执行下语句:
_thread.daemon = True
同样,要检查线程是否为守护状态,就要检查这个值是否为True
Thread类
Thread对象数据属性:
name : 线程名
ident : 线程的标识符
daemon : 布尔标志,表示这个线程是否是守护线程
Thread对象方法:
实例化一个线程对象:_init_(group=None, tatget=None, name=None,
args=(), kwargs={},verbose=None, daemon=None)
start():开始执行该线程
run():定义线程功能的方法
join(timeout=None):直到启动的线程终止之前一直挂起,除非给出timeout,否则一直阻塞
使用Thread类创建线程的方法:
1、创建Thread实例,传给它一个参数
2、创建Thread实例,传给它一个可调用的类实例
3、创建Thread实例,并创建子类的实例
方法一:创建Thread的实例,传给它一个参数
执行结果如下:
方法二:派生Thread的子类,并创建子类的实例
mtsleepE.py
执行结果如下:
Python多线程实践:
图书馆排名示例
下面先写一个没有使用线程版本的实例:
bookrank.py
from atexit import register
from re import compile
from threading import Thread
from time import ctime
from urllib.request import urlopen as uopen
REGEX = compile(' #([\d,]+) in Books ')
AMZN = 'http://amazon.com/dp/'
ISBNS = {
'0132269937': 'Core Python Programming',
'0132356139': 'Python Web Development with Django',
'0139143419': 'Python Fundamentals',
}
def getRanking(isbn):
page = uopen('%s%s' % (AMZN, isbn))
data =page.close()
page.close()
return REGEX.findall(data)[0]
def _showRanking(isbn):
print('- %r ranked %s' % (
ISBNS[isbn], getRanking(isbn)))
def main():
print('At', ctime(), 'on Amazon...')
for isbn in ISBNS:
_showRanking(isbn)
@register
def _atexit():
print('all done at:', ctime())
if __name__ == "__main__":
main()
执行结果如下:
可见只用单线程的话程序运行十分缓慢。
由于这是一个I/O密集型应用,因此这个程序使用多线程是一个好的选择。简单起见,直接将应用中的_showRanking(isbn)
进行修改即可:
def _showRanking(isbn):
Thread(target=_showRanking, args=(isbn,)).start()