第六部分 多任务处理
第20章 forking
1. fork()
多进程程序使用fork来实现,典型的fork结构:
pid = os.fork()
if pid:
#pid不是0,是父进程,pid是子进程的进场号
else:
#pid是0,子进程。
fork之后,每个进程都含有一个能够的地址空间,更改一个进程的变量不会影响其它进程中的变量。但是由于子进程会拷贝父进程的所有文件描述符和socket,只有两个进程都调用close之后socket才会被关闭。典型的情况是在fork之后马上就close不用的socket。
zombie进程。在子进程种植和父进程调用wait()之间的这段时间,子进程被称为zombie进程。所以必须调用wait函数。如果父进程在子进程之前终止,系统会把子进程的父进程设置为init,init会负责清除zombie进程。
firstfork.py是一个使用fork的实例。
zombieprob.py展示了zombie问题。
zombiesol.py解决zombie问题。
首先注册信号的处理程序,每次收到SIGCHLD的时候都会调用chldhandler。
signal.signal(signal.SIGCHLD, chldhandler)
chldhandler函数会调用result = os.waitpid(-1, os.WNOHANG)
zombiepoll.py采用轮询的方式来“收割”子进程。
2. forking服务器
echoserver.py是一个采用了forking的服务器。关键的语句是:
if pid:
clientsock.close()
continue
else:
s.close()
其中s是之前创建的服务器socket。在父进程中,不应该与clientsock通信,所以应该将它关闭。而在子进程中,程序不应该与s通信,所以应该将它关闭掉。
errorserver.py将fork放在一个try、catch中,避免服务器因为fork抛出的异常而崩溃。
3. 锁定
lockingserver.py文件演示了对文件的锁定和解锁。使用了fcntl模块,这个模块只在unix下有效。在对文件进行读操作的时候,会
fcntl.flock(fd, fcntl.LOCK_SH)
表示需要共享锁,其它进程可以读,但是不能写。读操作结束之后会进行解锁:
fcntl.flock(fd, fcntl.LOCK_UN)
解锁放在finally语句中,无论如何都会执行。在进行写操作的时候会要求独占锁:
fcntl.flock(fd, fcntl.LOCK_EX)
第21章 线程
1. 基础
firstthread.py是一个使用线程的示例。关键代码为:
t = threading.Thread(target = threadcode, name = "ChildThread")
t.setDaemon(1)
t.start()
t.join()
其中threadcode事先定义好的函数。在正常情况下,程序在所有线程都结束的时候才终止,但是这里t.setDaemon(1)使得程序不会等待该线程结束再终止。t.join()表示在子线程终止之前,父线程是被挂起的。
vars.py展示了线程之间共享变量。
locks.py展示了threading模块Lock锁的使用。创建一个锁:
l = threading.Lock()
使用l.acquire()取得锁,使用l.release()释放锁。Lock()确保了不会有多个线程同时修改一个变量而造成逻辑错误。
sem.py使用threading模块的Semaphore()信号。它的功能与Lock类似,但是多出一个计数器的功能。同样,使用acquire()来取得信号,使用release()来释放信号。
deadlock.py展示了死锁的情况。建议的实践是各个线程应该按照相同的顺序来取得锁,释放的时候则采取相反的顺序来释放锁。
2. 多线程服务器
echoserver.py建立了一个多线程的服务器。程序的大致结构是在主线程建立一个服务器socket,监听端口,当有新的客户端连接的时候就创建一个新线程,并且将客户socket传递给新建的线程,由新线程进行处理。
threadpool.py使用了线程池。线程在结束了任务之后并不是马上终止,而是等待着再次被安排新的任务。程序建立了两个字典busylist和waitinglist,其中busylist记录了正在工作的线程,而waitinglist记录了正在空闲中的线程。
难理解的地方:
1. 子线程为什么不会终止?子线程调用的processclients()包含了一个while 1的循环,不断地视图处理客户socket。所以其实子线程的任务永远都不会完成。
2. 那么如何确保子线程总能在合适的时机获得客户端连接呢?子线程会对信号sem.require。而当收到连接的时候才会sem.release()。所以子线程只有在有新连接的时候才能成功地执行处理连接的无限循环。
3. 多线程客户端
threadclient.py是一个有两个线程的客户端,主线程实现普通的客户端,子线程则不停地在屏幕上显示旋转的线。主要是为了展示网络连接的同时,利用另一个线程来控制UI显示。
第22章 异步通信
echoserver.py是一个采用了异步通信的服务器。核心在于使用select.poll()。
首先使用.register()来注册文件描述符,例如
self.p.register(fd, select.POLLIN | select.POLLOUT | self.stdmask)
然后在服务器的主循环中执行:
result = self.p.poll()
这里的result是一个元组的列表,格式为(fd, event),程序随后根据event对fd所代表的socket进行操作。
chatserver.py实现了一个简单的聊天室。没啥特别的。
inetd.py结合使用了fork和异步通信的特性。没看明白作者的意图。
twistedchatserver.py借助twisted库来实现了功能与chatserver.py相同的聊天室服务器。

本文探讨了多任务处理及并发编程技术,包括进程、线程及异步通信的实现方式。介绍了fork机制、僵尸进程处理、文件锁定、线程锁及信号量等概念,并通过示例代码详细解析了多线程服务器的设计原理。
2784

被折叠的 条评论
为什么被折叠?



