Python05标准库

Python标准库

Python有一套很有用的标准库(standard library)。标准库会随着Python解释器,一起安装在电脑中的。它是Python的一个组成部分。

正则表达式 (re包)

正则表达式(regular expression)主要功能是从字符串(string)中通过特定的模式(pattern),搜索想要找到的内容。

语法

1
文档: https://docs.python.org/3/library/re.html
1
2
3
import re
m = re.search('[0-9]','abcd5ef6ds')
print(m.group()) # 5

re.search()接收两个参数,第一个'[0-9]'就是正则表达式, 意为从字符串想要找的是从0到9的一个数字字符

re.search()如果从第二个参数找到符合要求的子字符串,就返回一个对象m,你可以通过m.group()的方法查看搜索到的结果。如果没有找到符合要求的字符,re.search()会返回None

正则表达式的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 搜索整个字符串,直到发现符合的子字符串。
m = re.search(pattern='[0-9]',string='abcd5ef6ds')
print(m.group()) # 5

# 从头开始检查字符串是否符合正则表达式。必须从字符串的第一个字符开始就相符。
m = re.match(pattern='[0-9]',string='abcd5ef6ds')
print(m) # None

# 搜索之后将搜索到的子字符串进行替换
str = re.sub(pattern='[0-9]',repl='xx',string='abcd5ef6ds')
print(str) # abcdxxefxxds

# 根据正则表达式分割字符串, 将分割后的所有子字符串放在一个表(list)中返回
result = re.split(pattern='[0-9]',string='abcd5ef6ds')
print(result) # ['abcd', 'ef', 'ds']

# 根据正则表达式搜索字符串,将所有符合的子字符串放在一个表(list)中返回
result = re.findall(pattern='[0-9]',string='abcd5ef6ds')
print(result) # ['5', '6']

# compile(), 根据包含的正则表达式的字符串创建模式对象, 以便于提高搜索效率
str = 'abcd5ef6ds'
reObj = re.compile(pattern='[0-9]')
print(reObj.split(str)) # ['abcd', 'ef', 'ds']
print(reObj.search(str).group()) # 5

写一个正则表达式

正则表达式的常用语法:

单个字符:

1
2
3
4
5
6
7
8
9
10
11
12
.          任意的一个字符
a|b 字符a或字符b
[afg] a或者f或者g的一个字符
[0-4] 0-4范围内的一个字符
[a-f] a-f范围内的一个字符
[^m] 不是m的一个字符
\s 一个空格
\S 一个非空格
\d [0-9]
\D [^0-9]
\w [0-9a-zA-Z]
\W [^0-9a-zA-Z]

重复

1
2
3
4
5
*         重复 >=0 次
+ 重复 >=1 次
? 重复 0或者1 次
{m} 重复m次。比如说 a{4}相当于aaaa,再比如说[1-3]{2}相当于[1-3][1-3]
{m, n} 重复m到n次。比如说a{2, 5}表示a重复2到5次。小于m次的重复,或者大于n次的重复都不符合条件。

位置

1
2
^ 字符串的起始位置
$ 字符串的结尾位置

返回控制

我们有可能对搜索的结果进行进一步精简信息。

1
2
3
4
5
6
7
m = re.search("output_(\d{4})","output_1988.txt")
# 用括号()包围了一个小的正则表达式,\d{4}。
# 这个小的正则表达式被用于从结果中筛选想要的信息(在这里是四位数字)。
# 这样被括号圈起来的正则表达式的一部分,称为群(group)。
print(m.group(0)) # output_1988
print(m.group(1)) # 1988
# group(0)是整个正则表达的搜索结果,group(1)是第一个群

我们还可以使用 ?P<name> 将群命名,以便更好地使用m.group查询:

1
2
3
m = re.search("output_(?P<year>\d{4})", "output_1988.txt")
# (?P<name>...) 为group命名
print(m.group('year')) # 1988

时间与日期 (time, datetime包)

time包

time包基于C语言的库函数(library functions)。

1
2
3
4
5
6
7
8
9
10
11
import time
print(time.time()) # 1653461946.6619015 挂钟时间(wall clock time)
# time.sleep(3) # 休眠三秒
print(time.ctime()) # Thu May 26 18:45:49 2021

