1. subprocess模块介绍
subprocess模块可用于产生进程,并连接到进程的输入/输出/错误输出管道,并获取进程的返回值。
该模块的完整描述,参考Python文档。
2. subprocess模块中的常量
PIPE: 一个特殊的值,指示应该创建一个管道
STDOUT: 指示stderr应该输出到stdout中
3. subprocess模块主要的API(4个)
(1)call (…) 运行一个命令,等待该命令执行完成(阻塞等待),然后返回该命令执行的返回码。
可以看出,call函数是构造了一个Popen对象,然后调用该对象的wait方法,阻塞等待,并返回命令执行的返回值。
(2)check_call (…) 类似于call(),但是会抛出CalledProcessError()异常,如果命令执行的返回码不是0的话。CalledProcessError对象会将返回码记录在returncode属性中。
可以看到,check_call中调用了call方法,因此check_call方法也是创建了进程了之后,阻塞等待,第一句:retcode =call(*popenargs, **kwargs),执行的是 retcode = Popen(*popenargs,**kwargs).wait()
如果线程退出值为0,则返回0;否则抛出CalledProcessError异常。
(3)check_output (…) 类似于check_call,但是该函数返回的是命令执行的输出内容,而不是返回值。
check_output()函数中,调用的是Popen对象的communicate方法,而不是调用wait方法,所以不是阻塞的。Popen对象的communicate方法返回一个元组(stdoutdata, stderrdata)
(4) Popen (…) 一个类,用于灵活地在一个新的进程中执行命令
4. class Popen
(1)Popen类的构造函数及其参数解释
参数:
args:一个字符串,或程序参数列表
bufsize: 当创建标准输入/标准输出/标准错误输出 管道文件对象时,该参数作为open()函数的缓冲区参数
executable:要执行的替换程序。一般不用
stdin,stdout, stderr: 分别指示要执行的程序标准输入、标准输出、标准错误输出文件的句柄
preexec_fn:(POSIX only),An object to be called in the childprocess just before the child is executed. 在子进程中调用的对象,仅在子进程执行(execuate)之前调用。钩子函数,在fork和exec之间执行。
close_fds:控制文件描述符的关闭和继承
shell:如果设为True,该命令会通过shell执行
cwd:在子进程执行之前,设置当前的工作目录
env:为新的进程定义环境变量
universal_newline:如果为True,在标准输入、输出、错误输出文件对象中,使用同一的行结束符 \n。
startupinfo和creationflags,仅在windows中使用
(2)Popen实例属性
def __init__(self, args, ,,, stdin, stdout,stderr,)
(3)communicate
和进程交互:给stdin发送数据,从stdout和stderr读取数据,直到读到end of file。等待进程种植。可选的输入参数为发送给子进程的一个字符串,如果没有要发送给子进程的数据的话,为空
check_output()函数中,调用Popen实例process的communicate方法
def _communicate(self, input): stdout = None # Return stderr = None # Return if self.stdout: stdout = [] stdout_thread = threading.Thread(target=self._readerthread, args=(self.stdout, stdout)) stdout_thread.setDaemon(True) # Thread对象的setDaemon方法 stdout_thread.start() if self.stderr: stderr = [] stderr_thread = threading.Thread(target=self._readerthread, args=(self.stderr, stderr)) stderr_thread.setDaemon(True) stderr_thread.start() if self.stdin: if input is not None: try: self.stdin.write(input) except IOError as e: if e.errno == errno.EPIPE: # communicate() should ignore broken pipe error pass elif e.errno == errno.EINVAL: # bpo-19612, bpo-30418: On Windows, stdin.write() # fails with EINVAL if the child process exited or # if the child process is still running but closed # the pipe. pass else: raise self.stdin.close() if self.stdout: stdout_thread.join() if self.stderr: stderr_thread.join() # All data exchanged. Translate lists into strings. if stdout is not None: stdout = stdout[0] if stderr is not None: stderr = stderr[0] # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) |
(4)poll
检查子进程是否结束,设置并返回returncode属性
5. 进程和线程
转自:
对于操作系统来说,一个任务就是一个进程(process),比如打开一个浏览器就启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个word就启动了一个word进程。
有些进程还不只同时干一件事,这就是由一个进程内的多个线程完成的。比如word, 它可以同时进行打字和拼写检查等。
多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才能实现。
同时执行多个任务,通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂时等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行。
因此,一般不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。比如在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话,只能先把视频播放完再播放音频。
python既支持多进程,又支持多线程。
线程是最小的执行单元,即CPU调度的最小单元,进程由一个或多个线程组成。如何调度线程和进程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
多线程和多进程的程序涉及到同步、数据共享的问题。
join方法可以等待子进程结束后再继续往下执行,通常用于进程间的同步。
6. lock()
转自:
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在每个进程中,互不影响;而多线程中,所有变量都由线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
来看多个线程同时操作一个变量,怎么把内存给该乱了:
importtime, threading
#假定这是你的银行存款
balance= 0
defchange_it(n):
#先存后取,结果应该为0
global balance
balance= balance + n
balance= balance - n
defrun_thread(n):
forI in range(100000):
change_it(n)
t1= threading.Thread(target = run_thread, args = (5, ))
t2= threading.Thread(target = run_thread, args = (8, ))
t1.start()
t2.start()
t1.join()
t2.join()
print(blance)
理论上结果应该为0,但是由于线程的调度是由操作系统决定的,当t1, t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。
原因是因为高级语言的一条语句在CPU执行时,是若干条语句,即使是一个简单的计算:balance = balance + n, 也分两步: 1)计算balance + n, 存入临时变量中;2)将临时变量的值赋给balance。
也就是说,可以看成:
x= balance + n
balance= x
由于x 是局部变量,两个线程各自都有自己的x,当代码正常执行时,
但是,t1和t2是交替运行的,如果操作系统以下面的顺序执行t1, t2:
究其原因,是因为修改balance需要多条语句(高级语言是一条,但CPU执行时可能是多条),而执行者几条语句时,线程可能会中断,从而导致多个线程把同一个对象的内容改乱了。
两个线程同时一存一取,就可能导致余额不对,所以我们必须确保一个线程在修改balance的时候,别的线程一定不能改;也就是说,在修改balance的操作上,不能并行,执行串行。
如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说该线程因为获得了锁,所以其他线程不能同时执行change_it(),只能等待。同一时刻最多只有一个线程持有该锁,所以不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:
balance = 0 lock = threading.Lock() def run_thread(n): for i in range(100000): # 先要获取锁: lock.acquire() try: # 放心地改吧: change_it(n) finally: # 改完了一定要释放锁: lock.release() |
获得锁的线程用完后一定要释放锁,否则哪些苦苦等待锁的线程将永远等待下去,称为死线程。所以我们用try… finally来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
多核CPU
如果你不幸拥有一个多核CPU,你肯定在想,多核应该可以同时执行多个线程。
如果写一个死循环的话,会出现什么情况呢?
打开Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以监控某个进程的CPU使用率。我们可以监控到一个死循环线程会100%占用一个CPU。如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。
试试用Python写个死循环:
import threading, multiprocessing
def loop():
x= 0
while True:
x = x ^ 1
for i inrange(multiprocessing.cpu_count()):
t= threading.Thread(target=loop)
t.start()
启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有160%,也就是使用不到两核。
即使启动100个线程,使用率也就170%左右,仍然不到两核。
但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
7. python 中的global关键字
python中global关键字主要用于声明变量的作用域
在C语言中,由于变量一定是先声明,后使用,所以我们可以清楚的知道,现在使用的变量是全局还是局部;在python中,变量不需要先声明,直接使用即可,那我们怎么知道用的是局部变量还是全局变量呢?首先,python使用的变量,在默认情况下一定是局部变量。其次,python如果想使用作用域之外的全局变量,需要加global前缀。
如,不用global的情况:
a= 5
deftest():
a = 1 局部变量,和外部的a不是一个变量(list类型除外)
print“In test function : a = %d”% a
test()
print“global a = %d ”% a
程序执行结果为:
Intest function: a = 1
globala = 5
可以看出,不加global的时候,在函数内部是改不了外面的全局变量的(list类型除外)
下面是加了global前缀的情况:
a= 5
deftest():
#告诉执行引擎,我要用全局变量a
globala
a= 1
print“In test func: a = %d ”% a
test()
print“Global a = %d ”% a
可以看出,在函数test的内部,成功修改了全局变量的数值
事实上,网络上很多文章推崇另外的一种方法来使用全局变量:使用单独的global文件。
方法如下:
1)在同一个文件夹下,新建两个文件; myglobal.py, test.py
#myglobal.py
a= 0
b= 1
c= 2
d= 3
#test.py
importglobal.py
deftest():
myglobal.a =100
print“myglobal a = %d” % myglobal.a
test()
print“after test, myglobal a = %d”% myglobal.a
转自 https://blog.csdn.net/diaoxuesong/article/details/42552943
8. python 常量(没有专门定义常量的方式)
这是subprocess.py文件中的两个常量。
python中没有专门定义常量的方式,通常使用大写变量名表示,仅仅是一种提示效果,本质还是变量。如NAME = ‘tony’
参考:
https://blog.csdn.net/bcfdsagbfcisbg/article/details/78134172?locationNum=7&fps=1
https://blog.csdn.net/wtq1993/article/details/51194119