Python 核心编程(第二版)——多线程编程

1、引言/动机

多线程编程对于某些任务来说,是最理想的。这些任务具有以下特点:它们本质上就是异步的,需要有多个并发事务,各个事务的运行顺序可以是不确定的,随机的,不可预测的。这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标。根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。

使用多线程编程和一个共享的数据结构如Queue,这种程序任务可以用几个功能单一的线程来组织:

􀁺 UserRequestThread: 负责读取客户的输入,可能是一个I/O 信道。程序可能创建多个线程,每个客户一个,请     求会被放入队列中。
􀁺 RequestProcessor: 一个负责从队列中获取并处理请求的线程,它为下面那种线程提供输出。
􀁺 ReplyThread: 负责把给用户的输出取出来,如果是网络应用程序就把结果发送出去,否则就保存到本地文件系     统或数据库中。

2、线程和进程

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

线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。实际上,在单CPU 的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU 让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其它的线程共享运行的结果。

如果多个线程共同访问同一片数据,则由于数据访问的顺序不一样,有可能导致数据结果的不一致的问题。这叫做竞态条件(race condition)。幸运的是,大多数线程库都带有一系列的同步原语,来控制线程的执行和数据的访问。

3、Python 、线程和全局解释器锁

1)全局解释器锁(GIL)

对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按以下方式执行:

(1). 设置GIL
(2). 切换到一个线程去运行
(3). 运行:
  a. 指定数量的字节码指令,或者
  b. 线程主动让出控制(可以调用time.sleep(0))
(4). 把线程设置为睡眠状态
(5). 解锁GIL
(6). 再次重复以上所有步骤

2)退出线程

当一个线程结束计算,它就退出了。线程可以调用thread.exit()(不建议使用)之类的退出函数,也可以使用Python 退出进程的标准方法,如sys.exit()或抛出一个SystemExit 异常等。不过,你不可以直接“杀掉”("kill")一个线程。

3)Python 的 threading 模块

Python 提供了几个用于多线程编程的模块,包括thread, threading 和Queue 等。thread 和threading 模块允许程序员创建和管理线程。thread 模块提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

核心提示:避免使用thread 模块。首先,更高级别的threading 模块更为先进,对线程的支持更为完善,而且使用thread 模块里的属性有可能会与threading 出现冲突。其次,低级别的thread 模块的同步原语很少(实际上只有一个),而threading 模块则有很多。另一个不要使用thread 原因是,对于你的进程什么时候应该结束完全没有控制,当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作。

4、thread 模块

thread 模块和锁对象
函数 描述
thread 模块

start_new_thread(function,args, kwargs=None)

产生一个新的线程,在新线程中用指定的参数和可选的kwargs 来调用这个函数。
allocate_lock() 分配一个LockType 类型的锁对象
exit() 让线程退出
LockType 类型锁对象方法
acquire(wait=None) 尝试获取锁对象
locked() 如果获取了锁对象返回True,否则返回False
release() 释放锁

 

5、threading 模块

threading 模块对象
threading 模块对象 描述
Thread 表示一个线程的执行的对象
Lock 锁原语对象(跟thread 模块里的锁对象相同)
RLock 可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)。
Condition 条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。如,状态的改变或值的改变。
Event 通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。
Semaphore 为等待锁的线程提供一个类似“等候室”的结构
BounderSemaphore 与Semaphore 类似,只是它不允许超过初始值
Timer 与Thread 相似,只是,它要等待一段时间后才开始运行。

核心提示:守护线程。另一个避免使用thread 模块的原因是,它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。threading 模块支持守护线程,它们是这样工作的:守护线程一般是一个等待客户请求的服务器,没有客户提出请求,它就在那等着。如果你设定一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的daemon 属性。即,在线程开始(调用thread.start())之前,调用setDaemon()函数设定线程的daemon 标志(thread.setDaemon(True))就表示这个线程“不重要”如果你想要等待子线程完成再退出, 那就什么都不用做, 或者显式地调用thread.setDaemon(False)以保证其daemon 标志为False。你可以调用thread.isDaemon()函数来判断其daemon 标志的值。新的子线程会继承其父线程的daemon 标志。整个Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

1) Thread 类

用Thread 类,你可以用多种方法来创建线程。我们在这里介绍三种比较相像的方法。你可以任选一种你喜欢的,或最适合你的程序以及最能满足程序可扩展性的(我们一般比较喜欢最后一个选择):

􀁺 创建一个 Thread 的实例,传给它一个函数
􀁺 创建一个 Thread 的实例,传给它一个可调用的类对象
􀁺 从 Thread 派生出一个子类,创建一个这个子类的实例

Thread 对象的函数
函数 描述
start() 开始线程的执行
run() 定义线程的功能的函数(一般会被子类重写)
join(timeout=None) 程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout 秒
getName() 返回线程的名字
setName(name) 设置线程的名字
isAlive() 布尔标志,表示这个线程是否还在运行中
isDaemon() 返回线程的daemon 标志

setDaemon(daemonic) 把线程的daemon 标志设为daemonic(一定要在调用start()函数前调用)

threading 模块的函数
函数 描述
activeCount() 当前活动的线程对象的数量
currentThread() 返回当前线程对象
enumerate() 返回当前活动线程的列表
settrace(func) 为所有线程设置一个跟踪函数
settrace(func) 为所有线程设置一个profile 函数

2) 常用的Queue 模块的属性

常用的Queue 模块的属性
函数 描述
Queue 模块函数
queue(size) 创建一个大小为size 的Queue 对象
Queue 对象函数
qsize() 返回队列的大小(由于在返回的时候,队列可能会被其它线程修改,所以这个值是近似值)
empty() 如果队列为空返回True,否则返回False
full() 如果队列已满返回True,否则返回False

put(item,block=0)

把item 放到队列中,如果给了block(不为0),函数会一直阻塞到队列中有空间为止
get(block=0) 从队列中取一个对象,如果给了block(不为0),函数会一直阻塞到队列中有对象为止

6、相关模块

多线程相关的标准库模块
模块 描述
thread 基本的,底级别的线程模块
threading 高级别的线程和同步对象
Queue 供多线程使用的同步先进先出(FIFO)队列
mutex 互斥对象
SocketServer 具有线程控制的TCP 和UDP 管理器
posted @ 2017-11-06 10:36  Christal_11  阅读(150)  评论(0编辑  收藏  举报