st = time.gmtime() # 返回struct_time格式的UTC时间
print(st) # time.struct_time(tm_year=2021, ...)
print(st.tm_zone) # UTC
st = time.localtime() # 返回struct_time格式的当地时间, 当地时区根据系统环境决定。
s = time.mktime(st) # 将struct_time格式转换成wall clock time
print(s) # 1653462216

datetime包

简介

datetime包是基于time包的一个高级包, 为我们提供了多一层的便利。

datetime可以理解为date和time两个组成部分。date是指年月日构成的日期(相当于日历),time是指时分秒微秒构成的一天24小时中的具体时间(相当于手表)。你可以将这两个分开管理(datetime.date类,datetime.time类),也可以将两者合在一起(datetime.datetime类)。

1
2
3
4
5
6
7
import datetime
t = datetime.datetime(2012,9,6,21,30)
print(t) # 2012-09-06 21:30:00
# t包含以下属性:
# hour, minute, second, microsecond,year, month, day, weekday
print(t.hour) # 21
print(t.microsecond) # 0

运算

datetime包还定义了时间间隔对象(timedelta)。一个时间点(datetime)加上一个时间间隔(timedelta)可以得到一个新的时间点(datetime)。比如今天的上午3点加上5个小时得到今天的上午8点。同理,两个时间点相减会得到一个时间间隔。

1
2
3
4
5
6
7
8
9
10
11
import datetime
t = datetime.datetime(2012,9,4,21,30)
t_next = datetime.datetime(2012,9,6,21,30)
delta1 = datetime.timedelta(seconds=600) # 时间间隔1
delta2 = datetime.timedelta(weeks=3) # 时间间隔2
print(t+delta1) # 2012-09-04 21:40:00
print(t+delta2) # 2012-09-25 21:30:00
print(t_next - t) # 2 days, 0:00:00

# 两个datetime对象还可以进行比较。比如使用上面的t和t_next:
print(t > t_next) # False

datetime对象与字符串转换

用格式化读取的方式读取时间信息。

1
2
3
4
5
6
7
from datetime import datetime
format = "output_%Y-%m-%d-%H%M%S.txt" # 定义时间输出格式
str = "output_1997-12-23-030000.txt" # 获取时间字符串
t = datetime.strptime(str,format) # 将字符串转化为标准格式时间
print(t) # 1997-12-23 03:00:00
t_next = datetime(2012,9,6,21,30)
print(t_next.strftime(format)) # output_2012-09-06-213000.txt 将时间转化为字符串
1
2
3
4
5
# 案例
# 字符串转时间戳
print(time.mktime(time.strptime("2021-05-25 16:39:19",'%Y-%m-%d %H:%M:%S')))
# 时间戳转字符串
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(1621931959)))

数学与随机数 (math包,random包)

math包补充了很多的函数。如果想要更加高级的数学功能,可以考虑选择标准库之外的numpyscipy项目,它们不但支持数组和矩阵运算,还有丰富的数学和物理方程可供使用。

random包可以用来生成随机数。随机数不仅可以用于数学用途,还经常被嵌入到算法中,用以提高算法效率,并提高程序的安全性。

math包

math包主要处理数学相关的运算。math包定义了两个常数:

1
2
math.e   # 自然常数e
math.pi # 圆周率pi

此外,math包还有各种运算函数 (下面函数的功能可以参考数学手册):

1
2
3
4
5
math.ceil(x)       # 对x向上取整,比如x=1.2,返回2
math.floor(x) # 对x向下取整,比如x=1.2,返回1
math.pow(x,y) # 指数运算,得到x的y次方
math.log(x) # 对数,默认基底为e。可以使用base参数,来改变对数的基地。比如math.log(100,10)
math.sqrt(x) # 平方根

三角函数: math.sin(x), math.cos(x), math.tan(x), math.asin(x), math.acos(x), math.atan(x)

这些函数都接收一个弧度(radian)为单位的x作为参数。

角度和弧度互换: math.degrees(x), math.radians(x)

双曲函数: math.sinh(x), math.cosh(x), math.tanh(x), math.asinh(x), math.acosh(x), math.atanh(x)

特殊函数: math.erf(x), math.gamma(x)

random包

1
文档 : https://docs.python.org/3/library/random.html
1
2
3
4
5
6
7
8
9
10
11
12
13
import random
random.seed(x) # 改变随机数生成器的种子seed

