1、一个顺序执行的程序要从每个I/O终端信道检查用户的输入时,程序无论如何也不能在读取I/O终端信道的时候阻塞,因为用户输入的到达是不确定的。
阻塞会导致其他I/O信息的数据部能被处理,顺序执行的程序必须使用非阻塞I/O,或是带有计时器的阻塞I/O(这样才能保证
阻塞只是暂时的)。
2、 使用多线程编程和一个共享的数据结构如Queue (本章后面会介绍的一种多线程队列数据结构),
这种程序任务可以用几个功能单一的线程来组织:
UserRequestThread: 负责读取客户的输入,可能是一个I/O信道。程序可能创建多个线程, 每个客户一个,请求会被放入队列中。
RequestProcessor: 一个负责从队列中获取并处理请求的线程,它为下面那种线程提供输出。
ReplyThread: 负责把给用户的输出取出来,如果是网络应用程序就把结果发送出去,否则就保存到本地文件系统或数据库中。
3、
计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。进程 (有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。进程也可以通过fork和spawn操作 来完成其它的任务。不过各个进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(IPC), 而不能直接共享信息。
4、 线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据 以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合 作变为可能。实际上,在单 CPU 的系统中,真正的并发是不可能的,每个线程会被安排成每次只运
行一小会,然后就把 CPU 让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只 做自己的事,在需要的时候跟其它的线程共享运行的结果。
当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访 问的顺序不一样,有可能导致数据结果的不一致的问题。这叫做竞态条件(race condition)。幸运的是,大多数线程库都带有一系列的同步原语,来控制线程的执行和数据的访问。
另一个要注意的地方是,由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让 CPU 的时间分配有所倾斜。导致各个线程分配到的运行时间可能不尽相同,不尽公平。
5、 对 Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按以下方式执行:
1. 设置GIL
2. 切换到一个线程去运行
3. 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁GIL
6. 再次重复以上所有步骤
6、 当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用Python 退出进程的标准方法,如 sys.exit()或抛出一个 SystemExit 异常等。不过,你不可以直接 “杀掉”("kill")一个线程。
7、 核心提示:避免使用 thread 模块
出于以下几点考虑,我们不建议您使用 thread 模块。首先,更高级别的 threading 模块更为先进,对线程的支持更为完善,而且使用 thread 模块里的属性有可能会与 threading 出现冲突。其次,低级别的 thread 模块的同步原语很少(实际上只有一个),而 threading 模块则有很多。
8、thread模块和锁对象
函数 描述
thread模块函数
start_new_thread(function, args, kwargs=None) 产生一个新的线程,在新线程中用指定的参数和可选的
kwargs来调用这个函数。
allocate_lock() 分配一个LockType类型的锁对象
exit() 让线程退出
LockType类型锁对象方法
acquire(wait=None) 尝试获取锁对象
locked() 如果获取了锁对象返回True,否则返回False
release() 释放锁
9、使用线程和锁
import thread
from time import sleep, ctime
loops = [4,2]
def loop(nloop, nsec, lock):
print 'start loop', nloop, 'at:', ctime()
sleep(nsec)
print 'loop', nloop, 'done at:', ctime()
lock.release()
def main():
print 'starting at:', ctime()
locks = []
nloops = range(len(loops))
for i in nloops:
lock = thread.allocate_lock()
lock.acquire()
locks.append(lock)
for i in nloops:
thread.start_new_thread(loop, (i, loops[i], locks[i]))
for i in nloops:
while locks[i].locked(): pass
print 'all DONE at:', ctime()
if __name__ == "__main__":
main()
运行结果:
>>> starting at: Sat Nov 03 20:02:34 2012
start loop 0 at: Sat Nov 03 20:02:34 2012
start loop 1 at: Sat Nov 03 20:02:34 2012
loop 1 done at: Sat Nov 03 20:02:36 2012
loop 0 done at: Sat Nov 03 20:02:38 2012
all DONE at: Sat Nov 03 20:02:38 2012
主要的工作在包含三个循环的main()函数中完成。我们先调用thread.allocate_lock()函数创建一个锁的列表,并分别调用各个锁的 acquire()函数获得锁。获得锁表示“把锁锁上”。锁上后,我们就把锁放到锁列表 locks 中。下一个循环创建线程,每个线程都用各自的循环号,睡眠时间和锁为参数去调用loop()函数。
在线程结束的时候,线程要自己去做解锁操作。最后一个循环只是坐在那一直等(达到暂停主线程的目的),直到两个锁都被解锁为止才继续运行。由于我们顺序检查每一个锁,所以我们可能会要长时间地等待运行时间长且放在前面的线程,当这些线程的锁释放之后,后面的锁可能早就释放了(表示对应的线程已经运行完了)。结果主线程只能毫不停歇地完成对后面这些锁的检查。