多线程多进程
多进程
概念
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的(静态的),进程是活的(动态的)。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身;用户进程就不必我多讲了吧,所有由你启动的进程都是用户进程。进程是操作系统进行资源分配的单位。
它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。
多进程和多线程的区别: 多线程使用的是cpu的一个核,适合io密集型 多进程使用的是cpu的多个核,适合运算密集型 组件 Python提供了非常好用的多进程包,multiprocessing,我们在使用的时候,只需要导入该模块就可以了。 Multiprocessing支持子进程,通信,共享数据,执行不同形式的同步,提供了Process,Pipe, Lock等组件
Process 1. 创建一个Process对象 p = multiprocessing.Process(target=worker_1, args=(2, )) target = 函数名字 args = 函数需要的参数,以tuple的形式传入 注意: 单个元素的tuple的表现形式 multprocessing用到的两个方法 cpu_count() 统计cpu总数 active_children() 获得所有子进程
Process 1. Process的常用方法 is_alive() 判断进程是否存活 run() 启动进程 start() 启动进程,会自动调用run方法,这个常用 join(timeout) 等待进程结束或者直到超时 2. Process的常用属性 name 进程名字 pid 进程的pid
代码例子:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # @Time : 2017/10/20 21:32# 4 # @File : demon1.py 5 import multiprocessing 6 import time 7 def work(interval, method): 8 print("start work_" + method) 9 time.sleep(interval) 10 print("end work_" + method) 11 if __name__ == "__main__": 12 p1 = multiprocessing.Process(target=work, args=(1, "1")) 13 p2 = multiprocessing.Process(target=work, args=(2, "2")) 14 p3 = multiprocessing.Process(target=work, args=(3, "3")) 15 p1.start() 16 p2.start() 17 p3.start() 18 print("The number of CPU is:") + str(multiprocessing.cpu_count()) 19 for p in multiprocessing.active_children(): 20 print("The name of active child is: " + p.name + ", pid is: " + str(p.pid) + "is alive: " + str(p.is_alive())) 21 print("MAIN IS END!")
结果:
C:Python27python.exe E:/python/12process/demon1.py
The number of CPU is:4
The name of active child is: Process-3, pid is9788is alive: True
The name of active child is: Process-1, pid is6936is alive: True
The name of active child is: Process-2, pid is3052is alive: True
MAIN IS END!
start work_2
start work_1
start work_3
end work_1
end work_2
end work_3
Lock组件
当我们用多进程来读写文件的时候,如果一个进程是写文件,一个进程是读文件,如果两个文件同时进行,肯定是不行的,必须是文件写结束以后,才可以进行读操作。或者是多个进程在共享一些资源的时候,同时只能有一个进程进行访问,那就要有一个锁机制进行控制。
需求:
一个进程写入一个文件,一个进程追加文件,一个进程读文件,同时启动起来 我们可以通过进程的join()方法来实现,但是为了学习Lock,
用Lock来实现。 先看不加锁程序,在看加锁程序,最后比较两个程序的区别
代码例子:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import multiprocessing 4 import time 5 def add1(lock, value, number): 6 with lock: 7 print("start add1 number = {0}".format(number)) 8 for i in xrange(1, 5): 9 number += value 10 time.sleep(0.3) 11 print("number = {0}".format(number)) 12 def add3(lock, value, number): 13 lock.acquire() 14 print("start add3 number = {0}".format(number)) 15 try: 16 for i in xrange(1, 5): 17 number += value 18 time.sleep(0.3) 19 print("number = {0}".format(number)) 20 finally: 21 lock.release() 22 if __name__ == "__main__": 23 lock = multiprocessing.Lock() 24 number = 0 25 pw = multiprocessing.Process(target=add1, args=(lock, 1, number)) 26 pa = multiprocessing.Process(target=add3, args=(lock, 3, number)) 27 pw.start() 28 pa.start() 29 print("main process end.")
结果:
main process end.
start add1 number = 0
number = 1
number = 2
number = 3
number = 4
start add3 number = 0
number = 3
number = 6
number = 9
number = 12
共享内存
python的multiprocessing模块也给我们提供了共享内存的操作
一般的变量在进程之间是没法进行通讯的,multiprocessing给我们提供了Value和Array模块,他们可以在不通的进程中共同使用
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 from multiprocessing import Process, Value, Array 4 def f(n, a,m): 5 n.value = 3.1415927 6 m = 20 7 for i in range(len(a)): 8 a[i] = -a[i] 9 print(m) 10 if __name__ == '__main__': 11 num = Value('d', 0.0) 12 arr = Array('i', range(10)) 13 m = 10 14 p = Process(target=f, args=(num, arr, m)) 15 p.start() 16 p.join() 17 18 print(num.value) 19 print(arr[:]) 20 print(m)
结果:
20
3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
10
强大的Manage
以上实现的数据共享的方式只有两种结构Value和Array。Python中提供了强大的Manage专门用来做数据共享的,其支持的类型非常多,包括,Value, Array,list,dict, Queue, Lock等。
下面看个例子:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 from multiprocessing import Process, Manager 5 def func(dt, lt): 6 for i in range(10): 7 key = 'arg' + str(i) 8 dt[key] = i * i 9 lt += range(11, 16) 10 if __name__ == "__main__": 11 manager = Manager() 12 dt = manager.dict() 13 lt = manager.list() 14 15 p = Process(target=func, args=(dt, lt)) 16 p.start() 17 p.join() 18 print(dt) 19 print(lt)
结果:
{'arg8': 64, 'arg9': 81, 'arg0': 0, 'arg1': 1, 'arg2': 4, 'arg3': 9, 'arg4': 16, 'arg5': 25, 'arg6': 36, 'arg7': 49}
[11, 12, 13, 14, 15]
进程池
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程。
下面我们先来看一个进程池非阻塞的例子:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import multiprocessing 4 import time 5 def fun(msg): 6 print("######start###### {0}".format(msg)) 7 time.sleep(3) 8 print("######end###### {0}".format(msg)) 9 if __name__ == "__main__": 10 pool = multiprocessing.Pool(processes=3) 11 for i in xrange(1, 6): 12 msg = "hello {0}".format(i) 13 pool.apply_async(fun, (msg,)) 14 15 print("##########start main#########") 16 pool.close() 17 pool.join() # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 18 19 print("##########end main#########")
结果:
##########start main#########
######start###### hello 1
######start###### hello 2
######start###### hello 3
######end###### hello 2
######end###### hello 1
######start###### hello 4
######start###### hello 5
######end###### hello 3
######end###### hello 5
######end###### hello 4
##########end main#########
进程池
阻塞和非阻塞的区别:
Pool.apply_async 非阻塞,定义的进程池进程最大数可以同时执行。
Pool.apply 一个进程结束,释放回进程池,下一个进程才可以开始
Python中提供了threading模块来对多线程的操作,
1. 多线程实例
线程是应用程序中工作的最小单元。
多线程是现实有两种方式:
方法一:将要执行的方法作为参数传给Thread的构造方法(和多进程类似)
t = threading.Thread(target=action, args=(i,))
方法二:从Thread继承,并重写run()
看源码:
P = threading.Thread
p.start() _start_new_thread(self.__bootstrap, ()) self.__bootstrap_inner()
self.run()
try:
if self.__target:
self.__target(*self.__args, **self.__kwargs)
所以如果重写了run,就直接调用run的函数了,如果run没有重新,就调用target函数。
2. 线程锁
通过threading.Lock()来创建锁,函数在执行的只有先要获得锁,左后执行完以后要释放锁:
要释放锁:
with lock:
lock.acquire()
lock.release()
实例:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import threading 4 import time 5 def worker(name, lock): 6 with lock: 7 print("start {0}".format(name)) 8 time.sleep(5) 9 print("end {0}".format(name)) 10 11 if __name__ == "__main__": 12 lock = threading.Lock() 13 t1 = threading.Thread(target=worker, args=("worker1", lock)) 14 t2 = threading.Thread(target=worker, args=("worker2", lock)) 15 t1.start() 16 t2.start()
结果:
start worker1
end worker1
start worker2
end worker2
3. 线程共享变量
线程和多进程不同之处在于多线程本身就是可以和父进程共享内存的,这也是为什么其中一个线程挂掉以后,为什么其他线程也会死掉的道理。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import threading 5 6 l = list() 7 l += range(1, 10) 8 9 def worker(): 10 l.append("ling") 11 l.append("shang") 12 l.append("hello") 13 if __name__ == "__main__": 14 t = threading.Thread(target=worker) 15 t.start() 16 print(l)
结果:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 'ling', 'shang', 'hello']
4. 线程池
通过传入一个参数组来实现多线程,并且它的多线程是有序的,顺序与参数组中的参数顺序保持一致。
安装包:
pip install threadpool
调用格式:
from threadpool import *
pool = ThreadPool(poolsize)
requests = makeRequests(some_callable, list_of_args, callback)
[pool.putRequest(req) for req in requests]
pool.wait()
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # @File : test14.py 4 import threadpool 5 def hello(m, n, o): 6 """""" 7 print "m = %s, n = %s, o = %s" % (m, n, o) 8 9 if __name__ == '__main__': 10 # 方法1 11 lst_vars_1 = ['1', '2', '3'] 12 lst_vars_2 = ['4', '5', '6'] 13 func_var = [(lst_vars_1, None), (lst_vars_2, None)] 14 # 方法2 15 dict_vars_1 = {'m': '1', 'n': '2', 'o': '3'} 16 dict_vars_2 = {'m': '4', 'n': '5', 'o': '6'} 17 func_var = [(None, dict_vars_1), (None, dict_vars_2)] 18 19 pool = threadpool.ThreadPool(2) 20 requests = threadpool.makeRequests(hello, func_var) 21 [pool.putRequest(req) for req in requests] 22 pool.wait()
1 #!/usr/bin/env python# 2 # -*- coding:utf-8 -*- 3 # # @File : demon6.py#!/usr/bin/env python 4 from multiprocessing import Process, Queue 5 import os, time, random 6 # 写数据进程执行的代码: 7 def write(q): 8 print('Process to write: %s' % os.getpid()) 9 for value in ['A', 'B', 'C']: 10 print('Put %s to queue...' % value) 11 q.put(value) 12 time.sleep(random.random()) 13 #读数据进程执行的代码: 14 def read(q): 15 print('Process to read: %s' % os.getpid()) 16 while True: 17 value = q.get(True) 18 print('Get %s from queue.' % value) 19 if __name__=='__main__': 20 # 父进程创建Queue,并传给各个子进程: 21 q = Queue() 22 pw = Process(target=write, args=(q,)) 23 pr = Process(target=read, args=(q,)) 24 # 启动子进程pw,写入: 25 pw.start() 26 # 启动子进程pr,读取: 27 pr.start() 28 #等待pw结束: 29 pw.join() 30 # pr进程里是死循环,无法等待其结束,只能强行终止: 31 pr.terminate()
消息队列
Python提供了Queue模块来专门实现消息队列
Queue对象
Queue对象实现一个fifo队列(其他的还有lifo、priority队列,这里不再介绍)。queue只有maxsize一个构造参数,用来指定队列容量,指定为0的时候代表容量无限。主要有以下成员函数:
Queue.qsize():返回消息队列的当前空间。返回的值不一定可靠。
Queue.empty():判断消息队列是否为空,返回True或False。同样不可靠。
Queue.full():类似上边,判断消息队列是否满
Queue.put(item, block=True, timeout=None):往消息队列中存放消息。block可以控制是否阻塞,timeout指定阻塞时候的等待时间。如果不阻塞或者超时,会引起一个full exception。
Queue.put_nowait(item):相当于put(item, False).
Queue.get(block=True, timeout=None):获取一个消息,其他同put。
以下两个函数用来判断消息对应的任务是否完成。
Queue.task_done():接受消息的线程通过调用这个函数来说明消息对应的任务已完成。
Queue.join(): 实际上意味着等到队列为空,再执行别的操作