# 随机挑选和排序
random.choice(seq) # 从序列的元素中随机挑选一个元素,比如random.choice(range(10)),从0到9中随机挑选一个整数。
random.sample(seq,k) # 从序列中随机挑选k个元素
random.shuffle(seq) # 将序列的所有元素随机排序

# 随机生成实数
random.random() # 随机生成下一个实数,它在[0,1)范围内。
random.uniform(a,b) # 随机生成下一个实数,它在[a,b]范围内。
random.gauss(mu,sigma) # 随机生成符合高斯分布的随机数,mu,sigma为高斯分布的两个参数。
random.expovariate(lambd) # 随机生成符合指数分布的随机数,lambd为指数分布的参数。

路径与文件 (os.path包, glob包)

os.path包

os.path包主要是处理路径字符串,比如说’/home/vamei/doc/file.txt’,提取出有用信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os.path
path = '/home/vamei/doc/file.txt'
print(os.path.basename(path)) # file.txt
print(os.path.dirname(path)) # /home/vamei/doc

info = os.path.split(path) # 将路径分割成文件名和目录两个部分,放在一个表中返回
print(info) # ('/home/vamei/doc', 'file.txt')
path2 = os.path.join('/','home', 'vamei', 'doc', 'file1.txt') # 使用目录名和文件名构成一个路径字符串
print(path2) # /home\vamei\doc\file1.txt

p_list = [path,path2]
print(os.path.commonpath(p_list)) # \home\vamei\doc 查询多个路径的共同部分
print(os.path.commonprefix(p_list)) # /home

print(os.path.normpath(path2)) # \home\vamei\doc\file1.txt 规范化路径

os.path还可以查询文件的相关信息(metadata)。文件的相关信息不存储在文件内部,而是由操作系统维护的,关于文件的一些信息(比如文件类型,大小,修改时间)。

1
2
3
4
5
6
7
8
import os.path
path = './first1.py'
print(os.path.exists(path)) # True 查询文件是否存在
print(os.path.getsize(path)) # 42 查询文件大小
atime = os.path.getatime(path) # 1621931959 查询文件上一次读取的时间
mtime = os.path.getmtime(path) # 1621931959 查询文件上一次修改的时间
print(os.path.isfile(path)) # True 路径是否指向常规文件
print(os.path.isdir(path)) # False 路径是否指向目录

glob包

glob.glob()。接受一个Linux式的文件名格式表达式(filename pattern expression),列出所有符合该表达式的文件(与正则表达式类似),将所有文件名放在一个表中返回。所以glob.glob()是一个查询目录下文件的好方法。

文件名格式表达式:

Filename Pattern Expression Python Regular Expression
* .*
? .
[0-9] same
[a-e] same
[^mnp] same
1
2
3
# 示例
import glob
print(glob.glob('./*')) # 找出当前目录下的所有文件

image-20220611020838258

文件管理 (部分os包,shutil包)

Python标准库则允许我们从Python内部管理文件。

os包

os包包括各种各样的函数,以实现操作系统的许多功能。这个包非常庞杂。os包的一些命令就是用于文件管理。

常用的有:

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
path = './ostest'
os.mkdir(path) # 创建新目录,不可重复创建同名文件夹
os.rmdir(path) # 删除空的目录
print(os.listdir(path)) # 返回目录中所有文件
os.remove(path+'/record.txt') # 删除 path指向的文件。
os.rename(path+'/record1.txt',path+'/record.txt') # 重命名文件
import stat
os.chmod(path,stat.S_IRWXU) # 改变path指向的文件的权限
os.chown(path+'/record.txt',uid,gid) # 改变path所指向文件的拥有者和拥有组
print(os.stat(path)) # os.stat_result(st_mode=16749,..) 查看path所指向文件的附加信息
os.symlink(path+'/record.txt',path+'/newlink') # 为文件创建软链接
print(os.getcwd()) # 查询当前工作路径 current working directory

shutil包

  • copy(src, dst) 复制文件,从src到dst。相当于$cp命令。
  • move(src, dst) 移动文件,从src到dst。相当于$mv命令。
1
2
3
4
5
6
import shutil
# print(help(shutil))
path = './ostest'
path2 = './shutiltest'
shutil.copy(path+"/record.txt",path2+"/r.txt") # 复制文件
shutil.move(path+"/record.txt",path+"/record1.txt") # 移动文件

