Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1707049
  • 博文数量: 607
  • 博客积分: 10031
  • 博客等级: 上将
  • 技术积分: 6633
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-30 17:41
文章分类

全部博文(607)

文章存档

2011年(2)

2010年(15)

2009年(58)

2008年(172)

2007年(211)

2006年(149)

我的朋友

分类: Python/Ruby

2007-09-17 15:43:45


15. 可扩展标记语言(XML)
15.1. 知识点
15.2. 常见编程错误
15.3. 移植性提示
15.4. 软件工程知识
15.5. 性能提示
15.6. 示例
16. Python的XML处理
16.1. 知识点
16.2. 良好编程习惯
16.3. 示例
17. 数据库应用程序编程接口(DB-API)
17.1. 知识点
17.2. 良好的编程习惯
17.3. 常见编程错误
17.4. 移植性提示
18. 进程管理
18.1. 知识点
18.2. 良好的编程习惯
18.3. 移植性提示
19. 多线程处理
19.1. 知识点
19.2. 性能提示
19.3. 常见编程错误
19.4. 测试和调试提示
19.5. 性能提示
20. 联网
20.1. 知识点
20.2. 常见编程错误
20.3. 软件工程知识
20.4. 性能提示

Chapter 15. 可扩展标记语言(XML)

15.1. 知识点

  1. XML(Extensible Markup Language,可扩展标记语言)于1996年由万维网协分(W3C)下属的“XML工作组”开发成功。它是一种可移植的、获得普遍支持的开放式技术 (也就是一种非专利技术),它用于对数据进行描述。XML很快就成为在应用程序之间交换数据的一个标准。

  2. XML是区分大小写的,为处理XML文档,需要一种名为“XML解析器”的程序。解析器负责检查XML文档语法,并使XML文档的数据能提供应用程序使用。

  3. 在XML中也有命名空间的概念,就象python中的一样。以防止程序员自定义标识符与类库中的标识符出现“命名冲突”。命名空间的前缀标识元素属于哪个命名空间,从而对元素进行区分,如:


    Python How to Program

  4. 为 确保命名空间是独一无二的,文档作者必须提供唯一的URI,一种常见的做法是将URL作为URI使用,因为URL中使用的域名肯定是没有重复的。注意,解 析器永远不会去访问这些URL,它们只是一系列用于区分名称的字符。并不引用实际的网页,也不一定需要具有正确的形式。

  5. 为了避免为每个标记名都附加命名空间前缀,文档作者可用“默认命名空间”。即不用,而用的形式来创建和使用命名空间。

  6. 尽 管XML是文本文件,但通过顺序文件访问技术从中获取数据,显得既不实际,也没效率,尤其是在其中的数据需要动态添加和删除的时候。所以我们需要一种解析 器,对XML文档进行解析,有些XML解析器能将文档数据解析成树的形式存储在内存中,这个层次化的树结构被称为“文档对象模型(DOM)”树,能够创建 这类结构的XML解析器称为“DOM”解析器。

  7. SAX(Simple API for XML)是解析XML文档的另一种方法,使用的是一个“基于事件的模型”。SAX处理文档时,会生成名为“事件”的通知信息。软件程序可“侦听”这些事件,以便从文档获取数据。

  8. DTD(文档类型定义)和Schema(模式)是指定XML文档结构的文档(包含哪些元素是允许的,一个元素可以有什么属性等等)。提供一个DTD或Schema之后,解析器就会读取它,用于验证XML文档的结构是否合法。

  9. DTD使用EBNF(Extended Backus-Naur Form)语法来描述XML文档内容。

  10. 一个商务信函DTD文档的例子(节选)




    ...

    以上声明为letter元素定义规则。letter包含一个或多个contact元素、一个salutation、一个或多个paragraph元素,而 且必须按这个顺序排列。加号(+)是“出现次数指示符”,表明元素必须出现一次或者多次;星号(*)表示一个可选元素可以出现任意次数;问号(?)表示一 个可选元素最多只能出现一次。如果省略出现次数指示符,就默认为刚好出现一次。

  11. 一个使用上面DTD的XML文档例子(节选)

    	





    test
    test
    ...



    第一行是XML文档的标准声明,第二行引用一个DTD文档,由三部份组成,1是DTD的根元素名称(letter),2是关键字SYSTEM,指定一个外部DTDY文档;3是DTD文档的名称和位置。

  12. Schema 是W3C推荐规范,XML社区的许多开发者都认为DTD灵活性欠佳,不能满足当今的编程需要。例如,程序不能采取与XML文档相同的方式来处理DTD(例 如搜索,或者转换成XHTML等不同的表示形式),因为DTD本身不是XML文档。正因为存在这些限制,促使Schema的问世。和DTD不同, Schema不使用EBNF语法。相反,它使用XML语法,而且本质就是XML文档,可能采取程序化方法进行处理。类似DTD,Schema也需要验证解 析器。

  13. Schema使用XSV(XML Schema Validator,XML模式验证程序)来验证XML文档。可在线使用XSV,请访问,要想下载XSV,请访问

  14. 一个使用Schema的XML文档例子






    python how to program


    perl how to program



  15. Schema文档通常采用.xsd扩展名。这个例子是上面XML文档的模式文档。




    xmlns:test = ""
    targetNamespace = "" >

















    在Schema中,element元素定义一个元素。name和type属性分别指定元素名称和数据类型。complexType元素定义一个复杂类型的元素类型,xsd:string定义title元素的数据类型。

  16. XML 允许作者自行创建标记,以便精确描述数据,各个学术领域的人和组织创建了大量XML词汇表来结构化数据。其中一些词汇表包括MathXML(数学标记语 言)、可扩展矢量图形(SVG)、无线标记语言(WML)、可扩展商业报表语言(XBRL)、可扩展用户界面语言(XUL)、以及VoiceXML。 Schema和XSL(可扩展样式表语言)也都是XML语汇表的例子。

  17. MathXML由W3C开发,用于描述数学符号及表达式。以前是需要专业软件包(如LaTeX)才能显示复杂的数学表达式。可解析和呈现MathXML的一个应用程序是W3C的Amaya浏览器/编辑器。可到以下网址下载

  18. 可 扩展样式表语言(Extensible Stylesheet Language,XSL)是一种XML语汇表,用于格式化XML。XSLT是XSL的一部份,负责XSL转换(XSLT),它可根据XML文档创建以格 式化好的文本为基础的文档。这个创建过程称为“转换”,其中牵涉到两个树结构,即源树(要转换的XML文档)和结果树(转换结果,例如XHTML)。完成 转换后,源树不会发生变化。要执行转换,需要使用XSLT处理器。流行的XSLT处理器包括微软的MSXML、APACHE SOFTWARE FOUNDATION的Xalan 2和Python的4XSLT包。

