多线程开发在各种语言中都是被支持的,通过多线程可以高效利用机器,让程序性能也相应的提高。python支持多线程,而且还支持多进程的开发。
在python中有两个库和多线程有关,一个是threading,另外一个是tread。两个的关系是threading库是thread库的更高层次的封装,通过使用threading库更加容易使用多线程进行相关开发。在有些时候,如果thread库无法使用或者缺失的时候,存在另外一个库可以代替dummy_threading,不过过这个库在新的版本中已经被废弃,所以不是迫不得已就不要使用该库进行多线程的开发。
因为python有不同的实现,有C、Java而且还有python自己的实现,所以不同的实现其相应的解释器也不会一样,对python多线程实现和支持就有些许的不同。对于CPython来说,因为它的解释器采用的是全局解释器所。通过在其解释器层面施加一个全局锁来保证同一时刻只有一个线程可以拥有锁,执行相应的python字节码。所以虽然冠名是是多线程,但是实质上还是只有一个线程在运行。有时候多线程可能会让程序得不到提高反而降低,因为线程之间需要竞争资源。所以很多人也说,如果想真正的同一时刻执行多个任务的话,就需要使用多进程。
多线程
多线程的实现
在python中多线程的实现还是很简单的,主要有两种方式。
1)你可以通过继承threading.Thread的方式实现,这和其他语言比较类似;
2)除此之外你还可以通过传递一个可以调用的对象[1]的方式启动多线程。
当采用第一种继承的方式实现多线程的时候必须记住覆盖父类的init和run方法,否则你所写的逻辑就无法执行。下面我们根据具体的例子进行详细的介绍:
def GET(self):上述的例子是我接受一个请求,然后去另一台机器scp文件,然后对文件进行处理的一个小程序。上述我的多线程实现就是采用了第二种方式,初始化Thread的时候传递一个可调用的对象。这只是多线程最简单的一种实现和运用方式。除此之外python对于多线程还提供了一些基本的其他的操作。比如线程的join,检测线程是否活跃,设置线程为守护线程[2]。
params = web.input()
self.logger.info(params)
files = []
files.append(params["matchfile"])
files.append(params["unmatchfile"])
files.append(params["partmatchfile"])
try:
t = threading.Thread(target = self.scpAndHandlerResult, args = (files,))
t.start()
except Exception, e:
self.logger.error("exception:" + str(e))
return json.dumps({"msg" : "handler the notice failed!!!"})
return json.dumps({"status" : "success", "msg" : "got your notice"})
def scpAndHandlerResult(self, files):
self.scp(files)
baseFiles = []
for f in files:
baseFiles.append(os.path.basename(f))
self.handler(baseFiles)
多线程介绍
对于python线程的join和java里的多线程有一些不同。在python中采用join方法之后会直接阻塞当前线程,而去执行被join的线程;而对于java来说,则是执行了当前线程之后再去执行被join的线程。
join([timeout])
Wait until the thread terminates. This blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception – or until the optional timeout occurs.
除了上述需要注意语言之间的差距之外,对于python本身来说把线程设置为守护线程也需要注意。
对于设置线程为守护进程需要格外小心。因为守护进程是在所有任务都退出了之后才会退出,当守护线程里打开了比较多的资源的时候,或者其他线程出现问题无法正常推出的时候,这个时候就会占据较多资源导致问题的出现。
在多线程中提供了多种线程之间的同步,包括信号,锁和可重入的锁,这个地方需要着重关注。
线程之间共享数据
因为线程之间可以共享进程的数据,所以只要多个线程是在同一个进程下面,那么就可以直接共享数据。但是如果两个线程不是在同一个进程之间,那么它们之间想共享数据的话,那么只能是通过进程之间通信进行共享数据了。
class AppClass:
def __init__(self):
self.log_fd = open("log","a+")
self.rtic={}
p = threading.Thread(target=self.getRTIC, args=("",))
p.start()
def __del__(self):
self.log_fd.close()
def setRTIC(self,data, in_rtic):
if not data:
return
for line in data.split(DEL):
if(line != ""):
k, v = line.split(":")
self.rtic[k] = int(v)
def recvAll(self,sock,length):
data = ""
while(len(data) < length):
packet = sock.recv(length - len(data))
if not packet:
return None
data += packet
return data
def getRTIC(self,params):
HOST = "127.0.0.1"
PORT = 9090
while(1):
a = time.time()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST,PORT))
sock.sendall("")
data = self.recvAll(sock, 15)
if data:
length = int(data.split(DEL)[0])
data = self.recvAll(sock, length)
self.setRTIC(data, params)
b = time.time()
cost = b - a
#break
time.sleep(TIME_SEG - cost)
比如上面代码,在一个类中启动一个线程建立socket获取数据,然后把获取的数据更新到类的成员变量rtic中。因为该类启动的时候,去获取信息的线程也启动,之间可以共享数据。所以在该类中有其他方法可以直接共享访问rtic,但是在这个时候就需要考虑多线程之间的同步操作了。不过在我们这个场景中,是只有一个线程进行写的操作,其他都是读操作,多线程之间的同步还不是很复杂。
从上述例子可以看出,上面的场景是一写多读的情况,很明显的可以利用读写锁,提高多线程之间的性能。但是在python中不知为何没有提供读写锁的支持进行线程之间同步访问。在附注中贴出几个链接[3]是几个读写锁的实现,但是没有纳入python标准中。
多进程
多进程使用
python想实现真正的并发运行程序需要的是多进程,具体的api在multiprocessing包里面,通过使用Process进行相应的多进程的开发。Process的使用和Thread使用类似,通过声明一个Process对象,调用start方法启动进程。参考官网上的一个例子:
from multiprocessing import Process无论是多进程还是多线程,在开发中尽量避免进程之间的数据交换和状态的共享。一般进程之间的数据共享和数据传递有共享内存,管道,socket等。在python中提供了基于管道和队列的数据交换,也提供了基于内存的数据状态共享。
def f(name):
print 'hello', name
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()
subprocess介绍
在多进程的使用中,有一个模块是非常有帮助的,那就是subprocess[2]模块。该模块在很大程度上替换了原来python执行本地命令的那几个模块,包括os.popen、os.system等几个模块。
通过使用subprocess,可以很方便的完成上述几个模块的功能。它的实现就是新生成一个进程,执行给出的相应的命令,包括shell命令,或者其他应用程序的执行命令。在该模块中有一个常用的方法就是call,还有一个核心的模块就是Popen。其实当调用call方法的时候,就是调用了Popen模块进行相应的新的进程的生成和启动。下面给出几个简单示例。
from subprocess import call需要注意的是,在该模块中是不可以使用相对路径的,它所有的路径都是针对当前路径,除非你使用绝对路径,或者chcwd。另外就是使用call或者Popen传递命令的时候,可以使用字符串如同上述示例,也开始使用数组。比如下面示例
filename = input("What file would you like to display?\n")
call("cat " + filename, shell=True)
subprocess.call(['ls', '-l'], shell=True)
附录:
[1] 可调用:在python中检测一个对象是否可调用是通过callable(obj)方法来检测的,一个对象是否可调用并不是唯一通过该方法来判定的。该方法返回True,但是该对象并不一定可调用;然后当返回False的时候,该对象确定不可调用。对一个类调用该方法返回True。https://docs.python.org/2/library/subprocess.html,但是如果该类中没有__call__方法,调用该类的时候依旧会出错。
[2] subprocess: https://docs.python.org/2/library/subprocess.html
[3]RWLock http://bugs.python.org/issue8800
http://code.activestate.com/recipes/577803-reader-writer-lock-with-priority-for-writers/