存储对象 (pickle包,cPickle包)

Python“一切皆对象”, 在Python中,无论是变量还是函数,都是一个对象。如何将对象保存到文件,并储存在硬盘上呢

计算机的内存中存储的是二进制的序列 (当然,在Linux眼中,是文本流)。我们可以直接将某个对象所对应位置的数据抓取下来,转换成文本流 (这个过程叫做serialize),然后将文本流存入到文件中。由于Python在创建对象时,要参考对象的类定义,所以当我们从文本中读取对象时,必须在手边要有该对象的类定义,才能懂得如何去重建这一对象。

从文件读取时,对于Python的内建(built-in)对象 (比如说整数、词典、表等等),由于其类定义已经载入内存,所以不需要我们再在程序中定义类。但对于用户自行定义的对象,就必须要先定义类,然后才能从文件中载入对象

pickle包

将内存中的对象转换成为文本流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pickle

class Bird(object):
have_feather = True
way_of_reproduction = 'egg'

summer = Bird()
pickleString = pickle.dumps(summer)
print(pickleString)

# 写入文件二进制数据
file = open('record3.txt','wb')
file.write(pickleString)
file.close()

# 读取文件中二进制数据
f = open('record3.txt','rb')
print(f.read())

# 或者使用上下文管理
with open('record4.txt','wb') as f:
prickString = pickle.dump(summer, f)

image-20220611021533243

重建对象

首先从文本中读出文本,存储到字符串 (文本文件的输入输出)。然后使用pickle.loads(str)的方法,将字符串转换成为对象。要记得,此时我们的程序中必须已经有了该对象的类定义。

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
file = open('record3.txt','rb')
pString = file.read()
print(pString)
summer = pickle.loads(pString)
print(summer.__class__) # <class '__main__.Bird'>

# 或者使用上下文管理
with open('record4.txt','rb') as f:
summer = pickle.load(f)

print(summer) # <__main__.Bird object at 0x01DE0118>

image-20220611021850001

cPickle包

cPickle包的功能和用法与pickle包几乎完全相同 (其存在差别的地方实际上很少用到),不同在于cPickle是基于c语言编写的,速度是比pickle包的快。如果想使用cPickle包,我们都可以将import语句改为:

1
import cPickle as pickle

子进程 (subprocess包)

subprocess包主要功能是执行外部的命令和程序。比如在Python中调用wget程序。从这个意义上来说,subprocess的功能与shell类似。

常用的封装函数

使用subprocess包中的函数创建子进程的时候,要注意:

  • 1 在创建子进程之后,父进程是否暂停,并等待子进程运行。
  • 2 函数返回什么
  • 3 当returncode不为0时,父进程如何处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import subprocess

# (1)、subprocess.call()
# 父进程等待子进程完成
# 返回退出信息(returncode,相当于exit code)
rc = subprocess.call('dir /A', shell=True)
rc = subprocess.call(['dir', '/A'], shell=True)
print(rc) # 0

# (2)、subprocess.check_call()
# 父进程等待子进程完成, 返回0
# 检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,
# 该对象包含有returncode属性,可用try...except...来检查(见Python错误处理)。
rc = subprocess.check_call(['dir'],shell=True)
print(rc) # 0

# (3)、subprocess.check_output()
# 父进程等待子进程完成, 返回子进程向标准输出的输出结果
# 检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,
# 该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try...except...来检查。
try:
rc = subprocess.check_call(['dir','-al'],shell=True)
print(rc)
except subprocess.CalledProcessError:
print(subprocess.CalledProcessError.output)
print(subprocess.CalledProcessError.returncode)

Popen()

上面的三个函数都是基于Popen()的封装(wrapper)

与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。

我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import subprocess
child = subprocess.Popen(['ping','-c','5','www.hao123.com'])

child.wait()
print('parent process')
# 还可以在父进程中对子进程进行其它操作
# child.poll() # 检查子进程状态
# child.kill() # 终止子进程
# child.send_signal() # 向子进程发送信号
# child.terminate() # 终止子进程
# print(child.pid) # 获取子进程的PID
# print(child.stdin) # 获取子进程的标准输入
# print(child.stdout) # 获取子进程的标准输出
# print(child.stderr) # 获取子进程的标准错误