15.2. 常见编程错误

  1. 不正确地嵌套XML标记会导致出错。

  2. 属性值必须用双引号或单引号封闭,否则就是错误。

  3. 以任何大小写组合形式创建名为xml的命名空间前缀都是错误的。

15.3. 移植性提示

  1. 尽管DTD是可选的,但DTD可使不同的程序生成的XML文档保持一致,所以建议使用。

15.4. 软件工程知识

  1. 属性无需用命名空间前缀加以限定,因为它们肯定同元素联系在一起。

  2. 尽管SAX已经获得了广泛的工业支持,但XML-DEV邮件列表的成员在开发SAX时是独立于W3C的。DOM是正式的W3C推荐规范。

  3. 许多组织和个人都在积极地为大范围的应用程序(比如金融业务、医药处方等)创建DTD和Schema。这些集合统称为Repositions,通常可从Web免费下载,如

15.5. 性能提示

  1. 处理大型XML文档时,基于SAX的解析通常比基于DOM的解析更有效。尤其重要的是,SAX解析器不会将整个XML文档载入内存。

  2. 如果文档只需解析一次,最有效的就是基于SAX的解析,如果程序要从文档快速获取信息时,DOM解析通常比SAX解析更有效。要求节省内存的机器通常使用基于SAX的解析器。

15.6. 示例

Example 15.1. sort.xml





Mary's XML Primer

Mary
White








advanced XML


Intermediate XML


Parsers and Tools


Entities


XML Fundamentals





Example 15.2. sorting.xsl





xmlns:xsl = "">


doctype-system =
""
doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/>











ISBN<xsl:value-of select = "@isbn" />-<xsl:value-of select = "title" />






by,


































( pages )

Chapter

( pages )

Appendix

( pages )


Pages:
select = "sum(chapters//*/@pages)" />


Media Type:






Chapter 16. Python的XML处理

16.1. 知识点

  1. 在python中,可以使用DOM(文档对象模型)和SAX(Simple API for XML)处理xml文档。

16.2. 良好编程习惯

  1. 尽管在python2.0或更高版本中不需要调用releaseNode方法,但请坚持这样做,以确保从内存中清除DOM树。

16.3. 示例

Example 16.1. 动态生成XML内容

