进程
创建进程的常见方式:
使用multiprocessing模块创建进程:
multiprocessing模块提供了一个Process类来代表一个进程对象
Process(group,target,name,args,kuargs)
...
group 参数未使用值始终未None
target 表示当前进程启动时执行的可调用对象
name 为当前进程实例的别名
args 表示传递给target函数的参数元组
kuargs 表示传递给target函数的参数字典
...
例:
from multiprocessing import Process #导入模块
#执行子进程代码
def test(interval):
print("我是子进程")
#执行主程序
def main():
print("主程序开始");
p = Process(target = test,args = (1,));
p.start();
print(“主程序结束”)
if __name__ == __main__:
main()
Process类的提供的方法及说明
方法 | 说明 |
---|---|
start() | 启动子进程 |
is_alive() | 检测子进程是否还在执行 |
run() | 没有给target参数,这个对象在调用start()的方法时就会执行对象的run() |
join([timeout]) | 等待进程结束,可省略参数等待多少秒 |
terminate() | 不管任务是否完成立即结束 |
name | 当前实例的别名:默认Process-N,N为从1开始的整数 |
pid | 当前进程实例的PID值 |
使用Proess子类创建进程
对于一些简单的小任务通常可以使用 Proess(target = test)的方式实现多进程。但是如果要处理复杂的任务的进程,通常定义一个类,使其继承Proess类 ,每次实例化这个类的时候就等同实例化一个进程对象。
例:(附运行结果)
# -*- coding:utf-8 -*-
from multiprocess import Process
import time
import os
#继承Process类
class SubProcess(Process):
...
由于Process类本身也有__init__初始化方法,这个子类相当于重写了父类的这个方法
...
def __init__(self,interval,name = ""):
Process.__init__(self) #调用父类初始化方法
self.interval = interval
if name:
self.name = name
#重写Process的run()方法
def run():
print("子进程(%s)开始执行,父进程(%s)",%(os.getpid(),os.getppid()))
t_start = time.time()
time.sheep(self.interval)
t_stop = time.time()
printf("子进程(%s)执行结束,耗时:%0.2f秒",%(os.getpid,t_start - t_stop))
if __name__ == "__main__" :
print("-------父进程开始执行-------\n父进程PID:%s",%(os.getpid))
p1 = SubProcess(interval = (1,),name = "花猪")
p2 = SubProcess(interval = (2,))
p1.start()
p2.start()
print("p1 .is_alive = %s",%p1.is_alive)
print("p2 .is_alive = %s",%p2.is_alive)
print("p1.name = %s\np1.pid = %s",%(p1.name,p1.pid))
print("p2.name = %s\np2.pid = %s",%(p2.name,p2.pid))
print("-------等待子进程-------")
p1.join()
p2.join()
print("-------主进程执行结束-------")
上述代码中定义了一个SubProcess子类,继承了multiprocess.Process父类。在SubProcess子类中定义了两个方法:_ init_()初始化方法和run()方法,在初始化_ init_()方法中调用了multiprocess.Process父类的_ init_()初始化方法,否则父类的初始化方法会被覆盖,无法开启进程,此外SubProcess类中并未定义start()方法,但是在主进程却调用了start()。此时调用的SubProcess.start()是从Process中继承过来的。由于在Process.start()中调用了Process.run(),而在继承过程中Process,run()的方法又被SubProcess.run()的方法覆盖掉了,所以就会出现这种,调用在子类不存在的父类方法却执行子类方法的情况。啰嗦了这么多,算是滾固一下类的继承吧。Ps:(“滾固”是这么写么)
-------父进程开始执行-------
父进程PID:8472
p1 .is_alive = True
p2 .is_alive = True
p1.name = 花猪
p1.pid = 19560
p2.name = Process-2
p2.pid = 19816
-------等待子进程-------
子进程(19560)开始执行,父进程(8472)
子进程(19590)执行结束,耗时:1.00秒
子进程(19816)开始执行,父进程(8472)
子进程(19816)执行结束,耗时:2.00秒
-------主进程执行结束-------
使用进程池创建进程
上面描述了,处理少量的简单的多进程情况和少量的复杂的多进程情况,但是如果说要创建几十个或则上百个进程的时候则需要实例化更多的Process类。这个时候就需要用到multiprocess模块提供的Pool类及Pool进程池
Pool类常用的方法及说明
方法 | 说明 |
---|---|
apply_async(func,[args,[kwds) | 非阻塞方式调用func()函数args为参数列表,kwds为关键字参数列表 |
apply(func,[args,[kwds) | 阻塞方式调用(阻塞方式必须等待上一个进程结束才能执行下一个进程) |
close() | 关闭Pool进程池停止接受任务 |
terminate() | 不管任务是否接受,立即终止 |
join() | 主进程阻塞等待子进程的退出,必须在close()或则terminate()后使用 |
例:
# -*- coding:utf-8 -*-
from multiprocess import Pool
import time,os
def task(name):
print("子进程(%s),执行task%s...",%(os.getpid,name))
time.sleep(1);
if __name__ == "__main__":
print("父进程(%s)",%os.getpid)
p = Pool(3) #创建容量为3的进程池
for i in range(10):
p.apply_async(func = task,args = (i,))
print("等待所有子进程结束...”)
p.close()
p.join()
print("所有子进程结束”)
通过结果可以看出Pool进程池的三个进程
(7092)执行了四个任务0,3,6,9
(21178)执行了三个任务1,4,7
(15580)执行了三个任务2,5,8
Pool进程池可以理解成一个水池,初始化了三个水龙头(当然可以更多)把任务当做水桶,执行的过程当做接水。
而Process的方式可以理解成,初始化一个水龙头为了给当前水龙头下的水池接水。
具体什么时候用Pool什么时候用Process不用再解释了吧。
父进程(11488)
等待所有子进程结束…
子进程(7902)执行0…
子进程(21179)执行1…
子进程(15580)执行2…
子进程(7902)执行3…
子进程(21179)执行4…
子进程(15580)执行5…
子进程(7902)执行6…
子进程(21179)执行7…
子进程(15580)执行8…
子进程(7902)执行9…
所有子进程结束
通过队列进行进程间通信
不介绍队列了,直接介绍Python中的队列类,在multiprocess模块中的Queue实现了多进程之间的数据传递。Queue本身是一个消息队列程序,下面简单介绍一下Queue的使用
初始化 Queue对象时(例:q = Queue(num))num为队列最大可接收消息数量,默认或者负数则表示无穷大知道内存尽头。
Queue常用方法和说明
方法 | 说明 |
---|---|
qsize() | 当前队列中的消息数量 |
empty() | 当前队列是否为空 |
full() | 当前队列是否满了 |
get([block,[timeout) | 从队列里获取一条数据,block,默认True 队列为空阻塞等待数据,False不阻塞。timeout阻塞等待时间秒数,默认为无限 |
get_nowait() | 相当于 Queue.get(False)非阻塞获取数据 |
put(item,[block,[timeout) | 向队列中添加一条item数据, block,默认True 队列为满阻塞等待数据,False不阻塞。timeout阻塞等待时间秒数,默认为无限 |
put_nowait() | 相当于 Queue.put(item,False)非阻塞获取数据 |
线程
还是简单介绍一下线程吧
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程当中。进程是实际执行的单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程可以执行不同的任务。例如浏览器的打开的多个网页就属于多个进程。一个视频网页中的播放视频的部分就有一个播放视频线程一个播放音频的线程,需要并发才能使音频视频同步,两个线程受同时受进度条的控制。(其实我不懂就算比喻吧)。再做个比喻吧,进程好比房间有相对应的属性比如占地面积、卧室、厨房、卫生间等,房子本身不能做任何事情,而线程就好比这个房子的居住者,他可以使用房子的每一个房间做饭,洗澡等。
创建线程
Python的标准库提供了两个模块:_thread 和 threading,_thread是低级模块,threading是高级模块,是对_thread进行了封装。所以我们只需要学会使用threading就可以了。
其实threading的用法和Process的用法差不多,此次为了凑字数还是简单的介绍一下吧。两者的内部实现以后有时间在研究吧。
使用threading模块创建进程:
threading(group,target,name,args,kuargs)
...
group 参数未使用值始终未None
target 表示当前进程启动时执行的可调用对象
name 为当前进程实例的别名,默认Thread-N
args 表示传递给target函数的参数元组
kuargs 表示传递给target函数的参数字典
...
使用Thread子类创建线程:
# -*- coding:utf-8 -*-
import time,threading
#继承threading的Thread类
class SubThread(threading.Thread):
def run(self):
for i in range(3):
time.sheep(1)
msg = "子线程:" + self.name + "执行步骤" + str(i+1)
printf(msg)
if __name__ == "__main__" :
print("-------主线程开始执行-------)
p1 = SubThread()
p2 = SubThread()
p1.start()
p2.start()
p1.join()
p2.join()
print("-------主线程程执行结束-------")
-------主线程开始执行-------
子线程:Thread-1执行步骤1
子线程:Thread-2执行步骤1
子线程:Thread-1执行步骤2
子线程:Thread-2执行步骤2
子线程:Thread-1执行步骤3
子线程:Thread-2执行步骤3
-------主线程程执行结束-------
线程通信
同一进程中的线程线程是可以共享信息的,由于线程可以对全局变量进行修改,这就可造成多线程之间对全局变量的混乱操作。所以引入线程锁。
在threading模块中使用Lock类可以方便的处理锁定。Lock类有2个方法:acquire() 锁定和release()释放锁示例如下
mutex = threading.Lock() #创建锁
mutex.acquire() #锁定
mutex.release() #释放锁
具体实例:
from threading import Thread,Lock
import time
n = 100
def task():
global n
mutex.acquire()
temp = n;
time.sleep(0.1)
n = temp -1;
print("购票成功,剩余%d张电影票"%n)
mutex.release()
if __name__ =="__main__":
mutex = Lock()
t_l = []
for i in range(10):
t = Thread(target = task)
t_l.append(t)
t.start()
for t in t_l:
t.join()
使用队列进行线程间通信
我们知道multiprocess模块的Queue队列可以实现进程间通信,同样在线程间也可以使用Queue实现线程间通信。不同之处在于我们需要使用queue模块的Queue队列而不是multiprocess模块的Queue队列,但是使用方法相同,参照上文的“使用队列进行进程间通信”部分(懒得写例子了)