子进程的文本流控制

我们可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,

并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

1
2
3
4
5
6
7
8
9
import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
out = child2.communicate()
print(out)
# subprocess.PIPE实际上为文本流提供一个缓存区。
# child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。
# child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
# communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。

还可以利用communicate()方法来使用PIPE给子进程输入:

1
2
3
4
5
import subprocess
child = subprocess.Popen(['type'],stdin=subprocess.PIPE,shell=True)
child.communicate('vamei')
# 我们启动子进程之后,cat会等待输入,直到我们用communicate()输入"vamei"。
# communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。

信号 (signal包)

1
2
3
# signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。
# 要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。
# On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM. A ValueError will be raised in any other case.

定义信号名

signal包定义了各个信号名及其对应的整数,比如:

1
2
3
import signal
print signal.SIGALRM # 14
print signal.SIGCONT # 18

Python所用的信号名和Linux一致。你可以通过以下命令查询:

1
$ man 7 signal  # 这一条命令在实验楼环境中无法执行成功,需要在真实的 Linux 系统中才能成功执行

预设信号处理函数

signal包的核心是使用**signal.signal()**函数来预设(register)信号处理函数,如下所示:

1
2
3
4
signal.signal(signalnum, handler)   # signalnum为某个信号,handler为该信号的处理函数
# 当handler为signal.SIG_IGN时,信号被无视(ignore)。
# 当handler为singal.SIG_DFL,进程采取默认操作(default)。
# 当handler为一个函数名时,进程采取函数中定义的操作。
1
2
3
4
5
6
7
8
9
# 示例
import signal
# 定义信号处理函数
def myHandle(signum,frame):
print('I received signal: ', signum)
# 注册函数到信号上
signal.signal(signal.SIGTSTP, myHandle)
signal.pause()
print('End of Signal Demo')

image-20220611030743503

myHandler的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由signal.singnal()函数来传递的。

当程序运行到signal.pause()的时候,进程暂停并等待信号。此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。可以看到,进程执行了myHandle()函数, 随后返回主程序,继续执行。

定时发出SIGALRM信号

函数signal.alarm(),它被用于在一定时间之后,向进程自身发送SIGALRM信号:

1
2
3
4
5
6
7
8
9
10
import signal
def myHandle(signum,frame):
print('it is time to alarm')
exit()

signal.signal(signal.SIGALRM, myHandle)
signal.alarm(5)
while True:
print('not yet')
# 用了一个无限循环以便让进程持续运行。在signal.alarm()执行5秒之后,进程将向自己发出SIGALRM信号,随后,信号处理函数myHandler开始执行。

效果如下:

1
2
3
4
not yet
...
not yet
it is time to alarm

发送信号

signal包的核心是设置信号处理函数。除了signal.alarm()向自身发送信号之外,并没有其他发送信号的功能。

但在os包中,有类似于linux的kill命令的函数,分别为:

1
2
3
4
import os
os.kill(pid,sig)
os.killpg(pgid,sig)
# 分别向进程和进程组发送信号。sig为信号所对应的整数或者singal.SIG*

多线程与同步 (threading包)

Python主要通过标准库中的threading包来实现多线程。

多线程售票以及同步

使用mutex (也就是Python中的Lock类对象) 来实现线程的同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import threading,time,os
def doSomeThing():
time.sleep(0.5)

def booth(tid): # 每个线程执行动作
global i,lock
while True:
lock.acquire() # 锁定, 或者等待其他线程释放
if i != 0:
i-=1 # 售出票
print(tid,':now left:',i) # 打印余票
doSomeThing() # 可以安全地使用共享资源
else:
print('thread_id',tid,' no more tickets')
os._exit(0) # 立即退出所有操作
lock.release() # 释放锁
doSomeThing() # 可以做一些不使用共享资源的操作

i = 100
lock = threading.Lock() # 互斥锁 (mutex)

for k in range(10):
new_thread = threading.Thread(target=booth,args=(k,))
new_thread.start()
# for循环中利用threading.Thread()的方法来创建一个Thread对象,并将函数booth()以及其参数传递给改对象,并调用start()方法来运行线程。

使用两个全局变量,一个是i,用以储存剩余票数;一个是lock对象,用于同步线程对i的修改。