perl,perl
ok,ok
test,test
check,check
python,python
jython,jython
上面是用于动态生成XML的原始文档。
#chapter16.1
#making up a text file's data as XML

import sys

print "Content-type:text/xml\n"

#write XML declaration and processing instruction
print """
href = "name.xsl"?>"""

#open data file
try:
file = open( "names.txt","r" )
except IOError:
sys.exit( "Error opening file" )

print "" #write root element

#list of tuples:(special character,entity reference)
replaceList = [ ( "&", "&" ),
( "<", "<" ),
( ">", ">" ),
( '"', """ ),
( "'", "'" ) ]

#replace special characters with entity reference
for currentLine in file.readlines():

for oldValue, newValue in replaceList:
currentLine = currentLine.replace( oldValue, newValue )

#extract lastname and firstname
last, first = currentLine.split( "," )
first = first.strip() #remove carriage return

#write contact element
print """
%s
%s
""" % ( last, first )

file.close()

print "
"
以上是将文本转换成XML的程序。





xmlns:xsl="">






Contact List




















First Name Last Name









以上是把动态生成的XML转换成HTML的XSLT。

Example 16.2. 一个XML论坛的例子








Feedback

python
my first forum
this is a xml forum.



--- 用于表示一个论坛的XML文档(内含一篇文章)---
===============================================================================






Feedback



--- 显示所有论坛的XML文档(内含一个feedback论坛) ---
===============================================================================

#!c:\python23\python.exe
# filename: default.py
# Default page for message forums

import os
import sys
from xml.dom.ext.reader import PyExpat

def printHeader( title, style ):
print """Content-type: text/html


"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">



%s



""" % ( title, style )

# open XML document that contains the forum names and locations
try:
XMLFile = open( "../htdocs/XML/forums.xml" )
except IOError:
print "Location: /error.html\n"
sys.exit()

# parse XML document containing forum information
reader = PyExpat.Reader()
document = reader.fromStream( XMLFile )
XMLFile.close()

# write XHTML to browser
printHeader( "my Forum", "/XML/site.css" )
print """

my Forum


Available Forum


    """

    # determine client-browser type
    if os.environ[ "HTTP_USER_AGENT"].find( "MSIE" ) != -1:
    prefix = "../XML/"
    else:
    prefix = "forum.py?file="

    # add links for each forum
    for forum in document.getElementsByTagName( "forum" ):

    #create link to forum
    link = prefix + forum.attributes.item( 0 ).value

    #get element nodes containing tag name "name"
    name = forum.getElementsByTagName( "name" )[ 0 ]

    #get Text node's value
    nameText = name.childNodes[ 0 ].nodeValue
    print '
  • %s
  • ' % ( link, nameText )

    print """

Forum Management




"""

reader.releaseNode( document )

--- 论坛的默认页面 ---
===============================================================================

#!c:\python23\python.exe
# filename: addForum.py
# Adds a forum to the list

import re
import sys
import cgi

#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint

def printHeader( title, style ):
print """Content-type: text/html


"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">



%s



""" % ( title, style )

form = cgi.FieldStorage()

# if user enters data in form fields
if form.has_key( "name" ) and form.has_key( "filename" ):
newFile = form[ "filename" ].value

#determine whether file has xml extension
if not re.match( "\w+\.xml$", newFile ):
print "Location: /error.html\n"
sys.exit()
else:
# create forum files from xml files
try:
newForumFile = open( "../htdocs/XML/" + newFile, "w" )
forumsFile = open( "../htdocs/XML/forums.xml", "r+" )
templateFile = open ( "../htdocs/XML/template.xml" )
except IOError:
print "Location: /error.html\n"
sys.exit()

# parse forums document
reader = PyExpat.Reader()
document = reader.fromStream( forumsFile )

#add new forum element
forum = document.createElement( "forum" )
forum.setAttribute( "filename", newFile )

name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )

# obtain root element of forum
documentNode = document.documentElement
firstForum = documentNode.getElementsByTagName( "forum" )[ 0 ]
documentNode.insertBefore( forum, firstForum )

# write update XML to disk
forumsFile.seek(0, 0)
forumsFile.truncate()
PrettyPrint( document, forumsFile )
forumsFile.close()

# create document for new forum from template file
document = reader.fromStream( templateFile )
forum = document.documentElement
forum.setAttribute( "file", newFile )

# create name element
name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )

# write generated XML to new forum file
PrettyPrint( document, newForumFile )
newForumFile.close()
templateFile.close()
reader.releaseNode( document )

print "Location: default.py\n"

else:
printHeader( "Add a forum", "/XML/site.css" )
print """

Forum Name



Forum File Name







Return to Main Page.

"""

--- 向forums.xml添加新论坛的脚本 ---
===============================================================================










---用于生成新论坛的XML模板 ---
===============================================================================

#!c:\python23\python.exe
# Adds a message to a forum

import re
import os
import sys
import cgi
import time

#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint

def printHeader( title, style ):
print """Content-type: text/html


"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">



%s



""" % ( title, style )

# identify client browser
if os.environ[ "HTTP_USER_AGENT" ].find( "MSIE" ) != -1:
prefix = "../XML/" #Internet Explorer
else:
prefix = "forum.py?file="

form = cgi.FieldStorage()

#user has submitted message to post
if form.has_key( "submit" ):
filename = form[ "file" ].value

# add message to forum
if not re.match( "\w+\.xml$", filename ):
print "Location: /error.html\n"
sys.exit()

try:
forumFile = open( "../htdocs/XML/" + filename, "r+" )
except IOError:
print "Location: /error.html\n"
sys.exit()

# parse forum document
reader = PyExpat.Reader()
document = reader.fromStream( forumFile )
documentNode = document.documentElement

# create message element
message = document.createElement( "message" )
message.setAttribute( "timestamp", time.ctime( time.time() ) )

# add elements to message
messageElements = [ "user", "title", "text" ]

for item in messageElements:

if not form.has_key( item ):
text = "( Field left blank )"
else:
text = form[ item ].value

# create nodes
element = document.createElement( item )
elementText = document.createTextNode( text )
element.appendChild( elementText )
message.appendChild( element )

#append new message to forum and update document on disk
documentNode.appendChild( message )
forumFile.seek(0,0)
forumFile.truncate()
PrettyPrint( document, forumFile )
forumFile.close()
reader.releaseNode( document )

print "Location: %s\n" % ( prefix + form[ "file" ].value )

# create form to obtain new message
elif form.has_key( "file" ):
printHeader( "Add a posting", "/XML/site.css" )
print """\n

User



Message Title



Message Text








Return to Forum


""" % ( form[ "file" ].value, prefix + form[ "file" ].value )
else:
print "Location: /error.html\n"

--- 为论坛添加文章的脚本 ---
===============================================================================





xmlns:xsl = "">













<xsl:value-of select = "name" />










































by

at









--- 将XML转换成XHTML的XSLT样式表 ---
===============================================================================

#!c:\python23\python.exe
#display forum postings for non-Internet Explorer browser.

import re
import cgi
import sys
from xml.xslt import Processor

form = cgi.FieldStorage()

# form to display has been specified
if form.has_key( "file" ):

# determine whether file is xml
if not re.match( "\w+\.xml$", form[ "file" ].value ):
print "Location: /error.html\n"
sys.exit()

try:
style = open( "../htdocs/XML/formatting.xsl" )
XMLFile = open( "../htdocs/XML/" + form[ "file" ].value )
except IOError:
print "Location: /error.html\n"
sys.exit()

# create XSLT processor instance
processor = Processor.Processor()

# specify style sheet
processor.appendStylesheetStream( style )

# apply style sheet to XML document
results = processor.runStream( XMLFile )
style.close()
XMLFile.close()
print "Content-type: text/html\n"
print results
else:
print "Location: /error.html\n"

--- 为不支持XSLT的浏览器将XML转换成HTML ---

Chapter 17. 数据库应用程序编程接口(DB-API)

17.1. 知识点

  1. python程序员使用遵循Python DB-API(数据库应用程序编程接口)规范的模块与数据库通信。

  2. DB-API描述了一个用于访问(连接)数据库的Connection对象。程序可用该对象创建Cursor对象,该对象负责处理和接收数据。

  3. 利 用Cursor对象,可用3种方法从查询结果集中获取行,即fetchone、fetchmany和fetchall。fetchone方法返回一个元 组,其中包含存储在Cursor中的一个结果集的下一行;fetchmany方法需要指定一个参数,即返回行的行数,并以元组的元组形式,从结果集中返回 后续一系列行;fetchall方法则采用“元组的元组”的形式,返回结果集中的所有行。

  4. DB-API的优点是,程序能方便地连接各种数库,python代码修改量很少,但不同数据库间转换时SQL代码需作修改。

17.2. 良好的编程习惯

  1. 习惯上,在不区分大小写的系统上,SQL关键字要全部采用大写字母。以突出显示SQL的关键字。

  2. 程序不再需要Cursor和Connection对象后,用相应的close方法显式关闭它们。

17.3. 常见编程错误

  1. SQL语句将单引号(')作为字符串的定界符。如果字符串本身包含有一个单引号(如 don't),就必须用两个单引号来表示一个单引号(如 don''t)。如果没有这样转义,就会造成SQL语句语法错误。

17.4. 移植性提示

  1. 并非所有数据库系统都支持like运算符,有的数据库在like表达式中使用的不是%,而是*。

Chapter 18. 进程管理

18.1. 知识点

  1. 有 两种方式来实现并发性,一种方式是让每个“任务"或“进程”在单独的内在空间中工作,每个都有自已的工作内存区域。不过,虽然进程可在单独的内存空间中执 行,但除非这些进程在单独的处理器上执行,否则,实际并不是“同时”运行的。是由操作系统把处理器的时间片分配给一个进程,用完时间片后就需退出处理器等 待另一个时间片的到来。另一种方式是在在程序中指定多个“执行线程”,让它们在相同的内存空间中工作。这称为“多线程处理”。线程比进程更有效,因为操作 系统不必为每个线程创建单独的内存空间。

  2. 新建进程用os.fork函数。但它只在POSIX系统上可用,在windows版的python中,os模块没有定义os.fork函数。相反,windows程序员用多线程编程技术来完成并发任务。

  3. os.fork 函数创建进程的过程是这样的。程序每次执行时,操作系统都会创建一个新进程来运行程序指令。进程还可调用os.fork,要求操作系统新建一个进程。父进 程是调用os.fork函数的进程。父进程所创建的进程叫子进程。每个进程都有一个不重复的进程ID号。或称pid,它对进程进行标识。子进程与父进程完 全相同,子进程从父进程继承了多个值的拷贝,如全局变量和环境变量。两个进程的唯一区别是fork的返回值。子进程接收返回值0,而父进程接收子进程的 pid作为返回值。

  4. 用os.fork创建的子进程和父进程作为异步的并发进程而单独执行。异步是指它们各行其是,相互间不进行同步;并发是指它们可同时执行。所以我们无法知道子进程和父进程的相对速度。

  5. os.wait 函数用于等待子进程结束(只适用于UNIX兼容系统)。该函数返回包含两个元素的元组,包括已完成的子进程号pid,以及子进程的退出状态,返回状态为 0,表明子进程成功完成。返回状态为正整数表明子进程终止时出错。如没有子进程,会引发OSError错误。os.wait要求父进程等待它的任何一个子 进程结束执行,然后唤醒父进程。

  6. 要指示父进程等候一个指定的子进程终止,可在父进程中使用os.waitpid函数(只 适用于unix兼容系统)。它可等候一个指定进程结束,然后返回一个双元素元组,其中包括子进程的pid和子进程的退出状态。函数调用将pid作为第一个 参数传递,并将一个选项作为第二个选项,如果第一个参数大于0,则waitpid会等待该pid结束,如果第一个参数是-1,则会等候所有子进程,也就和 os.wait一样。

  7. 用os.system 和 os.exec函数族来执行系统命令和其它程序。os.system使用shell来执行系统命令,然后在命令结束之后把控制权返回给原始进程; os.exec函数族在执行完命令后不将控制权返回给调用进程。它会接管python进程,pid不变。这两个函数支持unix和windows平台。

  8. os.popen ()函数可执行命令,并获得命令的stdout流。函数要取得两个参数,一个是要执行的命令,另一个是调用函数所用的模式,如“r"只读模式。 os.popen2()函数执行命令,并获得命令的stdout流和stdin流。函数返回一个元组,其中包含有两个文件对象,一个对象对应stdin 流,一个对象对应stdout流。

  9. 进程使用IPC机制在进程间传递信息,一种IPC机制是“管道”,它是一种类似于文件 的对象,提供单向通信渠道。父进程可打开一个管道,再分支一个子进程。父进程使用管道将信息写入(发送到)子进程,而子进程使用管道从父进程读取信息。在 python中使用os.pipe函数创建管道。

  10. os._exit()类似于sys.exit(),但它不执行任何的清 除工作(例如刷新缓冲区)。所以os._exit()尤其适用于退出子进程。如果程序使用sys.exit(),操作系统会回收父进程或其它子进程可能仍 然需要的资源。传给os._exit()函数的参数必须是进程的退出状态。退出状态为0,表示正常终止。

  11. 进程也可用信号 进行通信。所谓“信号”,是操作系统采取异步方式传给程序的消息。如CTRL+C会传递一个“中断信号”,通常该信号导致程序中止。然而程序完全可以指定 用不同的行动来响应任何一个信号。在信号处理中,程序要接收信号,并根据那个信号采取一项行动。错误(例如向已关闭管道写入)、事件(例如计时器变成0) 以及用户输入(例如按ctrl+c)都会产生信号。

  12. 针对每个信号,每个python程序都有一个默认的信号处理程序。例 如,假定python解释器收到一个信号,该信号指出程序试图向已关闭的管道写入,或者用户敲入一个键盘中断,python就会引发一个异常。发生异常 后,程序既可使用默认处理程序,也可使用自定义处理程序。

  13. signal.signal函数为中断信号注册一个信号处理程序。函数要获得两个参数:一个信号和一个对应于信号处理程序的函数。

  14. 在unix/linux 系统中,子进程终止后,会保留在进程表中,让父进程知道子进程是否正常终止。如果创建大量子进程,但在终止后没有从进程表中移除它们,进程表便会积累越来 越多的死进程,这些进程称为“zombies”(僵尸进程),消除僵尸进程的操作称为“reaping”,这是通过os.wait和os.waitpid 函数实现的。

18.2. 良好的编程习惯

  1. 进程应关闭不需要的管道端,因为操作系统限制了可同时打开的文件说明符数量。

18.3. 移植性提示

  1. 并不是所有操作系统都能从一个正在运行的程序创建单独的进程,所以,进程管理是移植性最差的一项python特性。

  2. 每个系统都定义了特有信号集。signal是依赖于具体平台的模块,其中只包含系统定义的信号。

Chapter 19. 多线程处理

19.1. 知识点

  1. 线程是“轻量级”进程,因为相较于进程的创建和管理,操作系统通常会用较少的资源来创建和管理线程。操作系统要为新建的进程分配单独的内在空间和数据;相反,程序中的线程在相同的内存空间中执行,并共享许多相同的资源。多线程程序在结内存的使用效率要优于多进程程序。

  2. python 提供了完整的多线程处理类,如果操作系统支持多线程,就可用python的threading模块创建多线程应用程序。程序员可以在一个应用程序中包含多 个执行线程,而且每个线程都表明程序中的一部份要与其他线程并发执行。许多应用程序都可获益于多线程编程。Web浏览器下载大文件时(比如音乐或视频), 用户希望立即可欣赏音乐或观看视频,这样就可以让一个线程下载,另一个线程播放已经下载的一部分。从而实现多个操作并发执行。

19.2. 性能提示

  1. 单线程程序问题在于要在结束费时较长的操作后,才能开始其它操作。而在多线程程序中,线程可共享一个或多个处理器,使多个任务并行执行。

  2. 解 释器开始执行程序时,“主”线程开始执行。每个线程都可创建和启动其它线程。如果程序包含多个正在运行的线程,它们将依据指定的间隔时间(称为一个 quantum),依次进入和离开解释器。Python的“全局解释器锁”(Global Interpreter Lock,GIL)保证解释器在任何时刻只运行一个线程。GIL每次可用时,都会有单个线程包含它,然后,线程进入解释器,关在该线程的quantum时 间段中执行它。一旦quantum到期,线程就离开解释器,同时释放GIL。

  3. 在任何时刻,线程都处于某种线程状态。新线 程将从“born”状态开始它的生命周期。线程保持这个状态,直到程序调用线程的start方法,这会使线程进入“ready”(就绪)状态,有时也称为 “runnable”状态。另外,控制权会立即返回至调用线程(调用者)。之后,调用者可与已启动的线程以及程序中的其他任何线程并发执行。当 “ready”线程首次获得GIL(Global Interpreter Lock,全局解释器锁),会执行它的run方法,成为一人“running”(正在运行)线程。run方法会一直执行,直到线程引发一个未处理的异常, 或者线程离开解释器。running线程离开解释器时,线程会记住它的当前执行位置。以后线程重新进入解释器,线程会从该位置继续执行。线程惟一能获得 GIL的状态就是“running”状态。

  4. run方法返回或终止(如遇到一个未进行捕捉的异常),就会进入“dead”状态。解释器最终会对dead线程进行处理。如果running线程调用另一个线程的join方法,running线程会失去GIL,并等待加入的方法死亡之后才会继续。

  5. 大 多数程序都使用外部资源(比如网络连接和磁盘文件)来执行任务。如果线程请求的资源不可用,线程就会进入“blocked”(暂停或阻塞)状态,直到资源 再次可用,线程发了I/O请求后(比如从磁盘上读入文件,或将文件发送到打印机),会失去GIL,并离开解释器。其它线程就中使用解释器,从而可高效利用 处理器,有助缩短程序的总体执行时间。I/O操作完成后,在“blocked”状态等候它的线程进入“ready”状态,之后,线程就会试图重新获得 GIL。

  6. “running”线程调用time.sleep函数后,会释放GIL并进入“sleeping”(休眠)状态。指定的休眠时间到期,“sleeping”线程会返回“ready”状态。即使解释器可用,“sleeping”线程也不能使用解释器。

  7. 程序中的线程通常共享数据。如果多个线程修改相同的数据,数据会出错,在这样的程序中,需要同步对共享数据的访问。这意味着访问共享数据的每个线程首先必须获得与数据对应的一个同步对象锁。一旦线程处理完数据,就应释放同步对象,使其它线程能访问数据。

  8. 有 时因为一个程序的逻辑需求,正在运行的线程即使为共享数据获得了同步对象,也不能对其执行操作。这种情况下,线程可调用同步对象的wait方法以主动释放 对象。这会导致线程释放GIL并针对那个同步对象进入“waiting”状态。另一个线程调用同步对象的notify方法时,那个同步对象的一个 “waiting”线程会变成“ready”状态。在重新获得GIL后,线程就会恢复执行。“running”线程调用同步对象的notifyAll方 法,处于“waiting”状态的每个线程都会变成“ready”状态。然后,解释器选择一个“ready”线程来执行。

  9. threading.currentThread函数会返回对当前正在运行的线程的一个引用。

  10. threading.enumerate函数返回一个列表,其中包含Thread类的当前所有活动对象(即run方法开始但未终止的任何线程)。

  11. threading.activeCount函数返回上面列表的长度。

  12. isAlive可测试线程是否死亡,如果返回1代表线程正在运行;setName方法设置线程名称;getName方法返回线程名称;为一个线程使用print方法会显示线程名称及其当前状态。

  13. 通常将访问共享数据的代码区称为“临界区”

  14. 访 问共享数据的每个线程都禁止其他所有线程同时访问相同的数据。这称为“独占”或“线程同步”。threading模块提供了许多线程同步机制。最简单的同 步机制是“锁”。锁对象用threading.RLock类创建,它定义了两个方法,即acquire(获得)和release(释放)。线程调用 acquire方法,锁会进入“locked”(锁定)状态,每次只有一个线程可获得锁。如另一个线程试图对同一个锁对象调用acquire方法,操作系 统会将那个线程转变为“blocked”状态,直到锁变得可用。拥有锁的线程调用release方法,锁会进入“unlocked”(解锁)状态。 “blocked”的线程会收到一个通知,并可获得锁。如果多个锁处于“blocked”状态,所有线程都会先解除该状态。然后,操作系统选择一个线程来 获得锁,再将其余线程变回“blocked”状态。

  15. 在多线程程序中,线程必须先获得一个锁,再进入临界区;退出临界区时,则要释放锁。

  16. 在 复杂的线程中,有时只在发生某些事件时才访问一个临界区(比如在某个数据值改变时)。这是通过“条件变量”来完成的。线程用条件变量来监视一个对象的状 态,或者发出事件通知。对象状态改变或事件发生时,处于blocked状态的线程会收到通知。收到通知后线程才访问临界区。

  17. 条 件变量用threading.Condition类创建。条件变量包含基本锁,所以它们提供了acquire和release方法。条件变量的其它方法还 有wait和nofify。线程成功获得一个基本锁后,调用wait方法会导致调用线程释放这个锁,并进入“blocked”状态,直到另一个线程调用同 一个条件变量的notify方法,从而将其唤醒。notify方法可唤醒一个正在等待条件变量的线程;notifyAll则唤醒所有正在等待的方法。

  18. 下 面通过一个“生产者/消费者”的关系,说明线程同步的应用情况。在这个关系中,应用程序的“生产者”部分生成数据,“消费者”部分使用数据,在多线程的生 产者/消费者关系中,“生产者线程”调用一个“生产方法”来生成数据,并将数据放到名为“缓冲区”的共享内存区域。“消费者线程”则调用一个“消费方法” 来读取数据。如果正在等待放入下一批数据的生产者线程发现消费者线程尚未从缓冲区中读取上一批数据,生产者线程就会调用条件变量的wait方法;否则,消 费者线程将无法看到上一批数据。消费者线程读取数据时,应调用条件变量的notify方法,使正在等待的生产者线程继续。如果消费者线程发现缓冲区为空, 或上一批数据已读取,就应调用条件变量的wait方法;否则,消费者线程会从缓冲区读入“垃圾”数据,或者重复处理以前的数据项--任何一种可能都会导致 应用程序出现逻辑错误。生产者将下一批数据放入缓冲区时,生产者线程应调用条件变量的notify方法,让消费者线程继续。

  19. 为尽可能缩短共享资源并以相同相对速度工作的各线程的等待时间,可用一个“队列(Queue)”来提供额外的缓冲区,便于生产者在其中放值,也便于消费者从中获得那些值。

  20. python提供一个Queue模块,该模块定义一人Queue类,即队列的一个同步实现。

  21. “信号机”(Semaphore)是一个变量,控制着对公共资源或临界区的访问。信号机维护着一个计数器,指定可同时使用资源或进入临界区的线程数。每次有一个线程获得信号机时,计数器都会自减。若计数器为0,其它线程便只能暂停访问信号机,直到另一个线程释放信号机。

  22. threading模块定义了可用于线程通信的Event(事件)类。Event有一个内部标记,可为true或false。一个或多个线程能调用Event对象的wait方法以暂停并等待事件发生。事件发生后,暂停的线程会按它们抵达的顺序被唤醒,并恢复执行。

19.3. 常见编程错误

  1. 必须把所有临界区都封闭在acquire和release调用之间。

  2. 如果程序使用了锁,就要仔细检查,保证程序不会死锁。如果程序或线程永远处于“blocked”状态,就会发生死锁。

  3. 等待一个条件变量的线程必须用notify显式唤醒,否则它会永远地等待下去,导致死锁。

19.4. 测试和调试提示

  1. 保证每个wait调用都有一个对应的notify调用,以最终结束等待,也可调用notifyAll以策万全。

19.5. 性能提示

  1. 通过同步来确保多线程程序的正确性,可能会减慢程序的运行速度,这是由于锁造成了额外的开销,而且需在线程的不同状态间频繁切换。

Chapter 20. 联网

20.1. 知识点

  1. python提供流套接字(tcp)和数据报套接字(udp)。

  2. urlparse模块提供了用于解析url的函数,以及用于url处理的函数。

  3. 要在python中建立具有TCP和流套接字的简单服务器,需要使用socket模块。利用该模块包含的函数和类定义,可生成通过网络通信的程序。建立这个连接需要6个步骤:

    • 第一步是创建socket对象。调用socket构造函数。如:

      socket = socket.socket( family, type )

      family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。

      type参数代表套接字类型,可为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)。

    • 第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:

      socket.bind( address )

      由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。

    • 第三步是使用socket套接字的listen方法接收连接请求。

      socket.listen( backlog )

      backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。

    • 第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。

      connection, address = socket.accept()

      调 用accept方法时,socket会时入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的 元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址。

    • 第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输 数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符 串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区 删除(以及自上次调用recv以来,客户可能发送的其它任何数据)。

    • 第六步,传输结束,服务器调用socket的close方法关闭连接。

  4. 在python中建立一个简单客户需要4个步骤。

    • 第一步是创建一个socket以连接服务器:

      socket = socket.socket( family, type )

    • 第二步是使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:

      socket.connect( (host,port) )

      host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。如连接成功,客户就可通过套接字与服务器通信,如果连接失败,会引发socket.error异常。

    • 第三步是处理阶段,客户和服务器将通过send方法和recv方法通信。

    • 第四步,传输结束,客户通过调用socket的close方法关闭连接。

20.2. 常见编程错误

  1. 套接字send方法只接受一个字符串参数。传递不同类型的值(比如整数)会出错。

20.3. 软件工程知识

  1. 端口号范围在0--65535之间,很多操作系统为系统服务保留了1024以下的端口号。应用程序在得到特别授权后才能使用这些保留端口号。服务器端应用程序一般不要把1024以下的端口号指定为连接端口。

  2. 利用python的多线程能力,程序员可创建多线程服务器,以便同时管理多个并发的客户连接。

  3. 多线程服务器可用每个accept调用返回的套接字来创建一个线程,由它来管理通过该套接字进行的网络IO。另外,可让多线程服务器维护一个线程池,以便管理通过新建套接字进行的网络IO。

20.4. 性能提示

  1. 在内存充足的高性能系统中,多线程服务器可创建一个线程池。可快速分配这些线程,以管理通过每个新建套接字进行的网络IO。以后收到连接请求时,服务器不会产生创建线程的开销。

阅读(1956) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~