此外,在最后的for循环中,我们总共设置了10个线程。每个线程都执行booth()函数。线程在调用start()方法的时候正式启动 (实际上,计算机中最多会有11个线程,因为主程序本身也会占用一个线程)。

Python使用threading.Thread对象来代表线程,用threading.Lock对象来代表一个互斥锁 (mutex)。

有两点需要注意:

  • 在函数中使用global来声明变量为全局变量,从而让多线程共享i和lock。如果不这么声明,由于i和lock是不可变数据对象,它们将被当作一个局部变量。如果是可变数据对象的话,则不需要global声明。我们甚至可以将可变数据对象作为参数来传递给线程函数。这些线程将共享这些可变数据对象。
  • 在booth中使用了两个doChore()函数。可以在未来改进程序,以便让线程除了进行i=i-1之外,做更多的操作,比如打印剩余票数,找钱,或者喝口水之类的。第一个doChore()依然在Lock内部,所以可以安全地使用共享资源 (critical operations, 比如打印剩余票数)。第二个doChore()时,Lock已经被释放,所以不能再去使用共享资源。这时候可以做一些不使用共享资源的操作 (non-critical operation, 比如找钱、喝水)。

OOP创建线程

通过面向对象 (OOP, object-oriented programming) 的方法实现多线程,其核心是继承threading.Thread类。

上面的for循环中已经利用了threading.Thread()的方法来创建一个Thread对象,并将函数booth()以及其参数传递给改对象,并调用start()方法来运行线程。OOP的话,通过修改Thread类的**run()**方法来定义线程所要执行的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import threading,time,os
def doSomeThing():
time.sleep(0.5)

class BoothThread(threading.Thread): # 每个进程执行动作
def __init__(self,tid,monitor):
self.tid = tid
self.monitor = monitor
threading.Thread.__init__(self)
def run(self):
while True:
monitor['lock'].acquire() # 锁定, 或者等待其他线程释放
if monitor['tick'] != 0:
monitor['tick'] -= 1 # 售票
print(self.tid, ': now left :', monitor['tick'])
doSomeThing()
else:
print('thread_id', self.tid, ' no more tickets')
os._exit(0)
monitor['lock'].release() # 释放锁
doSomeThing()

monitor = {'tick':20,'lock':threading.Lock()}
for k in range(10):
new_thread = BoothThread(k,monitor)
new_thread.start()

定义了一个类BoothThread, 这个类继承自thread.Threading类。

然后把上面的booth()所进行的操作统统放入到BoothThread类的run()方法中。

没有使用全局变量声明global,而是使用了一个词典monitor存放全局变量,然后把词典作为参数传递给线程函数。

由于词典是可变数据对象,所以当它被传递给函数的时候,函数所使用的依然是同一个对象,相当于被多个线程所共享。

其他

threading.Thread对象: 我们已经介绍了该对象的start()和run(), 此外:

  • **join()**方法,调用该方法的线程将等待直到该Thread对象完成,再恢复运行。这与进程间调用wait()函数相类似。

下面的对象用于处理多线程同步。对象一旦被建立,可以被多个线程共享,并根据情况阻塞某些进程。

下面的对象用于处理多线程同步。对象一旦被建立,可以被多个线程共享,并根据情况阻塞某些进程。

threading.Lock对象: mutex, 有acquire()和release()方法。

threading.Condition对象: condition variable,建立该对象时,会包含一个Lock对象 (因为condition variable总是和mutex一起使用)。可以对Condition对象调用acquire()和release()方法,以控制潜在的Lock对象。此外:

  • **wait()**方法,相当于cond_wait()
  • **notify_all()**,相当与cond_broadcast()
  • **nofify()**,与notify_all()功能类似,但只唤醒一个等待的线程,而不是全部

threading.Semaphore对象: semaphore,也就是计数锁(semaphore传统意义上是一种进程间同步工具)。创建对象的时候,可以传递一个整数作为计数上限 (sema = threading.Semaphore(5))。它与Lock类似,也有Lock的两个方法。

threading.Event对象: 与threading.Condition相类似,相当于没有潜在的Lock保护的condition variable。对象有True和False两个状态。可以多个线程使用wait()等待,直到某个线程调用该对象的**set()方法,将对象设置为True。线程可以调用对象的clear()方法来重置对象为False状态。